From f7eff6632e3e9a9dd55b498917c73bdf26ea4f78 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Fri, 6 Nov 2020 10:08:20 +0200 Subject: [PATCH] Cleaned up LSP types, add InitializeResult, no longer use a hardcoded string --- src/analysis.zig | 4 +- src/main.zig | 109 ++++++++--- src/references.zig | 8 +- src/requests.zig | 10 +- src/semantic_tokens.zig | 15 +- src/types.zig | 391 +++++++++++++++++++++------------------- 6 files changed, 307 insertions(+), 230 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index af18337..43d62e1 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -29,7 +29,7 @@ pub fn getDocComments( allocator: *std.mem.Allocator, tree: *ast.Tree, node: *ast.Node, - format: types.MarkupKind, + format: types.MarkupContent.Kind, ) !?[]const u8 { if (getDocCommentNode(tree, node)) |doc_comment_node| { return try collectDocComments(allocator, tree, doc_comment_node, format); @@ -41,7 +41,7 @@ pub fn collectDocComments( allocator: *std.mem.Allocator, tree: *ast.Tree, doc_comments: *ast.Node.DocComment, - format: types.MarkupKind, + format: types.MarkupContent.Kind, ) ![]const u8 { var lines = std.ArrayList([]const u8).init(allocator); defer lines.deinit(); diff --git a/src/main.zig b/src/main.zig index c3b7317..8d78519 100644 --- a/src/main.zig +++ b/src/main.zig @@ -12,6 +12,7 @@ const URI = @import("uri.zig"); const references = @import("references.zig"); const rename = @import("rename.zig"); const offsets = @import("offsets.zig"); +const semantic_tokens = @import("semantic_tokens.zig"); const logger = std.log.scoped(.main); @@ -50,8 +51,8 @@ pub fn log( }; send(&arena, types.Notification{ .method = "window/showMessage", - .params = types.NotificationParams{ - .ShowMessageParams = .{ + .params = types.Notification.Params{ + .ShowMessage = .{ .type = message_type, .message = message, }, @@ -67,8 +68,8 @@ pub fn log( send(&arena, types.Notification{ .method = "window/logMessage", - .params = types.NotificationParams{ - .LogMessageParams = .{ + .params = types.Notification.Params{ + .LogMessage = .{ .type = message_type, .message = message, }, @@ -97,12 +98,6 @@ const ClientCapabilities = struct { var client_capabilities = ClientCapabilities{}; var offset_encoding = offsets.Encoding.utf16; -const initialize_capabilities = - \\"capabilities": {"signatureHelpProvider": {"triggerCharacters": ["(",","]},"textDocumentSync": 1,"renameProvider":true,"completionProvider": {"resolveProvider": false,"triggerCharacters": [".",":","@"]},"documentHighlightProvider": false,"hoverProvider": true,"codeActionProvider": false,"declarationProvider": true,"definitionProvider": true,"typeDefinitionProvider": true,"implementationProvider": false,"referencesProvider": true,"documentSymbolProvider": true,"colorProvider": false,"documentFormattingProvider": true,"documentRangeFormattingProvider": false,"foldingRangeProvider": false,"selectionRangeProvider": false,"workspaceSymbolProvider": false,"rangeProvider": false,"documentProvider": true,"workspace": {"workspaceFolders": {"supported": true,"changeNotifications": true}},"semanticTokensProvider": {"documentProvider": true,"legend": {"tokenTypes": ["namespace","type","struct","enum","union","opaque","parameter","variable","tagField","field","errorTag","function","keyword","comment","string","number","operator","builtin","label","keywordLiteral"],"tokenModifiers": ["definition","async","documentation", "generic"]}}}}} -; - -const initialize_response = ",\"result\": {" ++ initialize_capabilities; - const not_implemented_response = \\,"error":{"code":-32601,"message":"NotImplemented"}} ; @@ -259,7 +254,7 @@ fn publishDiagnostics(arena: *std.heap.ArenaAllocator, handle: DocumentStore.Han try send(arena, types.Notification{ .method = "textDocument/publishDiagnostics", .params = .{ - .PublishDiagnosticsParams = .{ + .PublishDiagnostics = .{ .uri = handle.uri(), .diagnostics = diagnostics.items, }, @@ -331,7 +326,11 @@ fn nodeToCompletion( const node = node_handle.node; const handle = node_handle.handle; - const doc_kind: types.MarkupKind = if (client_capabilities.completion_doc_supports_md) .Markdown else .PlainText; + const doc_kind: types.MarkupContent.Kind = if (client_capabilities.completion_doc_supports_md) + .Markdown + else + .PlainText; + const doc = if (try analysis.getDocComments( list.allocator, handle.tree, @@ -563,7 +562,7 @@ fn gotoDefinitionSymbol(id: types.RequestId, arena: *std.heap.ArenaAllocator, de fn hoverSymbol(id: types.RequestId, arena: *std.heap.ArenaAllocator, decl_handle: analysis.DeclWithHandle) (std.os.WriteError || error{OutOfMemory})!void { const handle = decl_handle.handle; - const hover_kind: types.MarkupKind = if (client_capabilities.hover_supports_md) .Markdown else .PlainText; + const hover_kind: types.MarkupContent.Kind = if (client_capabilities.hover_supports_md) .Markdown else .PlainText; const md_string = switch (decl_handle.decl.*) { .ast_node => |node| ast_node: { if (try analysis.resolveVarDeclAlias(&document_store, arena, .{ .node = node, .handle = handle })) |result| { @@ -866,7 +865,7 @@ fn declToCompletion(context: DeclToCompletionContext, decl_handle: analysis.Decl switch (decl_handle.decl.*) { .ast_node => |node| try nodeToCompletion(context.arena, context.completions, .{ .node = node, .handle = decl_handle.handle }, null, context.orig_handle, false, context.config.*), .param_decl => |param| { - const doc_kind: types.MarkupKind = if (client_capabilities.completion_doc_supports_md) .Markdown else .PlainText; + const doc_kind: types.MarkupContent.Kind = if (client_capabilities.completion_doc_supports_md) .Markdown else .PlainText; const doc = if (param.doc_comments) |doc_comments| types.MarkupContent{ .kind = doc_kind, @@ -1073,7 +1072,6 @@ fn configFromUriOr(uri: []const u8, default: Config) Config { } fn initializeHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: requests.Initialize, config: Config) !void { - var send_encoding = req.params.capabilities.offsetEncoding.value.len != 0; for (req.params.capabilities.offsetEncoding.value) |encoding| { if (std.mem.eql(u8, encoding, "utf-8")) { offset_encoding = .utf8; @@ -1117,15 +1115,77 @@ fn initializeHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: try loadWorkspaceConfigs(); } - if (!send_encoding) { - try respondGeneric(id, initialize_response); - } else { - const response_str = try std.fmt.allocPrint(&arena.allocator, ",\"result\": {{\"offsetEncoding\":\"{}\",{}", .{ - if (offset_encoding == .utf8) @as([]const u8, "utf-8") else @as([]const u8, "utf-16"), - initialize_capabilities, - }); - try respondGeneric(id, response_str); - } + try send(arena, types.Response{ + .id = id, + .result = .{ + .InitializeResult = .{ + .offsetEncoding = if (offset_encoding == .utf8) + @as([]const u8, "utf-8") + else + "utf-16", + .serverInfo = .{ + .name = "zls", + .version = "0.1.0", + }, + .capabilities = .{ + .signatureHelpProvider = .{ + .triggerCharacters = &[_][]const u8{ "(", "," }, + }, + .textDocumentSync = .Full, + .renameProvider = true, + .completionProvider = .{ + .resolveProvider = false, + .triggerCharacters = &[_][]const u8{ ".", ":", "@" }, + }, + .documentHighlightProvider = false, + .hoverProvider = true, + .codeActionProvider = false, + .declarationProvider = true, + .definitionProvider = true, + .typeDefinitionProvider = true, + .implementationProvider = false, + .referencesProvider = true, + .documentSymbolProvider = true, + .colorProvider = false, + .documentFormattingProvider = true, + .documentRangeFormattingProvider = false, + .foldingRangeProvider = false, + .selectionRangeProvider = false, + .workspaceSymbolProvider = false, + .rangeProvider = false, + .documentProvider = true, + .workspace = .{ + .workspaceFolders = .{ + .supported = true, + .changeNotifications = true, + }, + }, + .semanticTokensProvider = .{ + .documentProvider = true, + .legend = .{ + .tokenTypes = comptime block: { + const tokTypeFields = std.meta.fields(semantic_tokens.TokenType); + var names: [tokTypeFields.len][]const u8 = undefined; + for (tokTypeFields) |field, i| { + names[i] = field.name; + } + break :block &names; + }, + .tokenModifiers = comptime block: { + const tokModFields = std.meta.fields(semantic_tokens.TokenModifiers); + var names: [tokModFields.len][]const u8 = undefined; + for (tokModFields) |field, i| { + names[i] = field.name; + } + break :block &names; + }, + }, + }, + }, + }, + }, + }); + logger.notice("zls initialized", .{}); logger.info("{}\n", .{client_capabilities}); logger.notice("Using offset encoding: {}\n", .{std.meta.tagName(offset_encoding)}); @@ -1201,7 +1261,6 @@ fn semanticTokensFullHandler(arena: *std.heap.ArenaAllocator, id: types.RequestI return try respondGeneric(id, no_semantic_tokens_response); }; - const semantic_tokens = @import("semantic_tokens.zig"); const token_array = try semantic_tokens.writeAllSemanticTokens(arena, &document_store, handle, offset_encoding); defer allocator.free(token_array); diff --git a/src/references.zig b/src/references.zig index 2ef68f5..c2dd82c 100644 --- a/src/references.zig +++ b/src/references.zig @@ -19,12 +19,12 @@ fn tokenReference( .uri = handle.uri(), .range = .{ .start = .{ - .line = @intCast(types.Integer, loc.line), - .character = @intCast(types.Integer, loc.column), + .line = @intCast(i64, loc.line), + .character = @intCast(i64, loc.column), }, .end = .{ - .line = @intCast(types.Integer, loc.line), - .character = @intCast(types.Integer, loc.column + offsets.tokenLength(handle.tree, tok, encoding)), + .line = @intCast(i64, loc.line), + .character = @intCast(i64, loc.column + offsets.tokenLength(handle.tree, tok, encoding)), }, }, }); diff --git a/src/requests.zig b/src/requests.zig index 6984667..47e83fe 100644 --- a/src/requests.zig +++ b/src/requests.zig @@ -121,7 +121,7 @@ pub fn fromDynamicTree(arena: *std.heap.ArenaAllocator, comptime T: type, value: //! Note that the parameter types may be incomplete. //! We only define what we actually use. -const MaybeStringArray = Default([]const types.String, &[0]types.String{}); +const MaybeStringArray = Default([]const []const u8, &[0][]const u8{}); pub const Initialize = struct { pub const ClientCapabilities = struct { @@ -161,14 +161,14 @@ pub const WorkspaceFoldersChange = struct { pub const OpenDocument = struct { params: struct { textDocument: struct { - uri: types.String, - text: types.String, + uri: []const u8, + text: []const u8, }, }, }; const TextDocumentIdentifier = struct { - uri: types.String, + uri: []const u8, }; pub const ChangeDocument = struct { @@ -205,7 +205,7 @@ pub const Rename = struct { params: struct { textDocument: TextDocumentIdentifier, position: types.Position, - newName: types.String, + newName: []const u8, }, }; diff --git a/src/semantic_tokens.zig b/src/semantic_tokens.zig index 9486efd..c8527ea 100644 --- a/src/semantic_tokens.zig +++ b/src/semantic_tokens.zig @@ -4,7 +4,7 @@ const DocumentStore = @import("document_store.zig"); const analysis = @import("analysis.zig"); const ast = std.zig.ast; -const TokenType = enum(u32) { +pub const TokenType = enum(u32) { namespace, type, @"struct", @@ -27,7 +27,7 @@ const TokenType = enum(u32) { keywordLiteral, }; -const TokenModifiers = packed struct { +pub const TokenModifiers = packed struct { definition: bool = false, @"async": bool = false, documentation: bool = false, @@ -275,7 +275,12 @@ fn writeNodeTokens(builder: *Builder, arena: *std.heap.ArenaAllocator, store: *D try await @asyncCall(child_frame, {}, writeNodeTokens, .{ builder, arena, store, child }); } } - try gap_highlighter.end(node.lastToken()); + + if (node.tag == .Root) { + try gap_highlighter.end(handle.tree.token_ids.len - 1); + } else { + try gap_highlighter.end(node.lastToken()); + } }, .VarDecl => { const var_decl = node.cast(ast.Node.VarDecl).?; @@ -734,11 +739,11 @@ fn writeNodeTokens(builder: *Builder, arena: *std.heap.ArenaAllocator, store: *D const pointer_type = node.castTag(.PtrType).?; const tok_ids = builder.handle.tree.token_ids; - const ptr_info = switch(tok_ids[pointer_type.op_token]) { + const ptr_info = switch (tok_ids[pointer_type.op_token]) { .AsteriskAsterisk => pointer_type.rhs.castTag(.PtrType).?.ptr_info, else => pointer_type.ptr_info, }; - const rhs = switch(tok_ids[pointer_type.op_token]) { + const rhs = switch (tok_ids[pointer_type.op_token]) { .AsteriskAsterisk => pointer_type.rhs.castTag(.PtrType).?.rhs, else => pointer_type.rhs, }; diff --git a/src/types.zig b/src/types.zig index 68264f6..c031c2b 100644 --- a/src/types.zig +++ b/src/types.zig @@ -1,25 +1,10 @@ -// Collection of JSONRPC and LSP structs, enums, and unions - const std = @import("std"); +// LSP types const json = std.json; -// JSON Types - -pub const String = []const u8; -pub const Integer = i64; -pub const Float = f64; -pub const Bool = bool; -pub const Array = json.Array; -pub const Object = json.ObjectMap; -// pub const Any = @TypeOf(var); - -// Basic structures - -pub const DocumentUri = String; - pub const Position = struct { - line: Integer, - character: Integer, + line: i64, + character: i64, }; pub const Range = struct { @@ -28,21 +13,15 @@ pub const Range = struct { }; pub const Location = struct { - uri: DocumentUri, range: Range + uri: []const u8, + range: Range, }; /// Id of a request pub const RequestId = union(enum) { - String: String, - Integer: Integer, - Float: Float, -}; - -/// Params of a request -pub const RequestParams = void; - -pub const NotificationParams = union(enum) { - LogMessageParams: LogMessageParams, PublishDiagnosticsParams: PublishDiagnosticsParams, ShowMessageParams: ShowMessageParams + String: []const u8, + Integer: i64, + Float: f64, }; /// Hover response @@ -60,35 +39,40 @@ pub const ResponseParams = union(enum) { TextEdits: []TextEdit, Locations: []Location, WorkspaceEdit: WorkspaceEdit, -}; - -/// JSONRPC error -pub const Error = struct { - code: Integer, - message: String, - data: String, -}; - -/// JSONRPC request -pub const Request = struct { - jsonrpc: String = "2.0", method: String, id: ?RequestId = RequestId{ .Integer = 0 }, params: RequestParams + InitializeResult: InitializeResult, }; /// JSONRPC notifications pub const Notification = struct { - jsonrpc: String = "2.0", method: String, params: NotificationParams + pub const Params = union(enum) { + LogMessage: struct { + type: MessageType, + message: []const u8, + }, + PublishDiagnostics: struct { + uri: []const u8, + diagnostics: []Diagnostic, + }, + ShowMessage: struct { + type: MessageType, + message: []const u8, + }, + }; + + jsonrpc: []const u8 = "2.0", + method: []const u8, + params: Params, }; /// JSONRPC response pub const Response = struct { - jsonrpc: String = "2.0", - // @"error": ?Error = null, + jsonrpc: []const u8 = "2.0", id: RequestId, result: ResponseParams, }; /// Type of a debug message -pub const MessageType = enum(Integer) { +pub const MessageType = enum(i64) { Error = 1, Warning = 2, Info = 3, @@ -103,12 +87,7 @@ pub const MessageType = enum(Integer) { } }; -/// Params for a LogMessage Notification (window/logMessage) -pub const LogMessageParams = struct { - type: MessageType, message: String -}; - -pub const DiagnosticSeverity = enum(Integer) { +pub const DiagnosticSeverity = enum(i64) { Error = 1, Warning = 2, Information = 3, @@ -126,19 +105,15 @@ pub const DiagnosticSeverity = enum(Integer) { pub const Diagnostic = struct { range: Range, severity: DiagnosticSeverity, - code: String, - source: String, - message: String, -}; - -pub const PublishDiagnosticsParams = struct { - uri: DocumentUri, diagnostics: []Diagnostic + code: []const u8, + source: []const u8, + message: []const u8, }; pub const TextDocument = struct { - uri: DocumentUri, + uri: []const u8, // This is a substring of mem starting at 0 - text: String, + text: []const u8, // This holds the memory that we have actually allocated. mem: []u8, }; @@ -172,91 +147,37 @@ pub const WorkspaceEdit = struct { pub const TextEdit = struct { range: Range, - newText: String, -}; - -pub const MarkupKind = enum(u1) { - PlainText = 0, // plaintext - Markdown = 1, // markdown - - pub fn jsonStringify( - value: MarkupKind, - options: json.StringifyOptions, - out_stream: anytype, - ) !void { - const str = switch (value) { - .PlainText => "plaintext", - .Markdown => "markdown", - }; - try json.stringify(str, options, out_stream); - } + newText: []const u8, }; pub const MarkupContent = struct { - kind: MarkupKind = MarkupKind.Markdown, - value: String, + pub const Kind = enum(u1) { + PlainText = 0, + Markdown = 1, + + pub fn jsonStringify( + value: Kind, + options: json.StringifyOptions, + out_stream: anytype, + ) !void { + const str = switch (value) { + .PlainText => "plaintext", + .Markdown => "markdown", + }; + try json.stringify(str, options, out_stream); + } + }; + + kind: Kind = .Markdown, + value: []const u8, }; -// pub const TextDocumentIdentifier = struct { -// uri: DocumentUri, -// }; - -// pub const CompletionTriggerKind = enum(Integer) { -// Invoked = 1, -// TriggerCharacter = 2, -// TriggerForIncompleteCompletions = 3, - -// pub fn jsonStringify( -// value: CompletionTriggerKind, -// options: json.StringifyOptions, -// out_stream: var, -// ) !void { -// try json.stringify(@enumToInt(value), options, out_stream); -// } -// }; - pub const CompletionList = struct { - isIncomplete: Bool, + isIncomplete: bool, items: []const CompletionItem, }; -pub const CompletionItemKind = enum(Integer) { - Text = 1, - Method = 2, - Function = 3, - Constructor = 4, - Field = 5, - Variable = 6, - Class = 7, - Interface = 8, - Module = 9, - Property = 10, - Unit = 11, - Value = 12, - Enum = 13, - Keyword = 14, - Snippet = 15, - Color = 16, - File = 17, - Reference = 18, - Folder = 19, - EnumMember = 20, - Constant = 21, - Struct = 22, - Event = 23, - Operator = 24, - TypeParameter = 25, - - pub fn jsonStringify( - value: CompletionItemKind, - options: json.StringifyOptions, - out_stream: anytype, - ) !void { - try json.stringify(@enumToInt(value), options, out_stream); - } -}; - -pub const InsertTextFormat = enum(Integer) { +pub const InsertTextFormat = enum(i64) { PlainText = 1, Snippet = 2, @@ -270,70 +191,162 @@ pub const InsertTextFormat = enum(Integer) { }; pub const CompletionItem = struct { - label: String, - kind: CompletionItemKind, + const Kind = enum(i64) { + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18, + Folder = 19, + EnumMember = 20, + Constant = 21, + Struct = 22, + Event = 23, + Operator = 24, + TypeParameter = 25, + + pub fn jsonStringify( + value: Kind, + options: json.StringifyOptions, + out_stream: anytype, + ) !void { + try json.stringify(@enumToInt(value), options, out_stream); + } + }; + + label: []const u8, + kind: Kind, textEdit: ?TextEdit = null, - filterText: ?String = null, - insertText: ?String = null, - insertTextFormat: ?InsertTextFormat = InsertTextFormat.PlainText, - detail: ?String = null, + filterText: ?[]const u8 = null, + insertText: ?[]const u8 = null, + insertTextFormat: ?InsertTextFormat = .PlainText, + detail: ?[]const u8 = null, documentation: ?MarkupContent = null, - // filterText: String = .NotDefined, -}; - -const SymbolKind = enum { - File = 1, - Module = 2, - Namespace = 3, - Package = 4, - Class = 5, - Method = 6, - Property = 7, - Field = 8, - Constructor = 9, - Enum = 10, - Interface = 11, - Function = 12, - Variable = 13, - Constant = 14, - String = 15, - Number = 16, - Boolean = 17, - Array = 18, - Object = 19, - Key = 20, - Null = 21, - EnumMember = 22, - Struct = 23, - Event = 24, - Operator = 25, - TypeParameter = 26, - - pub fn jsonStringify( - value: SymbolKind, - options: json.StringifyOptions, - out_stream: anytype, - ) !void { - try json.stringify(@enumToInt(value), options, out_stream); - } }; pub const DocumentSymbol = struct { - name: String, - detail: ?String = null, - kind: SymbolKind, + const Kind = enum { + File = 1, + Module = 2, + Namespace = 3, + Package = 4, + Class = 5, + Method = 6, + Property = 7, + Field = 8, + Constructor = 9, + Enum = 10, + Interface = 11, + Function = 12, + Variable = 13, + Constant = 14, + String = 15, + Number = 16, + Boolean = 17, + Array = 18, + Object = 19, + Key = 20, + Null = 21, + EnumMember = 22, + Struct = 23, + Event = 24, + Operator = 25, + TypeParameter = 26, + + pub fn jsonStringify( + value: Kind, + options: json.StringifyOptions, + out_stream: anytype, + ) !void { + try json.stringify(@enumToInt(value), options, out_stream); + } + }; + + name: []const u8, + detail: ?[]const u8 = null, + kind: Kind, deprecated: bool = false, range: Range, selectionRange: Range, children: []const DocumentSymbol = &[_]DocumentSymbol{}, }; -pub const ShowMessageParams = struct { - type: MessageType, - message: String, +pub const WorkspaceFolder = struct { + uri: []const u8, + name: []const u8, }; -pub const WorkspaceFolder = struct { - uri: DocumentUri, - name: String, +// Only includes options we set in our initialize result. +const InitializeResult = struct { + capabilities: struct { + signatureHelpProvider: struct { + triggerCharacters: []const []const u8, + }, + textDocumentSync: enum { + None = 0, + Full = 1, + Incremental = 2, + + pub fn jsonStringify( + value: @This(), + options: json.StringifyOptions, + out_stream: anytype, + ) !void { + try json.stringify(@enumToInt(value), options, out_stream); + } + }, + renameProvider: bool, + completionProvider: struct { + resolveProvider: bool, + triggerCharacters: []const []const u8, + }, + documentHighlightProvider: bool, + hoverProvider: bool, + codeActionProvider: bool, + declarationProvider: bool, + definitionProvider: bool, + typeDefinitionProvider: bool, + implementationProvider: bool, + referencesProvider: bool, + documentSymbolProvider: bool, + colorProvider: bool, + documentFormattingProvider: bool, + documentRangeFormattingProvider: bool, + foldingRangeProvider: bool, + selectionRangeProvider: bool, + workspaceSymbolProvider: bool, + rangeProvider: bool, + documentProvider: bool, + workspace: struct { + workspaceFolders: struct { + supported: bool, + changeNotifications: bool, + }, + }, + semanticTokensProvider: struct { + documentProvider: bool, + legend: struct { + tokenTypes: []const []const u8, + tokenModifiers: []const []const u8, + }, + }, + }, + serverInfo: struct { + name: []const u8, + version: ?[]const u8 = null, + }, + offsetEncoding: []const u8, };