Add LSP navigation and symbol panels
This commit is contained in:
+116
@@ -152,6 +152,11 @@ pub const Client = struct {
|
||||
if (std.mem.startsWith(u8, line, "lsp_sync ")) return self.openLspSync(line[9..]);
|
||||
if (std.mem.startsWith(u8, line, "lsp_diagnostics ")) return self.openLspDiagnostics(line[16..]);
|
||||
if (std.mem.eql(u8, line, "lsp_diagnostics_open_selected")) return self.openSelectedLspDiagnostic();
|
||||
if (std.mem.startsWith(u8, line, "lsp_definition ")) return self.openLspLocations("definition", line[15..]);
|
||||
if (std.mem.startsWith(u8, line, "lsp_references ")) return self.openLspLocations("references", line[15..]);
|
||||
if (std.mem.startsWith(u8, line, "lsp_document_symbols ")) return self.openLspSymbols("document", line[21..]);
|
||||
if (std.mem.startsWith(u8, line, "lsp_workspace_symbols ")) return self.openLspSymbols("workspace", line[22..]);
|
||||
if (std.mem.eql(u8, line, "lsp_navigation_open_selected")) return self.openSelectedLspNavigation();
|
||||
if (std.mem.startsWith(u8, line, "panel_open ")) return self.applyProtocolCommand(line);
|
||||
if (std.mem.startsWith(u8, line, "list_open ")) return self.applyProtocolCommand(line);
|
||||
if (std.mem.startsWith(u8, line, "list_filter ")) return self.applyProtocolCommand(line);
|
||||
@@ -451,6 +456,32 @@ pub const Client = struct {
|
||||
fn openSelectedLspDiagnostic(self: *Client) !void {
|
||||
const selected = self.session.selectListPanel() catch return Error.ProtocolRejected;
|
||||
const location = lsp_mod.diagnosticLocationFromRow(selected) catch return Error.ProtocolRejected;
|
||||
try self.jumpCurrentBufferToLspLocation(location);
|
||||
}
|
||||
|
||||
fn openLspLocations(self: *Client, kind: []const u8, payload: []const u8) !void {
|
||||
const rows = lsp_mod.locationRowsAlloc(self.allocator, kind, payload) catch return Error.ProtocolRejected;
|
||||
defer freeOwnedRows(self.allocator, rows);
|
||||
const title = if (std.mem.eql(u8, kind, "definition")) "lsp-definition" else "lsp-references";
|
||||
try self.session.openListPanel(title, rows);
|
||||
self.message = null;
|
||||
}
|
||||
|
||||
fn openLspSymbols(self: *Client, scope: []const u8, payload: []const u8) !void {
|
||||
const rows = lsp_mod.symbolRowsAlloc(self.allocator, scope, payload) catch return Error.ProtocolRejected;
|
||||
defer freeOwnedRows(self.allocator, rows);
|
||||
const title = if (std.mem.eql(u8, scope, "document")) "lsp-document-symbols" else "lsp-workspace-symbols";
|
||||
try self.session.openListPanel(title, rows);
|
||||
self.message = null;
|
||||
}
|
||||
|
||||
fn openSelectedLspNavigation(self: *Client) !void {
|
||||
const selected = self.session.selectListPanel() catch return Error.ProtocolRejected;
|
||||
const location = lsp_mod.navigationLocationFromRow(selected) catch return Error.ProtocolRejected;
|
||||
try self.jumpCurrentBufferToLspLocation(location);
|
||||
}
|
||||
|
||||
fn jumpCurrentBufferToLspLocation(self: *Client, location: lsp_mod.DiagnosticLocation) !void {
|
||||
const snap = try self.session.snapshot();
|
||||
const bytes = try self.allocator.dupe(u8, snap.bytes);
|
||||
defer self.allocator.free(bytes);
|
||||
@@ -1653,3 +1684,88 @@ test "adversarial: malformed diagnostics and unselected summary row do not corru
|
||||
const snap = try client.session.snapshot();
|
||||
try std.testing.expectEqualStrings("safe", snap.bytes);
|
||||
}
|
||||
|
||||
test "regular: lsp definition panel selects and jumps current buffer" {
|
||||
var client = try Client.init(std.testing.allocator, .{ .width = 96, .height = 6 });
|
||||
defer client.deinit();
|
||||
|
||||
try client.handleTraceLine("open one abcTARGET");
|
||||
const payload =
|
||||
\\{"jsonrpc":"2.0","id":2,"result":{"uri":"file:///tmp/main.zig","range":{"start":{"line":0,"character":4},"end":{"line":0,"character":7}}}}
|
||||
;
|
||||
const command = try std.fmt.allocPrint(std.testing.allocator, "lsp_definition {s}", .{payload});
|
||||
defer std.testing.allocator.free(command);
|
||||
try client.handleTraceLine(command);
|
||||
{
|
||||
const frame = try client.render(std.testing.allocator);
|
||||
defer std.testing.allocator.free(frame);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "lsp-definition") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "lsp:nav:definition:1:5:file_///tmp/main.zig:location") != null);
|
||||
}
|
||||
try client.handleTraceLine("lsp_navigation_open_selected");
|
||||
const snap = try client.session.snapshot();
|
||||
try std.testing.expectEqual(@as(usize, 4), snap.cursor_byte);
|
||||
}
|
||||
|
||||
test "regular: lsp references document symbols and workspace symbols render shared panels" {
|
||||
var client = try Client.init(std.testing.allocator, .{ .width = 112, .height = 7 });
|
||||
defer client.deinit();
|
||||
|
||||
try client.handleTraceLine("open symbol body");
|
||||
const references_payload =
|
||||
\\{"jsonrpc":"2.0","id":3,"result":[{"uri":"file:///tmp/main.zig","range":{"start":{"line":0,"character":0},"end":{"line":0,"character":3}}},{"uri":"file:///tmp/lib.zig","range":{"start":{"line":0,"character":7},"end":{"line":0,"character":11}}}]}
|
||||
;
|
||||
const references_command = try std.fmt.allocPrint(std.testing.allocator, "lsp_references {s}", .{references_payload});
|
||||
defer std.testing.allocator.free(references_command);
|
||||
try client.handleTraceLine(references_command);
|
||||
{
|
||||
const frame = try client.render(std.testing.allocator);
|
||||
defer std.testing.allocator.free(frame);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "lsp-references") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "lsp:nav:references:1:1:file_///tmp/main.zig:location") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "lsp:nav:references:1:8:file_///tmp/lib.zig:location") != null);
|
||||
}
|
||||
|
||||
const document_payload =
|
||||
\\{"jsonrpc":"2.0","id":4,"result":[{"name":"main","kind":12,"range":{"start":{"line":0,"character":0},"end":{"line":0,"character":6}},"selectionRange":{"start":{"line":0,"character":0},"end":{"line":0,"character":4}}}]}
|
||||
;
|
||||
const document_command = try std.fmt.allocPrint(std.testing.allocator, "lsp_document_symbols {s}", .{document_payload});
|
||||
defer std.testing.allocator.free(document_command);
|
||||
try client.handleTraceLine(document_command);
|
||||
{
|
||||
const frame = try client.render(std.testing.allocator);
|
||||
defer std.testing.allocator.free(frame);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "lsp-document-symbols") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "lsp:symbol:document:kind_12:1:1:current:main") != null);
|
||||
}
|
||||
|
||||
const workspace_payload =
|
||||
\\{"jsonrpc":"2.0","id":5,"result":[{"name":"helper","kind":12,"location":{"uri":"file:///tmp/lib.zig","range":{"start":{"line":0,"character":7},"end":{"line":0,"character":11}}}}]}
|
||||
;
|
||||
const workspace_command = try std.fmt.allocPrint(std.testing.allocator, "lsp_workspace_symbols {s}", .{workspace_payload});
|
||||
defer std.testing.allocator.free(workspace_command);
|
||||
try client.handleTraceLine(workspace_command);
|
||||
{
|
||||
const frame = try client.render(std.testing.allocator);
|
||||
defer std.testing.allocator.free(frame);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "lsp-workspace-symbols") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "lsp:symbol:workspace:kind_12:1:8:file_///tmp/lib.zig:helper") != null);
|
||||
}
|
||||
}
|
||||
|
||||
test "adversarial: lsp navigation failures do not corrupt buffer" {
|
||||
var client = try Client.init(std.testing.allocator, .{ .width = 80, .height = 5 });
|
||||
defer client.deinit();
|
||||
|
||||
try client.handleTraceLine("open safe");
|
||||
try std.testing.expectError(Error.ProtocolRejected, client.handleTraceLine("lsp_definition not-json"));
|
||||
const none_payload =
|
||||
\\{"jsonrpc":"2.0","id":2,"result":null}
|
||||
;
|
||||
const command = try std.fmt.allocPrint(std.testing.allocator, "lsp_definition {s}", .{none_payload});
|
||||
defer std.testing.allocator.free(command);
|
||||
try client.handleTraceLine(command);
|
||||
try std.testing.expectError(Error.ProtocolRejected, client.handleTraceLine("lsp_navigation_open_selected"));
|
||||
const snap = try client.session.snapshot();
|
||||
try std.testing.expectEqualStrings("safe", snap.bytes);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user