diff --git a/src/analysis.zig b/src/analysis.zig index 5cde42c..17b4927 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -1218,7 +1218,7 @@ fn tokenRangeAppend(prev: SourceRange, token: std.zig.Token) SourceRange { pub fn documentPositionContext(allocator: *std.mem.Allocator, document: types.TextDocument, position: types.Position) !PositionContext { const line = try document.getLine(@intCast(usize, position.line)); - const pos_char = @intCast(usize, position.character) + 1; + const pos_char = @intCast(usize, position.character); const idx = if (pos_char > line.len) line.len else pos_char; var arena = std.heap.ArenaAllocator.init(allocator); diff --git a/src/main.zig b/src/main.zig index 7073486..a13cfd7 100644 --- a/src/main.zig +++ b/src/main.zig @@ -9,6 +9,10 @@ const data = @import("data/" ++ build_options.data_version ++ ".zig"); const types = @import("types.zig"); const analysis = @import("analysis.zig"); const URI = @import("uri.zig"); +const rename = @import("rename.zig"); + +// TODO Fix LSP -> byte and byte -> LSP offsets +// Implement clangd extension for utf8 offsets as well. pub const log_level: std.log.Level = switch (std.builtin.mode) { .Debug => .debug, @@ -35,9 +39,11 @@ pub fn log( }; send(types.Notification{ .method = "window/showMessage", - .params = types.NotificationParams{ .ShowMessageParams = .{ - .type = message_type, - .message = message, }, + .params = types.NotificationParams{ + .ShowMessageParams = .{ + .type = message_type, + .message = message, + }, }, }) catch |err| { std.debug.print("Failed to send show message notification (error: {}).", .{err}); @@ -50,9 +56,11 @@ pub fn log( send(types.Notification{ .method = "window/logMessage", - .params = types.NotificationParams{ .LogMessageParams = .{ - .type = message_type, - .message = message, }, + .params = types.NotificationParams{ + .LogMessageParams = .{ + .type = message_type, + .message = message, + }, }, }) catch |err| { std.debug.print("Failed to send show message notification (error: {}).", .{err}); @@ -78,7 +86,7 @@ const ClientCapabilities = struct { var client_capabilities = ClientCapabilities{}; const initialize_response = - \\,"result": {"capabilities": {"signatureHelpProvider": {"triggerCharacters": ["(",","]},"textDocumentSync": 1,"completionProvider": {"resolveProvider": false,"triggerCharacters": [".",":","@"]},"documentHighlightProvider": false,"hoverProvider": true,"codeActionProvider": false,"declarationProvider": true,"definitionProvider": true,"typeDefinitionProvider": true,"implementationProvider": false,"referencesProvider": false,"documentSymbolProvider": true,"colorProvider": false,"documentFormattingProvider": true,"documentRangeFormattingProvider": false,"foldingRangeProvider": false,"selectionRangeProvider": false,"workspaceSymbolProvider": false,"rangeProvider": false,"documentProvider": true,"workspace": {"workspaceFolders": {"supported": true,"changeNotifications": true}},"semanticTokensProvider": {"documentProvider": true,"legend": {"tokenTypes": ["type","struct","enum","union","parameter","variable","tagField","field","errorTag","function","keyword","comment","string","number","operator","builtin","label"],"tokenModifiers": ["definition","async","documentation", "generic"]}}}}} + \\,"result": {"capabilities": {"signatureHelpProvider": {"triggerCharacters": ["(",","]},"textDocumentSync": 1,"renameProvider":true,"completionProvider": {"resolveProvider": false,"triggerCharacters": [".",":","@"]},"documentHighlightProvider": false,"hoverProvider": true,"codeActionProvider": false,"declarationProvider": true,"definitionProvider": true,"typeDefinitionProvider": true,"implementationProvider": false,"referencesProvider": false,"documentSymbolProvider": true,"colorProvider": false,"documentFormattingProvider": true,"documentRangeFormattingProvider": false,"foldingRangeProvider": false,"selectionRangeProvider": false,"workspaceSymbolProvider": false,"rangeProvider": false,"documentProvider": true,"workspace": {"workspaceFolders": {"supported": true,"changeNotifications": true}},"semanticTokensProvider": {"documentProvider": true,"legend": {"tokenTypes": ["type","struct","enum","union","parameter","variable","tagField","field","errorTag","function","keyword","comment","string","number","operator","builtin","label"],"tokenModifiers": ["definition","async","documentation", "generic"]}}}}} ; const not_implemented_response = @@ -110,7 +118,7 @@ fn send(reqOrRes: var) !void { defer arena.deinit(); var arr = std.ArrayList(u8).init(&arena.allocator); - try std.json.stringify(reqOrRes, .{}, arr.outStream()); + try std.json.stringify(reqOrRes, .{}, arr.writer()); const stdout_stream = stdout.outStream(); try stdout_stream.print("Content-Length: {}\r\n\r\n", .{arr.items.len}); @@ -709,6 +717,32 @@ fn gotoDefinitionString(id: types.RequestId, pos_index: usize, handle: *Document }); } +fn renameDefinitionGlobal(id: types.RequestId, handle: *DocumentStore.Handle, pos_index: usize, new_name: []const u8) !void { + // @TODO +} + +fn renameDefinitionFieldAccess(id: types.RequestId, handle: *DocumentStore.Handle, position: types.Position, new_name: []const u8) !void { + // @TODO +} + +fn renameDefinitionLabel(id: types.RequestId, handle: *DocumentStore.Handle, pos_index: usize, new_name: []const u8) !void { + // @TODO + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + + const decl = (try getLabelGlobal(pos_index, handle)) orelse return try respondGeneric(id, null_result_response); + + var workspace_edit = types.WorkspaceEdit{ + .changes = std.StringHashMap([]types.TextEdit).init(&arena.allocator), + }; + try rename.renameLabel(&arena, decl, new_name, &workspace_edit.changes.?); + + try send(types.Response{ + .id = id, + .result = .{ .WorkspaceEdit = workspace_edit }, + }); +} + const DeclToCompletionContext = struct { completions: *std.ArrayList(types.CompletionItem), config: *const Config, @@ -1140,7 +1174,7 @@ fn processJsonRpc(parser: *std.json.Parser, json: []const u8, config: Config, ke const pos = types.Position{ .line = position.getValue("line").?.Integer, - .character = position.getValue("character").?.Integer - 1, + .character = position.getValue("character").?.Integer, }; if (pos.character >= 0) { const pos_index = try handle.document.positionToIndex(pos); @@ -1206,7 +1240,7 @@ fn processJsonRpc(parser: *std.json.Parser, json: []const u8, config: Config, ke const pos = types.Position{ .line = position.getValue("line").?.Integer, - .character = position.getValue("character").?.Integer - 1, + .character = position.getValue("character").?.Integer, }; if (pos.character >= 0) { const resolve_alias = !std.mem.eql(u8, method, "textDocument/declaration"); @@ -1236,7 +1270,7 @@ fn processJsonRpc(parser: *std.json.Parser, json: []const u8, config: Config, ke const pos = types.Position{ .line = position.getValue("line").?.Integer, - .character = position.getValue("character").?.Integer - 1, + .character = position.getValue("character").?.Integer, }; if (pos.character >= 0) { const pos_index = try handle.document.positionToIndex(pos); @@ -1307,6 +1341,36 @@ fn processJsonRpc(parser: *std.json.Parser, json: []const u8, config: Config, ke } } return try respondGeneric(id, null_result_response); + } else if (std.mem.eql(u8, method, "textDocument/rename")) { + const params = root.Object.getValue("params").?.Object; + const document = params.getValue("textDocument").?.Object; + const uri = document.getValue("uri").?.String; + const position = params.getValue("position").?.Object; + + const handle = document_store.getHandle(uri) orelse { + std.log.debug(.main, "Trying to got to definition in non existent document {}", .{uri}); + return try respondGeneric(id, null_result_response); + }; + + // @TODO + const pos = types.Position{ + .line = position.getValue("line").?.Integer, + .character = position.getValue("character").?.Integer, + }; + if (pos.character >= 0) { + const new_name = params.getValue("newName").?.String; + const pos_index = try handle.document.positionToIndex(pos); + const pos_context = try analysis.documentPositionContext(allocator, handle.document, pos); + + switch (pos_context) { + .var_access => try renameDefinitionGlobal(id, handle, pos_index, new_name), + .field_access => try renameDefinitionFieldAccess(id, handle, pos, new_name), + .label => try renameDefinitionLabel(id, handle, pos_index, new_name), + else => try respondGeneric(id, null_result_response), + } + } else { + try respondGeneric(id, null_result_response); + } } else if (std.mem.eql(u8, method, "textDocument/references") or std.mem.eql(u8, method, "textDocument/documentHighlight") or std.mem.eql(u8, method, "textDocument/codeAction") or @@ -1314,7 +1378,6 @@ fn processJsonRpc(parser: *std.json.Parser, json: []const u8, config: Config, ke std.mem.eql(u8, method, "textDocument/documentLink") or std.mem.eql(u8, method, "textDocument/rangeFormatting") or std.mem.eql(u8, method, "textDocument/onTypeFormatting") or - std.mem.eql(u8, method, "textDocument/rename") or std.mem.eql(u8, method, "textDocument/prepareRename") or std.mem.eql(u8, method, "textDocument/foldingRange") or std.mem.eql(u8, method, "textDocument/selectionRange")) diff --git a/src/types.zig b/src/types.zig index f398199..5d02ca1 100644 --- a/src/types.zig +++ b/src/types.zig @@ -61,6 +61,7 @@ pub const ResponseParams = union(enum) { DocumentSymbols: []DocumentSymbol, SemanticTokens: struct { data: []const u32 }, TextEdits: []TextEdit, + WorkspaceEdit: WorkspaceEdit, }; /// JSONRPC error @@ -201,6 +202,33 @@ pub const TextDocument = struct { } }; +pub const WorkspaceEdit = struct { + changes: ?std.StringHashMap([]TextEdit), + + pub fn jsonStringify( + self: WorkspaceEdit, + options: std.json.StringifyOptions, + writer: var, + ) @TypeOf(writer).Error!void { + try writer.writeByte('{'); + if (self.changes) |changes| { + try writer.writeAll("\"changes\": {"); + var it = changes.iterator(); + var idx: usize = 0; + while (it.next()) |entry| : (idx += 1) { + if (idx != 0) try writer.writeAll(", "); + + try writer.writeByte('"'); + try writer.writeAll(entry.key); + try writer.writeAll("\":"); + try std.json.stringify(entry.value, options, writer); + } + try writer.writeByte('}'); + } + try writer.writeByte('}'); + } +}; + pub const TextEdit = struct { range: Range, newText: String,