diff --git a/src/analysis.zig b/src/analysis.zig index 8e788c624..24829bd2e 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -45,11 +45,16 @@ pub fn invalidate(self: *Analyser) void { _ = self.arena.reset(.free_all); } +pub fn getDocCommentsBeforeToken(allocator: std.mem.Allocator, tree: Ast, base: Ast.TokenIndex) !?[]const u8 { + const tokens = tree.tokens.items(.tag); + const doc_comment_index = getDocCommentTokenIndex(tokens, base) orelse return null; + return try collectDocComments(allocator, tree, doc_comment_index, false); +} + /// Gets a declaration's doc comments. Caller owns returned memory. pub fn getDocComments(allocator: std.mem.Allocator, tree: Ast, node: Ast.Node.Index) !?[]const u8 { const base = tree.nodes.items(.main_token)[node]; const base_kind = tree.nodes.items(.tag)[node]; - const tokens = tree.tokens.items(.tag); switch (base_kind) { // As far as I know, this does not actually happen yet, but it @@ -67,10 +72,7 @@ pub fn getDocComments(allocator: std.mem.Allocator, tree: Ast, node: Ast.Node.In .container_field_init, .container_field_align, .container_field, - => { - if (getDocCommentTokenIndex(tokens, base)) |doc_comment_index| - return try collectDocComments(allocator, tree, doc_comment_index, false); - }, + => return try getDocCommentsBeforeToken(allocator, tree, base), else => {}, } return null; @@ -2044,7 +2046,7 @@ pub const Declaration = union(enum) { block: Ast.Node.Index, }, /// always an identifier - error_token: Ast.Node.Index, + error_token: Ast.TokenIndex, pub const Index = enum(u32) { _ }; diff --git a/src/features/completions.zig b/src/features/completions.zig index 2c1464807..f1f46b07b 100644 --- a/src/features/completions.zig +++ b/src/features/completions.zig @@ -85,6 +85,32 @@ fn typeToCompletion( } } +fn completionDoc( + server: *Server, + either_descriptor: ?[]const u8, + doc_comments: ?[]const u8, +) error{OutOfMemory}!@TypeOf(@as(types.CompletionItem, undefined).documentation) { + var list = std.ArrayList(u8).init(server.arena.allocator()); + const writer = list.writer(); + + if (either_descriptor) |ed| + try writer.print("`Conditionally available: {s}`", .{ed}); + + if (doc_comments) |dc| { + if (either_descriptor != null) + try writer.writeAll("\n\n"); + try writer.writeAll(dc); + } + + if (list.items.len == 0) + return null; + + return .{ .MarkupContent = types.MarkupContent{ + .kind = if (server.client_capabilities.completion_doc_supports_md) .markdown else .plaintext, + .value = list.items, + } }; +} + fn nodeToCompletion( server: *Server, list: *std.ArrayListUnmanaged(types.CompletionItem), @@ -107,27 +133,11 @@ fn nodeToCompletion( const datas = tree.nodes.items(.data); const token_tags = tree.tokens.items(.tag); - const doc_kind: types.MarkupKind = if (server.client_capabilities.completion_doc_supports_md) - .markdown - else - .plaintext; - - const Documentation = @TypeOf(@as(types.CompletionItem, undefined).documentation); - - const doc: Documentation = if (try Analyser.getDocComments( - allocator, - handle.tree, - node, - )) |doc_comments| .{ .MarkupContent = types.MarkupContent{ - .kind = doc_kind, - .value = if (either_descriptor) |ed| - try std.fmt.allocPrint(allocator, "`Conditionally available: {s}`\n\n{s}", .{ ed, doc_comments }) - else - doc_comments, - } } else (if (either_descriptor) |ed| .{ .MarkupContent = types.MarkupContent{ - .kind = doc_kind, - .value = try std.fmt.allocPrint(allocator, "`Conditionally available: {s}`", .{ed}), - } } else null); + const doc = try completionDoc( + server, + either_descriptor, + try Analyser.getDocComments(allocator, handle.tree, node), + ); if (ast.isContainer(handle.tree, node)) { const context = DeclToCompletionContext{ @@ -358,17 +368,15 @@ fn declToCompletion(context: DeclToCompletionContext, decl_handle: Analyser.Decl context.either_descriptor, ), .param_payload => |pay| { - const Documentation = @TypeOf(@as(types.CompletionItem, undefined).documentation); - const param = pay.param; - const doc_kind: types.MarkupKind = if (context.server.client_capabilities.completion_doc_supports_md) .markdown else .plaintext; - const doc: Documentation = if (param.first_doc_comment) |doc_comments| .{ .MarkupContent = types.MarkupContent{ - .kind = doc_kind, - .value = if (context.either_descriptor) |ed| - try std.fmt.allocPrint(allocator, "`Conditionally available: {s}`\n\n{s}", .{ ed, try Analyser.collectDocComments(allocator, tree, doc_comments, false) }) + const doc = try completionDoc( + context.server, + context.either_descriptor, + if (param.first_doc_comment) |doc_comments| + try Analyser.collectDocComments(allocator, tree, doc_comments, false) else - try Analyser.collectDocComments(allocator, tree, doc_comments, false), - } } else null; + null, + ); try context.completions.append(allocator, .{ .label = tree.tokenSlice(param.name_token.?), @@ -395,12 +403,18 @@ fn declToCompletion(context: DeclToCompletionContext, decl_handle: Analyser.Decl .insertTextFormat = .PlainText, }); }, - .error_token => { - const name = tree.tokenSlice(decl_handle.decl.error_token); + .error_token => |token| { + const name = tree.tokenSlice(token); + const doc = try completionDoc( + context.server, + context.either_descriptor, + try Analyser.getDocCommentsBeforeToken(allocator, tree, token), + ); try context.completions.append(allocator, .{ .label = name, .kind = .Constant, + .documentation = doc, .detail = try std.fmt.allocPrint(allocator, "error.{s}", .{name}), .insertText = name, .insertTextFormat = .PlainText, diff --git a/src/features/hover.zig b/src/features/hover.zig index 5c3fba834..1faf1724f 100644 --- a/src/features/hover.zig +++ b/src/features/hover.zig @@ -81,13 +81,16 @@ pub fn hoverSymbol(server: *Server, decl_handle: Analyser.DeclWithHandle, markup break :def ast.paramSlice(tree, param); }, + .error_token => |token| def: { + doc_str = try Analyser.getDocCommentsBeforeToken(server.arena.allocator(), tree, token); + break :def tree.tokenSlice(decl_handle.nameToken()); + }, .pointer_payload, .error_union_payload, .array_payload, .array_index, .switch_payload, .label_decl, - .error_token, => tree.tokenSlice(decl_handle.nameToken()), };