Implement label details support
This commit is contained in:
parent
803f89941b
commit
64f525f95f
222
src/main.zig
222
src/main.zig
@ -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,
|
||||||
@ -1076,6 +1081,7 @@ fn completeLabel(arena: *std.heap.ArenaAllocator, id: types.RequestId, pos_index
|
|||||||
.orig_handle = handle,
|
.orig_handle = handle,
|
||||||
};
|
};
|
||||||
try analysis.iterateLabels(handle, pos_index, declToCompletion, context);
|
try analysis.iterateLabels(handle, pos_index, declToCompletion, context);
|
||||||
|
sortCompletionItems(completions.items, config);
|
||||||
truncateCompletions(completions.items, config.max_detail_length);
|
truncateCompletions(completions.items, config.max_detail_length);
|
||||||
|
|
||||||
try send(arena, types.Response{
|
try send(arena, types.Response{
|
||||||
@ -1148,8 +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);
|
||||||
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 = .{
|
||||||
@ -1175,7 +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);
|
||||||
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{
|
||||||
@ -1189,11 +1208,180 @@ 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();
|
||||||
|
|
||||||
const completions = try document_store.errorCompletionItems(arena, handle);
|
var completions = try document_store.errorCompletionItems(arena, handle);
|
||||||
|
|
||||||
truncateCompletions(completions, config.max_detail_length);
|
truncateCompletions(completions, config.max_detail_length);
|
||||||
logger.debug("Completing error:", .{});
|
logger.debug("Completing error:", .{});
|
||||||
|
|
||||||
@ -1208,11 +1396,38 @@ fn completeError(arena: *std.heap.ArenaAllocator, id: types.RequestId, handle: *
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn kindToSortScore(kind: types.CompletionItem.Kind) [] const u8 {
|
||||||
|
return switch (kind) {
|
||||||
|
.Variable => "2_",
|
||||||
|
.Field => "3_",
|
||||||
|
.Function => "4_",
|
||||||
|
|
||||||
|
.Keyword,
|
||||||
|
.EnumMember => "5_",
|
||||||
|
|
||||||
|
.Class,
|
||||||
|
.Interface,
|
||||||
|
.Struct,
|
||||||
|
// Union?
|
||||||
|
.TypeParameter => "6_",
|
||||||
|
|
||||||
|
else => "9_"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sortCompletionItems(completions: []types.CompletionItem, _: *const Config) void {
|
||||||
|
// TODO: config for sorting rule?
|
||||||
|
for (completions) |*c| {
|
||||||
|
c.sortText = kindToSortScore(c.kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn completeDot(arena: *std.heap.ArenaAllocator, id: types.RequestId, handle: *DocumentStore.Handle, config: *const Config) !void {
|
fn completeDot(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();
|
||||||
|
|
||||||
var completions = try document_store.enumCompletionItems(arena, handle);
|
var completions = try document_store.enumCompletionItems(arena, handle);
|
||||||
|
sortCompletionItems(completions, config);
|
||||||
truncateCompletions(completions, config.max_detail_length);
|
truncateCompletions(completions, config.max_detail_length);
|
||||||
|
|
||||||
try send(arena, types.Response{
|
try send(arena, types.Response{
|
||||||
@ -1300,6 +1515,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)) {
|
||||||
@ -1332,6 +1548,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,
|
||||||
@ -1791,6 +2010,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];
|
||||||
|
Loading…
Reference in New Issue
Block a user