reimplement document store

This commit is contained in:
Techatrix 2022-10-05 13:23:38 +02:00
parent ac6353add7
commit dab7eb81cc
5 changed files with 673 additions and 940 deletions

File diff suppressed because it is too large Load Diff

View File

@ -151,7 +151,7 @@ fn showMessage(server: *Server, writer: anytype, message_type: types.MessageType
}); });
} }
fn publishDiagnostics(server: *Server, writer: anytype, handle: *DocumentStore.Handle) !void { fn publishDiagnostics(server: *Server, writer: anytype, handle: DocumentStore.Handle) !void {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -252,17 +252,19 @@ fn publishDiagnostics(server: *Server, writer: anytype, handle: *DocumentStore.H
} }
} }
for (handle.cimports) |cimport| { for (handle.cimports.items(.hash)) |hash, i| {
if (cimport.result != .failure) continue; const result = server.document_store.cimports.get(hash) orelse continue;
const stderr = std.mem.trim(u8, cimport.result.failure, " "); if (result != .failure) continue;
const stderr = std.mem.trim(u8, result.failure, " ");
var pos_and_diag_iterator = std.mem.split(u8, stderr, ":"); var pos_and_diag_iterator = std.mem.split(u8, stderr, ":");
_ = pos_and_diag_iterator.next(); // skip file path _ = pos_and_diag_iterator.next(); // skip file path
_ = pos_and_diag_iterator.next(); // skip line _ = pos_and_diag_iterator.next(); // skip line
_ = pos_and_diag_iterator.next(); // skip character _ = pos_and_diag_iterator.next(); // skip character
const node = handle.cimports.items(.node)[i];
try diagnostics.append(allocator, .{ try diagnostics.append(allocator, .{
.range = offsets.nodeToRange(handle.tree, cimport.node, server.offset_encoding), .range = offsets.nodeToRange(handle.tree, node, server.offset_encoding),
.severity = .Error, .severity = .Error,
.code = "cImport", .code = "cImport",
.source = "zls", .source = "zls",
@ -283,7 +285,7 @@ fn publishDiagnostics(server: *Server, writer: anytype, handle: *DocumentStore.H
fn getAstCheckDiagnostics( fn getAstCheckDiagnostics(
server: *Server, server: *Server,
handle: *DocumentStore.Handle, handle: DocumentStore.Handle,
diagnostics: *std.ArrayListUnmanaged(types.Diagnostic), diagnostics: *std.ArrayListUnmanaged(types.Diagnostic),
) !void { ) !void {
var allocator = server.arena.allocator(); var allocator = server.arena.allocator();
@ -966,10 +968,7 @@ fn gotoDefinitionString(
defer tracy_zone.end(); defer tracy_zone.end();
const import_str = analysis.getImportStr(handle.tree, 0, pos_index) orelse return null; const import_str = analysis.getImportStr(handle.tree, 0, pos_index) orelse return null;
const uri = server.document_store.uriFromImportStr(server.arena.allocator(), handle.*, import_str) catch |err| switch (err) { const uri = try server.document_store.uriFromImportStr(server.arena.allocator(), handle.*, import_str);
error.UriBadScheme => return null,
error.OutOfMemory => |e| return e,
};
return types.Location{ return types.Location{
.uri = uri orelse return null, .uri = uri orelse return null,
@ -1327,7 +1326,7 @@ fn completeError(server: *Server, handle: *DocumentStore.Handle) ![]types.Comple
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
return try server.document_store.errorCompletionItems(&server.arena, handle); return try server.document_store.errorCompletionItems(server.arena.allocator(), handle.*);
} }
fn kindToSortScore(kind: types.CompletionItem.Kind) ?[]const u8 { fn kindToSortScore(kind: types.CompletionItem.Kind) ?[]const u8 {
@ -1362,12 +1361,12 @@ fn completeDot(server: *Server, handle: *DocumentStore.Handle) ![]types.Completi
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
var completions = try server.document_store.enumCompletionItems(&server.arena, handle); var completions = try server.document_store.enumCompletionItems(server.arena.allocator(), handle.*);
return completions; return completions;
} }
fn completeFileSystemStringLiteral(allocator: std.mem.Allocator, handle: *DocumentStore.Handle, completing: []const u8, is_import: bool) ![]types.CompletionItem { fn completeFileSystemStringLiteral(allocator: std.mem.Allocator, store: *const DocumentStore, handle: *DocumentStore.Handle, completing: []const u8, is_import: bool) ![]types.CompletionItem {
var subpath_present = false; var subpath_present = false;
var completions = std.ArrayListUnmanaged(types.CompletionItem){}; var completions = std.ArrayListUnmanaged(types.CompletionItem){};
@ -1407,10 +1406,11 @@ fn completeFileSystemStringLiteral(allocator: std.mem.Allocator, handle: *Docume
} }
if (!subpath_present and is_import) { if (!subpath_present and is_import) {
if (handle.associated_build_file) |bf| { if (handle.associated_build_file) |uri| {
try completions.ensureUnusedCapacity(allocator, bf.config.packages.len); const build_file = store.build_files.get(uri).?;
try completions.ensureUnusedCapacity(allocator, build_file.config.packages.len);
for (bf.config.packages) |pkg| { for (build_file.config.packages) |pkg| {
completions.appendAssumeCapacity(.{ completions.appendAssumeCapacity(.{
.label = pkg.name, .label = pkg.name,
.kind = .Module, .kind = .Module,
@ -1704,13 +1704,14 @@ fn changeDocumentHandler(server: *Server, writer: anytype, id: types.RequestId,
_ = id; _ = id;
const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse { const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse return;
log.debug("Trying to change non existent document {s}", .{req.params.textDocument.uri});
return;
};
try server.document_store.applyChanges(handle, req.params.contentChanges, server.offset_encoding); const new_text = try diff.applyTextEdits(server.allocator, handle.text, req.params.contentChanges, server.offset_encoding);
try server.publishDiagnostics(writer, handle); server.allocator.free(handle.text);
handle.text = new_text;
try server.document_store.refreshDocument(handle);
try server.publishDiagnostics(writer, handle.*);
} }
fn saveDocumentHandler(server: *Server, writer: anytype, id: types.RequestId, req: requests.SaveDocument) !void { fn saveDocumentHandler(server: *Server, writer: anytype, id: types.RequestId, req: requests.SaveDocument) !void {
@ -1721,10 +1722,7 @@ fn saveDocumentHandler(server: *Server, writer: anytype, id: types.RequestId, re
const allocator = server.arena.allocator(); const allocator = server.arena.allocator();
const uri = req.params.textDocument.uri; const uri = req.params.textDocument.uri;
const handle = server.document_store.getHandle(uri) orelse { const handle = server.document_store.getHandle(uri) orelse return;
log.warn("Trying to save non existent document {s}", .{uri});
return;
};
try server.document_store.applySave(handle); try server.document_store.applySave(handle);
if (handle.tree.errors.len != 0) return; if (handle.tree.errors.len != 0) return;
@ -1732,7 +1730,7 @@ fn saveDocumentHandler(server: *Server, writer: anytype, id: types.RequestId, re
if (!server.config.enable_autofix) return; if (!server.config.enable_autofix) return;
var diagnostics = std.ArrayListUnmanaged(types.Diagnostic){}; var diagnostics = std.ArrayListUnmanaged(types.Diagnostic){};
try getAstCheckDiagnostics(server, handle, &diagnostics); try getAstCheckDiagnostics(server, handle.*, &diagnostics);
var builder = code_actions.Builder{ var builder = code_actions.Builder{
.arena = &server.arena, .arena = &server.arena,
@ -1790,7 +1788,6 @@ fn semanticTokensFullHandler(server: *Server, writer: anytype, id: types.Request
if (!server.config.enable_semantic_tokens) return try respondGeneric(writer, id, no_semantic_tokens_response); if (!server.config.enable_semantic_tokens) return try respondGeneric(writer, id, no_semantic_tokens_response);
const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse { const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse {
log.warn("Trying to get semantic tokens of non existent document {s}", .{req.params.textDocument.uri});
return try respondGeneric(writer, id, no_semantic_tokens_response); return try respondGeneric(writer, id, no_semantic_tokens_response);
}; };
@ -1807,7 +1804,6 @@ fn completionHandler(server: *Server, writer: anytype, id: types.RequestId, req:
defer tracy_zone.end(); defer tracy_zone.end();
const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse { const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse {
log.warn("Trying to complete in non existent document {s}", .{req.params.textDocument.uri});
return try respondGeneric(writer, id, no_completions_response); return try respondGeneric(writer, id, no_completions_response);
}; };
@ -1838,7 +1834,7 @@ fn completionHandler(server: *Server, writer: anytype, id: types.RequestId, req:
const completing = offsets.locToSlice(handle.tree.source, loc); const completing = offsets.locToSlice(handle.tree.source, loc);
const is_import = pos_context == .import_string_literal; const is_import = pos_context == .import_string_literal;
break :blk try completeFileSystemStringLiteral(server.arena.allocator(), handle, completing, is_import); break :blk try completeFileSystemStringLiteral(server.arena.allocator(), &server.document_store, handle, completing, is_import);
}, },
else => null, else => null,
}; };
@ -1878,7 +1874,6 @@ fn signatureHelpHandler(server: *Server, writer: anytype, id: types.RequestId, r
const getSignatureInfo = @import("signature_help.zig").getSignatureInfo; const getSignatureInfo = @import("signature_help.zig").getSignatureInfo;
const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse { const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse {
log.warn("Trying to get signature help in non existent document {s}", .{req.params.textDocument.uri});
return try respondGeneric(writer, id, no_signatures_response); return try respondGeneric(writer, id, no_signatures_response);
}; };
@ -1912,7 +1907,6 @@ fn gotoHandler(server: *Server, writer: anytype, id: types.RequestId, req: reque
defer tracy_zone.end(); defer tracy_zone.end();
const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse { const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse {
log.warn("Trying to go to definition in non existent document {s}", .{req.params.textDocument.uri});
return try respondGeneric(writer, id, null_result_response); return try respondGeneric(writer, id, null_result_response);
}; };
@ -1956,7 +1950,6 @@ fn hoverHandler(server: *Server, writer: anytype, id: types.RequestId, req: requ
defer tracy_zone.end(); defer tracy_zone.end();
const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse { const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse {
log.warn("Trying to get hover in non existent document {s}", .{req.params.textDocument.uri});
return try respondGeneric(writer, id, null_result_response); return try respondGeneric(writer, id, null_result_response);
}; };
@ -1986,7 +1979,6 @@ fn documentSymbolsHandler(server: *Server, writer: anytype, id: types.RequestId,
defer tracy_zone.end(); defer tracy_zone.end();
const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse { const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse {
log.warn("Trying to get document symbols in non existent document {s}", .{req.params.textDocument.uri});
return try respondGeneric(writer, id, null_result_response); return try respondGeneric(writer, id, null_result_response);
}; };
try server.documentSymbol(writer, id, handle); try server.documentSymbol(writer, id, handle);
@ -1998,7 +1990,6 @@ fn formattingHandler(server: *Server, writer: anytype, id: types.RequestId, req:
if (server.config.zig_exe_path) |zig_exe_path| { if (server.config.zig_exe_path) |zig_exe_path| {
const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse { const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse {
log.warn("Trying to got to definition in non existent document {s}", .{req.params.textDocument.uri});
return try respondGeneric(writer, id, null_result_response); return try respondGeneric(writer, id, null_result_response);
}; };
@ -2124,14 +2115,6 @@ const GeneralReferencesRequest = union(enum) {
.highlight => |highlight| highlight.params.position, .highlight => |highlight| highlight.params.position,
}; };
} }
pub fn name(self: @This()) []const u8 {
return switch (self) {
.rename => "rename",
.references => "references",
.highlight => "highlight references",
};
}
}; };
fn generalReferencesHandler(server: *Server, writer: anytype, id: types.RequestId, req: GeneralReferencesRequest) !void { fn generalReferencesHandler(server: *Server, writer: anytype, id: types.RequestId, req: GeneralReferencesRequest) !void {
@ -2141,7 +2124,6 @@ fn generalReferencesHandler(server: *Server, writer: anytype, id: types.RequestI
const allocator = server.arena.allocator(); const allocator = server.arena.allocator();
const handle = server.document_store.getHandle(req.uri()) orelse { const handle = server.document_store.getHandle(req.uri()) orelse {
log.warn("Trying to get {s} in non existent document {s}", .{ req.name(), req.uri() });
return try respondGeneric(writer, id, null_result_response); return try respondGeneric(writer, id, null_result_response);
}; };
@ -2223,7 +2205,6 @@ fn inlayHintHandler(server: *Server, writer: anytype, id: types.RequestId, req:
if (!server.config.enable_inlay_hints) return try respondGeneric(writer, id, null_result_response); if (!server.config.enable_inlay_hints) return try respondGeneric(writer, id, null_result_response);
const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse { const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse {
log.warn("Trying to get inlay hint of non existent document {s}", .{req.params.textDocument.uri});
return try respondGeneric(writer, id, null_result_response); return try respondGeneric(writer, id, null_result_response);
}; };
@ -2272,7 +2253,6 @@ fn inlayHintHandler(server: *Server, writer: anytype, id: types.RequestId, req:
fn codeActionHandler(server: *Server, writer: anytype, id: types.RequestId, req: requests.CodeAction) !void { fn codeActionHandler(server: *Server, writer: anytype, id: types.RequestId, req: requests.CodeAction) !void {
const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse { const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse {
log.warn("Trying to get code actions of non existent document {s}", .{req.params.textDocument.uri});
return try respondGeneric(writer, id, null_result_response); return try respondGeneric(writer, id, null_result_response);
}; };
@ -2402,12 +2382,12 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void
.shutdown => return try sendErrorResponse(writer, server.arena.allocator(), .InvalidRequest, "server received a request after shutdown!"), .shutdown => return try sendErrorResponse(writer, server.arena.allocator(), .InvalidRequest, "server received a request after shutdown!"),
} }
const start_time = std.time.milliTimestamp(); // const start_time = std.time.milliTimestamp();
defer { defer {
// makes `zig build test` look nice // makes `zig build test` look nice
if (!zig_builtin.is_test and !std.mem.eql(u8, method, "shutdown")) { if (!zig_builtin.is_test and !std.mem.eql(u8, method, "shutdown")) {
const end_time = std.time.milliTimestamp(); // const end_time = std.time.milliTimestamp();
log.debug("Took {}ms to process method {s}", .{ end_time - start_time, method }); // log.debug("Took {}ms to process method {s}", .{ end_time - start_time, method });
} }
} }
@ -2535,7 +2515,10 @@ pub fn init(
try config.configChanged(allocator, config_path); try config.configChanged(allocator, config_path);
var document_store = try DocumentStore.init(allocator, config); var document_store = DocumentStore{
.allocator = allocator,
.config = config,
};
errdefer document_store.deinit(); errdefer document_store.deinit();
var builtin_completions = try std.ArrayListUnmanaged(types.CompletionItem).initCapacity(allocator, data.builtins.len); var builtin_completions = try std.ArrayListUnmanaged(types.CompletionItem).initCapacity(allocator, data.builtins.len);

View File

@ -912,18 +912,16 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, arena: *std.heap.ArenaAl
if (node_tags[import_param] != .string_literal) return null; if (node_tags[import_param] != .string_literal) return null;
const import_str = tree.tokenSlice(main_tokens[import_param]); const import_str = tree.tokenSlice(main_tokens[import_param]);
const new_handle = (store.resolveImport(handle, import_str[1 .. import_str.len - 1]) catch |err| { const import_uri = (try store.uriFromImportStr(arena.allocator(), handle.*, import_str[1 .. import_str.len - 1])) orelse return null;
log.debug("Error {} while processing import {s}", .{ err, import_str });
return null; const new_handle = store.getHandle(import_uri) orelse return null;
}) orelse return null;
// reference to node '0' which is root // reference to node '0' which is root
return TypeWithHandle.typeVal(.{ .node = 0, .handle = new_handle }); return TypeWithHandle.typeVal(.{ .node = 0, .handle = new_handle });
} else if (std.mem.eql(u8, call_name, "@cImport")) { } else if (std.mem.eql(u8, call_name, "@cImport")) {
const new_handle = (store.resolveCImport(handle, node) catch |err| { const cimport_uri = (try store.resolveCImport(handle.*, node)) orelse return null;
log.debug("Error {} while processing cImport", .{err}); // TODO improve
return null; const new_handle = store.getHandle(cimport_uri) orelse return null;
}) orelse return null;
// reference to node '0' which is root // reference to node '0' which is root
return TypeWithHandle.typeVal(.{ .node = 0, .handle = new_handle }); return TypeWithHandle.typeVal(.{ .node = 0, .handle = new_handle });
@ -1088,7 +1086,6 @@ pub fn resolveTypeOfNode(store: *DocumentStore, arena: *std.heap.ArenaAllocator,
} }
/// Collects all `@import`'s we can find into a slice of import paths (without quotes). /// Collects all `@import`'s we can find into a slice of import paths (without quotes).
/// Caller owns returned memory.
pub fn collectImports(allocator: std.mem.Allocator, tree: Ast) error{OutOfMemory}!std.ArrayListUnmanaged([]const u8) { pub fn collectImports(allocator: std.mem.Allocator, tree: Ast) error{OutOfMemory}!std.ArrayListUnmanaged([]const u8) {
var imports = std.ArrayListUnmanaged([]const u8){}; var imports = std.ArrayListUnmanaged([]const u8){};
errdefer { errdefer {

View File

@ -1,5 +1,7 @@
const std = @import("std"); const std = @import("std");
const types = @import("types.zig"); const types = @import("types.zig");
const requests = @import("requests.zig");
const offsets = @import("offsets.zig");
pub const Error = error{ OutOfMemory, InvalidRange }; pub const Error = error{ OutOfMemory, InvalidRange };
@ -350,3 +352,37 @@ fn char_pos_to_range(
.end = result_end_pos.?, .end = result_end_pos.?,
}; };
} }
// Caller owns returned memory.
pub fn applyTextEdits(
allocator: std.mem.Allocator,
text: []const u8,
content_changes: []const requests.TextDocumentContentChangeEvent,
encoding: offsets.Encoding,
) ![:0]const u8 {
var last_full_text_change: ?usize = null;
var i: usize = content_changes.len;
while (i > 0) {
i -= 1;
if (content_changes[i].range == null) {
last_full_text_change = i;
}
}
var text_array = std.ArrayListUnmanaged(u8){};
errdefer text_array.deinit(allocator);
try text_array.appendSlice(allocator, if (last_full_text_change) |index| content_changes[index].text else text);
// don't even bother applying changes before a full text change
const changes = content_changes[if (last_full_text_change) |index| index + 1 else 0..];
for (changes) |item| {
const range = item.range.?; // every element is guaranteed to have `range` set
const loc = offsets.rangeToLoc(text_array.items, range, encoding);
try text_array.replaceRange(allocator, loc.start, loc.end - loc.start, item.text);
}
return try text_array.toOwnedSliceSentinel(allocator, 0);
}

View File

@ -479,26 +479,26 @@ pub fn symbolReferences(
if (decl_handle.decl.* != .ast_node) return builder.locations; if (decl_handle.decl.* != .ast_node) return builder.locations;
if (!workspace) return builder.locations; if (!workspace) return builder.locations;
var imports = std.ArrayListUnmanaged(*DocumentStore.Handle){}; var imports = std.ArrayListUnmanaged(*const DocumentStore.Handle){};
var handle_it = store.handles.iterator(); for (store.handles.values()) |*handle| {
while (handle_it.next()) |entry| { if (skip_std_references and std.mem.indexOf(u8, handle.uri, "std") != null) {
if (skip_std_references and std.mem.indexOf(u8, entry.key_ptr.*, "std") != null) { if (!include_decl or !std.mem.eql(u8, handle.uri, curr_handle.uri))
if (!include_decl or entry.value_ptr.* != curr_handle)
continue; continue;
} }
// Check entry's transitive imports // Check entry's transitive imports
try imports.append(arena.allocator(), entry.value_ptr.*); try imports.append(arena.allocator(), handle);
var i: usize = 0; var i: usize = 0;
blk: while (i < imports.items.len) : (i += 1) { blk: while (i < imports.items.len) : (i += 1) {
const import = imports.items[i]; const import = imports.items[i];
for (import.imports_used.items) |uri| { // TODO handle cimports
for (import.import_uris.items) |uri| {
const h = store.getHandle(uri) orelse break; const h = store.getHandle(uri) orelse break;
if (h == curr_handle) { if (h == curr_handle) {
// entry does import curr_handle // entry does import curr_handle
try symbolReferencesInternal(&builder, 0, entry.value_ptr.*); try symbolReferencesInternal(&builder, 0, handle);
break :blk; break :blk;
} }