Merge pull request #507 from ryuukk/label_details_support

Label details support
This commit is contained in:
Auguste Rame 2022-07-07 03:15:55 -04:00 committed by GitHub
commit cad9b0aba2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 208 additions and 6 deletions

View File

@ -89,6 +89,7 @@ const ClientCapabilities = struct {
supports_semantic_tokens: bool = false, supports_semantic_tokens: bool = false,
hover_supports_md: bool = false, hover_supports_md: bool = false,
completion_doc_supports_md: bool = false, completion_doc_supports_md: bool = false,
label_details_support: bool = false,
}; };
var client_capabilities = ClientCapabilities{}; var client_capabilities = ClientCapabilities{};
@ -302,6 +303,7 @@ fn typeToCompletion(
if (!type_handle.type.is_type_val) { if (!type_handle.type.is_type_val) {
try list.append(.{ try list.append(.{
.label = "len", .label = "len",
.detail = "const len: usize",
.kind = .Field, .kind = .Field,
.insertText = "len", .insertText = "len",
.insertTextFormat = .PlainText, .insertTextFormat = .PlainText,
@ -482,6 +484,7 @@ fn nodeToCompletion(
=> { => {
try list.append(.{ try list.append(.{
.label = "len", .label = "len",
.detail = "const len: usize",
.kind = .Field, .kind = .Field,
.insertText = "len", .insertText = "len",
.insertTextFormat = .PlainText, .insertTextFormat = .PlainText,
@ -512,6 +515,7 @@ fn nodeToCompletion(
}); });
try list.append(.{ try list.append(.{
.label = "len", .label = "len",
.detail = "const len: usize",
.kind = .Field, .kind = .Field,
.insertText = "len", .insertText = "len",
.insertTextFormat = .PlainText, .insertTextFormat = .PlainText,
@ -539,6 +543,7 @@ fn nodeToCompletion(
.string_literal => { .string_literal => {
try list.append(.{ try list.append(.{
.label = "len", .label = "len",
.detail = "const len: usize",
.kind = .Field, .kind = .Field,
.insertText = "len", .insertText = "len",
.insertTextFormat = .PlainText, .insertTextFormat = .PlainText,
@ -1149,10 +1154,15 @@ fn completeGlobal(arena: *std.heap.ArenaAllocator, id: types.RequestId, pos_inde
.orig_handle = handle, .orig_handle = handle,
}; };
try analysis.iterateSymbolsGlobal(&document_store, arena, handle, pos_index, declToCompletion, context); try analysis.iterateSymbolsGlobal(&document_store, arena, handle, pos_index, declToCompletion, context);
sortCompletionItems(completions.items, config, arena.allocator()); sortCompletionItems(completions.items, config, arena.allocator());
truncateCompletions(completions.items, config.max_detail_length); truncateCompletions(completions.items, config.max_detail_length);
if (client_capabilities.label_details_support) {
for (completions.items) |*item| {
try formatDetailledLabel(item, arena.allocator());
}
}
try send(arena, types.Response{ try send(arena, types.Response{
.id = id, .id = id,
.result = .{ .result = .{
@ -1178,9 +1188,13 @@ fn completeFieldAccess(arena: *std.heap.ArenaAllocator, id: types.RequestId, han
if (try analysis.getFieldAccessType(&document_store, arena, handle, position.absolute_index, &tokenizer)) |result| { if (try analysis.getFieldAccessType(&document_store, arena, handle, position.absolute_index, &tokenizer)) |result| {
held_range.release(); held_range.release();
try typeToCompletion(arena, &completions, result, handle, config); try typeToCompletion(arena, &completions, result, handle, config);
sortCompletionItems(completions.items, config, arena.allocator()); sortCompletionItems(completions.items, config, arena.allocator());
truncateCompletions(completions.items, config.max_detail_length); truncateCompletions(completions.items, config.max_detail_length);
if (client_capabilities.label_details_support) {
for (completions.items) |*item| {
try formatDetailledLabel(item, arena.allocator());
}
}
} }
try send(arena, types.Response{ try send(arena, types.Response{
@ -1194,6 +1208,174 @@ fn completeFieldAccess(arena: *std.heap.ArenaAllocator, id: types.RequestId, han
}); });
} }
fn formatDetailledLabel(item: *types.CompletionItem, alloc: std.mem.Allocator) !void {
// NOTE: this is not ideal, we should build a detailled label like we do for label/detail
// because this implementation is very loose, nothing is formated properly so we need to clean
// things a little bit, wich is quite messy
// but it works, it provide decent results
if (item.detail == null)
return;
var detailLen: usize = item.detail.?.len;
var it: []u8 = try alloc.alloc(u8, detailLen);
detailLen -= std.mem.replace(u8, item.detail.?, " ", " ", it) * 3;
it = it[0..detailLen];
// HACK: for enums 'MyEnum.', item.detail shows everything, we don't want that
const isValue = std.mem.startsWith(u8, item.label, it);
const isVar = std.mem.startsWith(u8, it, "var ");
const isConst = std.mem.startsWith(u8, it, "const ");
// we don't want the entire content of things, see the NOTE above
if (std.mem.indexOf(u8, it, "{")) |end| {
it = it[0..end];
}
if (std.mem.indexOf(u8, it, "}")) |end| {
it = it[0..end];
}
if (std.mem.indexOf(u8, it, ";")) |end| {
it = it[0..end];
}
// logger.info("## label: {s} it: {s} kind: {} isValue: {}", .{item.label, it, item.kind, isValue});
if (std.mem.startsWith(u8, it, "fn ")) {
var s: usize = std.mem.indexOf(u8, it, "(") orelse return;
var e: usize = std.mem.lastIndexOf(u8, it, ")") orelse return;
if (e < s) {
logger.warn("something wrong when trying to build label detail for {s} kind: {s}", .{ it, item.kind });
return;
}
item.insertText = item.label;
item.insertTextFormat = .PlainText;
item.detail = item.label;
item.labelDetails = .{ .detail = it[s .. e + 1], .description = it[e + 1 ..] };
if (item.kind == .Constant) {
if (std.mem.indexOf(u8, it, "= struct")) |_| {
item.labelDetails.?.description = "struct";
} else if (std.mem.indexOf(u8, it, "= union")) |_| {
var us: usize = std.mem.indexOf(u8, it, "(") orelse return;
var ue: usize = std.mem.lastIndexOf(u8, it, ")") orelse return;
if (ue < us) {
logger.warn("something wrong when trying to build label detail for a .Constant|union {s}", .{it});
return;
}
item.labelDetails.?.description = it[us - 5 .. ue + 1];
}
}
} else if ((item.kind == .Variable or item.kind == .Constant) and (isVar or isConst)) {
item.insertText = item.label;
item.insertTextFormat = .PlainText;
item.detail = item.label;
const eqlPos = std.mem.indexOf(u8, it, "=");
if (std.mem.indexOf(u8, it, ":")) |start| {
if (eqlPos != null) {
if (start > eqlPos.?) return;
}
var e: usize = eqlPos orelse it.len;
item.labelDetails = .{
.detail = "", // left
.description = it[start + 1 .. e], // right
};
} else if (std.mem.indexOf(u8, it, "= .")) |start| {
item.labelDetails = .{
.detail = "", // left
.description = it[start + 2 .. it.len], // right
};
} else if (eqlPos) |start| {
item.labelDetails = .{
.detail = "", // left
.description = it[start + 2 .. it.len], // right
};
}
} else if (item.kind == .Variable) {
var s: usize = std.mem.indexOf(u8, it, ":") orelse return;
var e: usize = std.mem.indexOf(u8, it, "=") orelse return;
if (e < s) {
logger.warn("something wrong when trying to build label detail for a .Variable {s}", .{it});
return;
}
// logger.info("s: {} -> {}", .{s, e});
item.insertText = item.label;
item.insertTextFormat = .PlainText;
item.detail = item.label;
item.labelDetails = .{
.detail = "", // left
.description = it[s + 1 .. e], // right
};
} else if (std.mem.indexOf(u8, it, "@import") != null) {
item.insertText = item.label;
item.insertTextFormat = .PlainText;
item.detail = item.label;
item.labelDetails = .{
.detail = "", // left
.description = it, // right
};
} else if (item.kind == .Constant or item.kind == .Field) {
var s: usize = std.mem.indexOf(u8, it, " ") orelse return;
var e: usize = std.mem.indexOf(u8, it, "=") orelse it.len;
if (e < s) {
logger.warn("something wrong when trying to build label detail for a .Variable {s}", .{it});
return;
}
// logger.info("s: {} -> {}", .{s, e});
item.insertText = item.label;
item.insertTextFormat = .PlainText;
item.detail = item.label;
item.labelDetails = .{
.detail = "", // left
.description = it[s + 1 .. e], // right
};
if (std.mem.indexOf(u8, it, "= union(")) |_| {
var us: usize = std.mem.indexOf(u8, it, "(") orelse return;
var ue: usize = std.mem.lastIndexOf(u8, it, ")") orelse return;
if (ue < us) {
logger.warn("something wrong when trying to build label detail for a .Constant|union {s}", .{it});
return;
}
item.labelDetails.?.description = it[us - 5 .. ue + 1];
} else if (std.mem.indexOf(u8, it, "= enum(")) |_| {
var es: usize = std.mem.indexOf(u8, it, "(") orelse return;
var ee: usize = std.mem.lastIndexOf(u8, it, ")") orelse return;
if (ee < es) {
logger.warn("something wrong when trying to build label detail for a .Constant|enum {s}", .{it});
return;
}
item.labelDetails.?.description = it[es - 4 .. ee + 1];
} else if (std.mem.indexOf(u8, it, "= struct")) |_| {
item.labelDetails.?.description = "struct";
} else if (std.mem.indexOf(u8, it, "= union")) |_| {
item.labelDetails.?.description = "union";
} else if (std.mem.indexOf(u8, it, "= enum")) |_| {
item.labelDetails.?.description = "enum";
}
} else if (item.kind == .Field and isValue) {
item.insertText = item.label;
item.insertTextFormat = .PlainText;
item.detail = item.label;
item.labelDetails = .{
.detail = "", // left
.description = item.label, // right
};
} else {
// TODO: if something is missing, it neecs to be implemented here
}
// if (item.labelDetails != null)
// logger.info("labelDetails: {s} :: {s}", .{item.labelDetails.?.detail, item.labelDetails.?.description});
}
fn completeError(arena: *std.heap.ArenaAllocator, id: types.RequestId, handle: *DocumentStore.Handle, config: *const Config) !void { fn completeError(arena: *std.heap.ArenaAllocator, id: types.RequestId, handle: *DocumentStore.Handle, config: *const Config) !void {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -1342,6 +1524,7 @@ fn initializeHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req:
} }
if (textDocument.completion) |completion| { if (textDocument.completion) |completion| {
if (completion.completionItem) |completionItem| { if (completion.completionItem) |completionItem| {
client_capabilities.label_details_support = completionItem.labelDetailsSupport.value;
client_capabilities.supports_snippets = completionItem.snippetSupport.value; client_capabilities.supports_snippets = completionItem.snippetSupport.value;
for (completionItem.documentationFormat.value) |documentationFormat| { for (completionItem.documentationFormat.value) |documentationFormat| {
if (std.mem.eql(u8, "markdown", documentationFormat)) { if (std.mem.eql(u8, "markdown", documentationFormat)) {
@ -1374,6 +1557,9 @@ fn initializeHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req:
.completionProvider = .{ .completionProvider = .{
.resolveProvider = false, .resolveProvider = false,
.triggerCharacters = &[_][]const u8{ ".", ":", "@" }, .triggerCharacters = &[_][]const u8{ ".", ":", "@" },
.completionItem = .{
.labelDetailsSupport = true
}
}, },
.documentHighlightProvider = false, .documentHighlightProvider = false,
.hoverProvider = true, .hoverProvider = true,
@ -1833,6 +2019,7 @@ fn processJsonRpc(arena: *std.heap.ArenaAllocator, parser: *std.json.Parser, jso
inline for (method_map) |method_info| { inline for (method_map) |method_info| {
if (done == null and std.mem.eql(u8, method, method_info[0])) { if (done == null and std.mem.eql(u8, method, method_info[0])) {
if (method_info.len == 1) { if (method_info.len == 1) {
logger.warn("method not mapped: {s}", .{method});
done = error.HackDone; done = error.HackDone;
} else if (method_info[1] != void) { } else if (method_info[1] != void) {
const ReqT = method_info[1]; const ReqT = method_info[1];

View File

@ -158,6 +158,7 @@ pub const Initialize = struct {
completion: ?struct { completion: ?struct {
completionItem: ?struct { completionItem: ?struct {
snippetSupport: Default(bool, false), snippetSupport: Default(bool, false),
labelDetailsSupport: Default(bool, true),
documentationFormat: MaybeStringArray, documentationFormat: MaybeStringArray,
}, },
}, },

View File

@ -241,13 +241,26 @@ pub const CompletionItem = struct {
}; };
label: string, label: string,
labelDetails: ?CompletionItemLabelDetails = null,
kind: Kind, kind: Kind,
textEdit: ?TextEdit = null,
filterText: ?string = null,
insertText: string = "",
insertTextFormat: ?InsertTextFormat = .PlainText,
detail: ?string = null, detail: ?string = null,
sortText: ?string = null,
filterText: ?string = null,
insertText: ?string = null,
insertTextFormat: ?InsertTextFormat = .PlainText,
documentation: ?MarkupContent = null, documentation: ?MarkupContent = null,
// FIXME: i commented this out, because otherwise the vscode client complains about *ranges*
// and breaks code completion entirely
// see: https://github.com/zigtools/zls-vscode/pull/33
// textEdit: ?TextEdit = null,
};
pub const CompletionItemLabelDetails = struct {
detail: ?string,
description: ?string,
sortText: ?string = null, sortText: ?string = null,
}; };
@ -339,6 +352,7 @@ const InitializeResult = struct {
completionProvider: struct { completionProvider: struct {
resolveProvider: bool, resolveProvider: bool,
triggerCharacters: []const string, triggerCharacters: []const string,
completionItem: struct { labelDetailsSupport: bool },
}, },
documentHighlightProvider: bool, documentHighlightProvider: bool,
hoverProvider: bool, hoverProvider: bool,