From 176bf17d153b0c8e893c089e91ebdafed4af39b5 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sun, 25 Sep 2022 02:44:38 +0200 Subject: [PATCH 1/3] remove sessions.zig --- tests/sessions.zig | 105 --------------------------------------------- 1 file changed, 105 deletions(-) delete mode 100644 tests/sessions.zig diff --git a/tests/sessions.zig b/tests/sessions.zig deleted file mode 100644 index 012afea..0000000 --- a/tests/sessions.zig +++ /dev/null @@ -1,105 +0,0 @@ -const std = @import("std"); -const Context = @import("context.zig").Context; - -const allocator = std.testing.allocator; - -test "Request completion in an empty file" { - var ctx = try Context.init(); - defer ctx.deinit(); - - try ctx.request("textDocument/didOpen", - \\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":""}} - , null); - try ctx.request("textDocument/completion", - \\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":0,"character":0}} - , null); -} - -test "Request completion with no trailing whitespace" { - var ctx = try Context.init(); - defer ctx.deinit(); - - try ctx.request("textDocument/didOpen", - \\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"const std = @import(\"std\");\nc"}} - , null); - - try ctx.request("textDocument/completion", - \\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":1}} - , - \\{"isIncomplete":false,"items":[{"label":"std","labelDetails":{"detail":"","description":"@import(\"std\")","sortText":null},"kind":21,"detail":"std","sortText":"1_std","filterText":null,"insertText":"std","insertTextFormat":1,"documentation":null}]} - ); -} - -test "Encoded space in file name and usingnamespace on non-existing symbol" { - var ctx = try Context.init(); - defer ctx.deinit(); - - try ctx.request("textDocument/didOpen", - \\{"textDocument":{"uri":"file:///%20test.zig","languageId":"zig","version":420,"text":"usingnamespace a.b;\nb."}} - , null); - try ctx.request("textDocument/completion", - \\{"textDocument":{"uri":"file:///%20test.zig"}, "position":{"line":1,"character":2}} - , - \\{"isIncomplete":false,"items":[]} - ); -} - -test "Self-referential definition" { - var ctx = try Context.init(); - defer ctx.deinit(); - - try ctx.request("textDocument/didOpen", - \\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"const h = h(0);\nc"}} - , null); - try ctx.request("textDocument/completion", - \\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":1}} - , - \\{"isIncomplete":false,"items":[{"label":"h","labelDetails":{"detail":"","description":"h(0)","sortText":null},"kind":21,"detail":"h","sortText":"1_h","filterText":null,"insertText":"h","insertTextFormat":1,"documentation":null}]} - ); -} - -// This test as written depends on the configuration in the *host* machines zls.json, if `enable_snippets` is true then -// the insert text is "w()" if it is false it is "w" -// -// test "Missing return type" { -// var server = try Server.start(initialize_msg, null); -// defer server.shutdown(); - -// try server.request("textDocument/didOpen", -// \\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"fn w() {}\nc"}} -// , null); -// try server.request("textDocument/completion", -// \\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":1}} -// , -// \\{"isIncomplete":false,"items":[{"label":"w","kind":3,"textEdit":null,"filterText":null,"insertText":"w","insertTextFormat":1,"detail":"fn","documentation":null}]} -// ); -// } - -test "Pointer and optional deref" { - var ctx = try Context.init(); - defer ctx.deinit(); - - try ctx.request("textDocument/didOpen", - \\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"var value: ?struct { data: i32 = 5 } = null;const ptr = &value;\nconst a = ptr.*.?."}} - , null); - try ctx.request("textDocument/completion", - \\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":18}} - , - \\{"isIncomplete":false,"items":[{"label":"data","labelDetails":{"detail":"","description":"i32 ","sortText":null},"kind":5,"detail":"data","sortText":"3_data","filterText":null,"insertText":"data","insertTextFormat":1,"documentation":null}]} - ); -} - -// not fixed yet! -// test "Self-referential import" { -// var ctx = try Context.init(); -// defer ctx.deinit(); -// -// try ctx.request("textDocument/didOpen", -// \\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"const a = @import(\"test.zig\").a;\nc"}} -// , null); -// try ctx.request("textDocument/completion", -// \\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":1}} -// , -// \\{"isIncomplete":false,"items":[]} -// ); -// } From 00be49c5951f282f311364a1e3cf3abf00c580b2 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sun, 25 Sep 2022 02:45:02 +0200 Subject: [PATCH 2/3] add keyword snippets for autocomplete --- src/Server.zig | 44 +++++++++++++++++++++--- src/data/snippets.zig | 79 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 src/data/snippets.zig diff --git a/src/Server.zig b/src/Server.zig index 01eabd2..9a427f0 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -16,9 +16,11 @@ const shared = @import("shared.zig"); const Ast = std.zig.Ast; const tracy = @import("tracy.zig"); const uri_utils = @import("uri.zig"); -const data = @import("data/data.zig"); const diff = @import("diff.zig"); +const data = @import("data/data.zig"); +const snipped_data = @import("data/snippets.zig"); + const log = std.log.scoped(.server); // Server fields @@ -1160,6 +1162,30 @@ fn populateBuiltinCompletions(builtin_completions: *std.ArrayListUnmanaged(types truncateCompletions(builtin_completions.items, config.max_detail_length); } +fn populateSnippedCompletions( + allocator: std.mem.Allocator, + completions: *std.ArrayListUnmanaged(types.CompletionItem), + snippets: []const snipped_data.Snipped, + config: Config, + start_with: ?[]const u8, +) error{OutOfMemory}!void { + try completions.ensureUnusedCapacity(allocator, snippets.len); + + for (snippets) |snipped| { + if (start_with) |needle| { + if (!std.mem.startsWith(u8, snipped.label, needle)) continue; + } + + completions.appendAssumeCapacity(.{ + .label = snipped.label, + .kind = snipped.kind, + .detail = if (config.enable_snippets) snipped.text else null, + .insertText = if (config.enable_snippets) snipped.text else null, + .insertTextFormat = if (config.enable_snippets and snipped.text != null) .Snippet else .PlainText, + }); + } +} + fn completeBuiltin(server: *Server, writer: anytype, id: types.RequestId) !void { const tracy_zone = tracy.trace(@src()); defer tracy_zone.end(); @@ -1192,6 +1218,7 @@ fn completeGlobal(server: *Server, writer: anytype, id: types.RequestId, pos_ind .orig_handle = handle, }; try analysis.iterateSymbolsGlobal(&server.document_store, &server.arena, handle, pos_index, declToCompletion, context); + try populateSnippedCompletions(server.arena.allocator(), &completions, &snipped_data.generic, server.config.*, null); sortCompletionItems(completions.items, server.arena.allocator()); truncateCompletions(completions.items, server.config.max_detail_length); @@ -1437,7 +1464,7 @@ fn kindToSortScore(kind: types.CompletionItem.Kind) []const u8 { .Field => "3_", .Function => "4_", - .Keyword, .EnumMember => "5_", + .Keyword, .Snippet, .EnumMember => "5_", .Class, .Interface, @@ -1770,8 +1797,17 @@ fn completionHandler(server: *Server, writer: anytype, id: types.RequestId, req: return try respondGeneric(writer, id, no_completions_response); }; - if (req.params.position.character == 0) - return try respondGeneric(writer, id, no_completions_response); + if (req.params.position.character == 0) { + var completions = std.ArrayListUnmanaged(types.CompletionItem){}; + try populateSnippedCompletions(server.arena.allocator(), &completions, &snipped_data.top_level_decl_data, server.config.*, null); + + return try send(writer, server.arena.allocator(), types.Response{ + .id = id, + .result = .{ + .CompletionList = .{ .isIncomplete = false, .items = completions.items }, + }, + }); + } const source_index = offsets.positionToIndex(handle.document.text, req.params.position, server.offset_encoding); const pos_context = try analysis.getPositionContext(server.arena.allocator(), handle.document, source_index); diff --git a/src/data/snippets.zig b/src/data/snippets.zig new file mode 100644 index 0000000..1b2d8f9 --- /dev/null +++ b/src/data/snippets.zig @@ -0,0 +1,79 @@ +const types = @import("../types.zig"); + +pub const Snipped = struct { + label: []const u8, + kind: types.CompletionItem.Kind, + text: ?[]const u8 = null, +}; + +pub const top_level_decl_data = [_]Snipped{ + .{ .label = "fn", .kind = .Snippet, .text = "fn ${1:name}($2) ${3:!void} {$0}" }, + .{ .label = "pub fn", .kind = .Snippet, .text = "pub fn ${1:name}($2) ${3:!void} {$0}" }, + .{ .label = "struct", .kind = .Snippet, .text = "const $1 = struct {$0}" }, + .{ .label = "error set", .kind = .Snippet, .text = "const ${1:Error} = error {$0}" }, + .{ .label = "enum", .kind = .Snippet, .text = "const $1 = enum {$0}" }, + .{ .label = "union", .kind = .Snippet, .text = "const $1 = union {$0}" }, + .{ .label = "union tagged", .kind = .Snippet, .text = "const $1 = union(${2:enum}) {$0}" }, + .{ .label = "test", .kind = .Snippet, .text = "test \"$1\" {$0}" }, +}; + +pub const generic = [_]Snipped{ + // keywords + .{ .label = "align", .kind = .Keyword }, + .{ .label = "allowzero", .kind = .Keyword }, + .{ .label = "and", .kind = .Keyword }, + .{ .label = "anyframe", .kind = .Keyword }, + .{ .label = "anytype", .kind = .Keyword }, + .{ .label = "asm", .kind = .Keyword }, + .{ .label = "async", .kind = .Keyword }, + .{ .label = "await", .kind = .Keyword }, + .{ .label = "break", .kind = .Keyword }, + .{ .label = "callconv", .kind = .Keyword, .text = "callconv($0)" }, + .{ .label = "catch", .kind = .Keyword }, + .{ .label = "comptime", .kind = .Keyword }, + .{ .label = "const", .kind = .Keyword }, + .{ .label = "continue", .kind = .Keyword }, + .{ .label = "defer", .kind = .Keyword }, + .{ .label = "else", .kind = .Keyword, .text = "else {$0}" }, + .{ .label = "enum", .kind = .Keyword, .text = "enum {$0}" }, + .{ .label = "errdefer", .kind = .Keyword }, + .{ .label = "error", .kind = .Keyword }, + .{ .label = "export", .kind = .Keyword }, + .{ .label = "extern", .kind = .Keyword }, + .{ .label = "fn", .kind = .Keyword, .text = "fn ${1:name}($2) ${3:!void} {$0}" }, + .{ .label = "for", .kind = .Keyword, .text = "for ($1) {$0}" }, + .{ .label = "if", .kind = .Keyword, .text = "if ($1) {$0}" }, + .{ .label = "inline", .kind = .Keyword }, + .{ .label = "noalias", .kind = .Keyword }, + .{ .label = "nosuspend", .kind = .Keyword }, + .{ .label = "noinline", .kind = .Keyword }, + .{ .label = "opaque", .kind = .Keyword }, + .{ .label = "or", .kind = .Keyword }, + .{ .label = "orelse", .kind = .Keyword }, + .{ .label = "packed", .kind = .Keyword }, + .{ .label = "pub", .kind = .Keyword }, + .{ .label = "resume", .kind = .Keyword }, + .{ .label = "return", .kind = .Keyword }, + .{ .label = "linksection", .kind = .Keyword }, + .{ .label = "struct", .kind = .Keyword, .text = "struct {$0}" }, + .{ .label = "suspend", .kind = .Keyword }, + .{ .label = "switch", .kind = .Keyword, .text = "switch ($1) {$0}" }, + .{ .label = "test", .kind = .Keyword, .text = "test \"$1\" {$0}" }, + .{ .label = "threadlocal", .kind = .Keyword }, + .{ .label = "try", .kind = .Keyword }, + .{ .label = "union", .kind = .Keyword }, + .{ .label = "unreachable", .kind = .Keyword }, + .{ .label = "usingnamespace", .kind = .Keyword }, + .{ .label = "var", .kind = .Keyword }, + .{ .label = "volatile", .kind = .Keyword }, + .{ .label = "while", .kind = .Keyword, .text = "while ($1) {$0}" }, + + // keyword snippets + .{ .label = "asmv", .kind = .Snippet, .text = "asm volatile (${1:input}, ${0:input})" }, + .{ .label = "pub fn", .kind = .Snippet, .text = "pub fn ${1:name}($2) ${3:!void} {$0}" }, + .{ .label = "forv", .kind = .Snippet, .text = "for ($1) |${2:value}| {$0}" }, + .{ .label = "fori", .kind = .Snippet, .text = "for ($1) |_, ${2:i}| {$0}" }, + .{ .label = "forvi", .kind = .Snippet, .text = "for ($1) |${2:value},${3:i}| {$0}" }, + .{ .label = "if else", .kind = .Snippet, .text = "if ($1) {$2} else {$0}" }, + .{ .label = "catch switch", .kind = .Snippet, .text = "catch |${1:err}| switch(${1:err}) {$0}" }, +}; From 5fa76563147b746965ef9e51bca87da287014be5 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sun, 25 Sep 2022 03:09:54 +0200 Subject: [PATCH 3/3] update snippets --- src/data/snippets.zig | 24 +++++++++++++++++------- tests/tests.zig | 1 - 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/data/snippets.zig b/src/data/snippets.zig index 1b2d8f9..d04150b 100644 --- a/src/data/snippets.zig +++ b/src/data/snippets.zig @@ -9,12 +9,13 @@ pub const Snipped = struct { pub const top_level_decl_data = [_]Snipped{ .{ .label = "fn", .kind = .Snippet, .text = "fn ${1:name}($2) ${3:!void} {$0}" }, .{ .label = "pub fn", .kind = .Snippet, .text = "pub fn ${1:name}($2) ${3:!void} {$0}" }, - .{ .label = "struct", .kind = .Snippet, .text = "const $1 = struct {$0}" }, - .{ .label = "error set", .kind = .Snippet, .text = "const ${1:Error} = error {$0}" }, - .{ .label = "enum", .kind = .Snippet, .text = "const $1 = enum {$0}" }, - .{ .label = "union", .kind = .Snippet, .text = "const $1 = union {$0}" }, - .{ .label = "union tagged", .kind = .Snippet, .text = "const $1 = union(${2:enum}) {$0}" }, + .{ .label = "struct", .kind = .Snippet, .text = "const $1 = struct {$0};" }, + .{ .label = "error set", .kind = .Snippet, .text = "const ${1:Error} = error {$0};" }, + .{ .label = "enum", .kind = .Snippet, .text = "const $1 = enum {$0};" }, + .{ .label = "union", .kind = .Snippet, .text = "const $1 = union {$0};" }, + .{ .label = "union tagged", .kind = .Snippet, .text = "const $1 = union(${2:enum}) {$0};" }, .{ .label = "test", .kind = .Snippet, .text = "test \"$1\" {$0}" }, + .{ .label = "main", .kind = .Snippet, .text = "pub fn main() !void {$0}" }, }; pub const generic = [_]Snipped{ @@ -55,7 +56,7 @@ pub const generic = [_]Snipped{ .{ .label = "resume", .kind = .Keyword }, .{ .label = "return", .kind = .Keyword }, .{ .label = "linksection", .kind = .Keyword }, - .{ .label = "struct", .kind = .Keyword, .text = "struct {$0}" }, + .{ .label = "struct", .kind = .Keyword, .text = "struct {$0};" }, .{ .label = "suspend", .kind = .Keyword }, .{ .label = "switch", .kind = .Keyword, .text = "switch ($1) {$0}" }, .{ .label = "test", .kind = .Keyword, .text = "test \"$1\" {$0}" }, @@ -75,5 +76,14 @@ pub const generic = [_]Snipped{ .{ .label = "fori", .kind = .Snippet, .text = "for ($1) |_, ${2:i}| {$0}" }, .{ .label = "forvi", .kind = .Snippet, .text = "for ($1) |${2:value},${3:i}| {$0}" }, .{ .label = "if else", .kind = .Snippet, .text = "if ($1) {$2} else {$0}" }, - .{ .label = "catch switch", .kind = .Snippet, .text = "catch |${1:err}| switch(${1:err}) {$0}" }, + .{ .label = "catch switch", .kind = .Snippet, .text = "catch |${1:err}| switch(${1:err}) {$0};" }, + + // snippets + .{ .label = "main", .kind = .Snippet, .text = "pub fn main() !void {$0}" }, + .{ .label = "todo", .kind = .Snippet, .text = "std.debug.todo(\"$0\");" }, + .{ .label = "print", .kind = .Snippet, .text = "std.debug.print(\"$1\", .{$0});" }, + .{ .label = "log err", .kind = .Snippet, .text = "std.log.err(\"$1\", .{$0});" }, + .{ .label = "log warn", .kind = .Snippet, .text = "std.log.warn(\"$1\", .{$0});" }, + .{ .label = "log info", .kind = .Snippet, .text = "std.log.info(\"$1\", .{$0});" }, + .{ .label = "log debug", .kind = .Snippet, .text = "std.log.debug(\"$1\", .{$0});" }, }; diff --git a/tests/tests.zig b/tests/tests.zig index 6848c0a..3f85a87 100644 --- a/tests/tests.zig +++ b/tests/tests.zig @@ -1,6 +1,5 @@ comptime { _ = @import("helper.zig"); - _ = @import("sessions.zig"); _ = @import("utility/offsets.zig"); _ = @import("utility/position_context.zig");