Add option to truncate long completions

The detail entries for big structs such as std.zig.CrossTarget were
bricking the preview window in Sublime Text.
This commit is contained in:
Jonathan Hähne 2021-03-27 20:37:51 +01:00
parent 3e300e4d74
commit b33e26612a
3 changed files with 71 additions and 19 deletions

View File

@ -74,6 +74,12 @@ pub fn config(step: *std.build.Step) anyerror!void {
else => else =>
try zinput.askBool("Should the @ sign be included in completions of builtin functions?\nChange this later if `@inc` completes to `include` or `@@include`") try zinput.askBool("Should the @ sign be included in completions of builtin functions?\nChange this later if `@inc` completes to `include` or `@@include`")
}; };
const max_detail_length: usize = switch (editor) {
.Sublime =>
256,
else =>
1024 * 1024
};
var dir = try std.fs.cwd().openDir(builder.exe_dir, .{}); var dir = try std.fs.cwd().openDir(builder.exe_dir, .{});
defer dir.close(); defer dir.close();
@ -92,6 +98,7 @@ pub fn config(step: *std.build.Step) anyerror!void {
.enable_semantic_tokens = semantic_tokens, .enable_semantic_tokens = semantic_tokens,
.operator_completions = operator_completions, .operator_completions = operator_completions,
.include_at_in_builtins = include_at_in_builtins, .include_at_in_builtins = include_at_in_builtins,
.max_detail_length = max_detail_length,
}, std.json.StringifyOptions{}, out); }, std.json.StringifyOptions{}, out);
std.debug.warn("Successfully saved configuration options!\n", .{}); std.debug.warn("Successfully saved configuration options!\n", .{});

View File

@ -17,7 +17,7 @@ warn_style: bool = false,
/// Path to the build_runner.zig file. /// Path to the build_runner.zig file.
build_runner_path: ?[]const u8 = null, build_runner_path: ?[]const u8 = null,
/// Path to a directory that will be used as cache when `zig run`ing the build runner /// Path to a directory that will be used as cache when `zig run`ning the build runner
build_runner_cache_path: ?[]const u8 = null, build_runner_cache_path: ?[]const u8 = null,
/// Semantic token support /// Semantic token support
@ -29,6 +29,9 @@ operator_completions: bool = true,
/// Whether the @ sign should be part of the completion of builtins /// Whether the @ sign should be part of the completion of builtins
include_at_in_builtins: bool = false, include_at_in_builtins: bool = false,
/// The detail field of completions is truncated to be no longer than this (in bytes).
max_detail_length: usize = 1024 * 1024,
/// Skips references to std. This will improve lookup speeds. /// Skips references to std. This will improve lookup speeds.
/// Going to definition however will continue to work /// Going to definition however will continue to work
skip_std_references: bool = false, skip_std_references: bool = false,

View File

