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