add keyword snippets for autocomplete

This commit is contained in:
Techatrix 2022-09-25 02:45:02 +02:00
parent 176bf17d15
commit 00be49c595
2 changed files with 119 additions and 4 deletions

View File

@ -16,9 +16,11 @@ const shared = @import("shared.zig");
const Ast = std.zig.Ast; const Ast = std.zig.Ast;
const tracy = @import("tracy.zig"); const tracy = @import("tracy.zig");
const uri_utils = @import("uri.zig"); const uri_utils = @import("uri.zig");
const data = @import("data/data.zig");
const diff = @import("diff.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); const log = std.log.scoped(.server);
// Server fields // Server fields
@ -1160,6 +1162,30 @@ fn populateBuiltinCompletions(builtin_completions: *std.ArrayListUnmanaged(types
truncateCompletions(builtin_completions.items, config.max_detail_length); 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 { fn completeBuiltin(server: *Server, writer: anytype, id: types.RequestId) !void {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -1192,6 +1218,7 @@ fn completeGlobal(server: *Server, writer: anytype, id: types.RequestId, pos_ind
.orig_handle = handle, .orig_handle = handle,
}; };
try analysis.iterateSymbolsGlobal(&server.document_store, &server.arena, handle, pos_index, declToCompletion, context); 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()); sortCompletionItems(completions.items, server.arena.allocator());
truncateCompletions(completions.items, server.config.max_detail_length); truncateCompletions(completions.items, server.config.max_detail_length);
@ -1437,7 +1464,7 @@ fn kindToSortScore(kind: types.CompletionItem.Kind) []const u8 {
.Field => "3_", .Field => "3_",
.Function => "4_", .Function => "4_",
.Keyword, .EnumMember => "5_", .Keyword, .Snippet, .EnumMember => "5_",
.Class, .Class,
.Interface, .Interface,
@ -1770,8 +1797,17 @@ fn completionHandler(server: *Server, writer: anytype, id: types.RequestId, req:
return try respondGeneric(writer, id, no_completions_response); return try respondGeneric(writer, id, no_completions_response);
}; };
if (req.params.position.character == 0) if (req.params.position.character == 0) {
return try respondGeneric(writer, id, no_completions_response); 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 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); const pos_context = try analysis.getPositionContext(server.arena.allocator(), handle.document, source_index);

79
src/data/snippets.zig Normal file
View File

@ -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}" },
};