@ -138,6 +138,16 @@ fn send(arena: *std.heap.ArenaAllocator, reqOrRes: anytype) !void {
try stdout.flush(); try stdout.flush();
} }
fn truncateCompletions(list: []types.CompletionItem, max_detail_length: usize) void {
for (list) |*item| {
if (item.detail) |det| {
if (det.len > max_detail_length) {
item.detail = det[0..max_detail_length];
}
}
}
}
fn respondGeneric(id: types.RequestId, response: []const u8) !void { fn respondGeneric(id: types.RequestId, response: []const u8) !void {
const id_len = switch (id) { const id_len = switch (id) {
.Integer => |id_val| blk: { .Integer => |id_val| blk: {
@ -998,6 +1008,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);
truncateCompletions(completions.items, config.max_detail_length);
try send(arena, types.Response{ try send(arena, types.Response{
.id = id, .id = id,
@ -1039,6 +1050,7 @@ fn completeBuiltin(arena: *std.heap.ArenaAllocator, id: types.RequestId, config:
else else
insert_text[1..]; insert_text[1..];
} }
truncateCompletions(builtin_completions.?, config.max_detail_length);
} }
try send(arena, types.Response{ try send(arena, types.Response{
@ -1062,6 +1074,7 @@ 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);
truncateCompletions(completions.items, config.max_detail_length);
try send(arena, types.Response{ try send(arena, types.Response{
.id = id, .id = id,
@ -1087,6 +1100,7 @@ fn completeFieldAccess(
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| {
try typeToCompletion(arena, &completions, result, handle, config); try typeToCompletion(arena, &completions, result, handle, config);
} }
truncateCompletions(completions.items, config.max_detail_length);
try send(arena, types.Response{ try send(arena, types.Response{
.id = id, .id = id,
@ -1099,6 +1113,46 @@ fn completeFieldAccess(
}); });
} }
fn completeError(
arena: *std.heap.ArenaAllocator,
id: types.RequestId,
handle: *DocumentStore.Handle,
config: Config
) !void {
const completions = try document_store.errorCompletionItems(arena, handle);
truncateCompletions(completions, config.max_detail_length);
try send(arena, types.Response{
.id = id,
.result = .{
.CompletionList = .{
.isIncomplete = false,
.items = completions,
},
},
});
}
fn completeDot(
arena: *std.heap.ArenaAllocator,
id: types.RequestId,
handle: *DocumentStore.Handle,
config: Config
) !void {
var completions = try document_store.enumCompletionItems(arena, handle);
truncateCompletions(completions, config.max_detail_length);
try send(arena, types.Response{
.id = id,
.result = .{
.CompletionList = .{
.isIncomplete = false,
.items = completions,
},
},
});
}
fn documentSymbol(arena: *std.heap.ArenaAllocator, id: types.RequestId, handle: *DocumentStore.Handle) !void { fn documentSymbol(arena: *std.heap.ArenaAllocator, id: types.RequestId, handle: *DocumentStore.Handle) !void {
try send(arena, types.Response{ try send(arena, types.Response{
.id = id, .id = id,
@ -1117,6 +1171,9 @@ fn loadConfig(folder_path: []const u8) ?Config {
}; };
defer allocator.free(file_buf); defer allocator.free(file_buf);
// TODO: Uh oh. Profile the actual build time impact
// of adding config options and consider alternatives (TOML?)
@setEvalBranchQuota(2000);
// TODO: Better errors? Doesn't seem like std.json can provide us positions or context. // TODO: Better errors? Doesn't seem like std.json can provide us positions or context.
var config = std.json.parse(Config, &std.json.TokenStream.init(file_buf), std.json.ParseOptions{ .allocator = allocator }) catch |err| { var config = std.json.parse(Config, &std.json.TokenStream.init(file_buf), std.json.ParseOptions{ .allocator = allocator }) catch |err| {
logger.warn("Error while parsing configuration file: {}", .{err}); logger.warn("Error while parsing configuration file: {}", .{err});
@ -1304,28 +1361,13 @@ fn completionHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req:
const doc_position = try offsets.documentPosition(handle.document, req.params.position, offset_encoding); const doc_position = try offsets.documentPosition(handle.document, req.params.position, offset_encoding);
const pos_context = try analysis.documentPositionContext(arena, handle.document, doc_position); const pos_context = try analysis.documentPositionContext(arena, handle.document, doc_position);
const use_snippets = config.enable_snippets and client_capabilities.supports_snippets; const use_snippets = config.enable_snippets and client_capabilities.supports_snippets;
switch (pos_context) { switch (pos_context) {
.builtin => try completeBuiltin(arena, id, config), .builtin => try completeBuiltin(arena, id, config),
.var_access, .empty => try completeGlobal(arena, id, doc_position.absolute_index, handle, config), .var_access, .empty => try completeGlobal(arena, id, doc_position.absolute_index, handle, config),
.field_access => |range| try completeFieldAccess(arena, id, handle, doc_position, range, config), .field_access => |range| try completeFieldAccess(arena, id, handle, doc_position, range, config),
.global_error_set => try send(arena, types.Response{ .global_error_set => try completeError(arena, id, handle, config),
.id = id, .enum_literal => try completeDot(arena, id, handle, config),
.result = .{
.CompletionList = .{
.isIncomplete = false,
.items = try document_store.errorCompletionItems(arena, handle),
},
},
}),
.enum_literal => try send(arena, types.Response{
.id = id,
.result = .{
.CompletionList = .{
.isIncomplete = false,
.items = try document_store.enumCompletionItems(arena, handle),
},
},
}),
.label => try completeLabel(arena, id, doc_position.absolute_index, handle, config), .label => try completeLabel(arena, id, doc_position.absolute_index, handle, config),
else => try respondGeneric(id, no_completions_response), else => try respondGeneric(id, no_completions_response),
} }