Merge pull request #784 from matklad/selectionRange
textDocument/selectionRange
This commit is contained in:
commit
34621b7358
@ -1612,7 +1612,7 @@ fn initializeHandler(server: *Server, writer: anytype, id: types.RequestId, req:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(textDocument.synchronization) |synchronization| {
|
if (textDocument.synchronization) |synchronization| {
|
||||||
server.client_capabilities.supports_will_save = synchronization.willSave.value;
|
server.client_capabilities.supports_will_save = synchronization.willSave.value;
|
||||||
server.client_capabilities.supports_will_save_wait_until = synchronization.willSaveWaitUntil.value;
|
server.client_capabilities.supports_will_save_wait_until = synchronization.willSaveWaitUntil.value;
|
||||||
}
|
}
|
||||||
@ -1662,7 +1662,7 @@ fn initializeHandler(server: *Server, writer: anytype, id: types.RequestId, req:
|
|||||||
.documentFormattingProvider = true,
|
.documentFormattingProvider = true,
|
||||||
.documentRangeFormattingProvider = false,
|
.documentRangeFormattingProvider = false,
|
||||||
.foldingRangeProvider = true,
|
.foldingRangeProvider = true,
|
||||||
.selectionRangeProvider = false,
|
.selectionRangeProvider = true,
|
||||||
.workspaceSymbolProvider = false,
|
.workspaceSymbolProvider = false,
|
||||||
.rangeProvider = false,
|
.rangeProvider = false,
|
||||||
.documentProvider = true,
|
.documentProvider = true,
|
||||||
@ -1906,7 +1906,7 @@ fn willSaveHandler(server: *Server, writer: anytype, id: types.RequestId, req: r
|
|||||||
const tracy_zone = tracy.trace(@src());
|
const tracy_zone = tracy.trace(@src());
|
||||||
defer tracy_zone.end();
|
defer tracy_zone.end();
|
||||||
|
|
||||||
if(server.client_capabilities.supports_will_save_wait_until) return;
|
if (server.client_capabilities.supports_will_save_wait_until) return;
|
||||||
try willSaveWaitUntilHandler(server, writer, id, req);
|
try willSaveWaitUntilHandler(server, writer, id, req);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1927,7 +1927,7 @@ fn willSaveWaitUntilHandler(server: *Server, writer: anytype, id: types.RequestI
|
|||||||
|
|
||||||
return try send(writer, allocator, types.Response{
|
return try send(writer, allocator, types.Response{
|
||||||
.id = id,
|
.id = id,
|
||||||
.result = .{.TextEdits = text_edits.toOwnedSlice(allocator)},
|
.result = .{ .TextEdits = text_edits.toOwnedSlice(allocator) },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2690,6 +2690,64 @@ fn foldingRangeHandler(server: *Server, writer: anytype, id: types.RequestId, re
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn selectionRangeHandler(server: *Server, writer: anytype, id: types.RequestId, req: requests.SelectionRange) !void {
|
||||||
|
const allocator = server.arena.allocator();
|
||||||
|
const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse {
|
||||||
|
log.warn("Trying to get selection range of non existent document {s}", .{req.params.textDocument.uri});
|
||||||
|
return try respondGeneric(writer, id, null_result_response);
|
||||||
|
};
|
||||||
|
|
||||||
|
// For each of the input positons, we need to compute the stack of AST
|
||||||
|
// nodes/ranges which contain the position. At the moment, we do this in a
|
||||||
|
// super inefficient way, by iterationg _all_ nodes, selecting the ones that
|
||||||
|
// contain position, and then sorting.
|
||||||
|
//
|
||||||
|
// A faster algorithm would be to walk the tree starting from the root,
|
||||||
|
// descending into the child containing the position at every step.
|
||||||
|
var result = try allocator.alloc(*types.SelectionRange, req.params.positions.len);
|
||||||
|
var locs = try std.ArrayListUnmanaged(offsets.Loc).initCapacity(allocator, 32);
|
||||||
|
for (req.params.positions) |position, position_index| {
|
||||||
|
const index = offsets.positionToIndex(handle.text, position, server.offset_encoding);
|
||||||
|
|
||||||
|
locs.clearRetainingCapacity();
|
||||||
|
for (handle.tree.nodes.items(.data)) |_, i| {
|
||||||
|
const node = @intCast(u32, i);
|
||||||
|
const loc = offsets.nodeToLoc(handle.tree, node);
|
||||||
|
if (loc.start <= index and index <= loc.end) {
|
||||||
|
(try locs.addOne(allocator)).* = loc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std.sort.sort(offsets.Loc, locs.items, {}, shorterLocsFirst);
|
||||||
|
{
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i + 1 < locs.items.len) {
|
||||||
|
if (std.meta.eql(locs.items[i], locs.items[i + 1])) {
|
||||||
|
_ = locs.orderedRemove(i);
|
||||||
|
} else {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var selection_ranges = try allocator.alloc(types.SelectionRange, locs.items.len);
|
||||||
|
for (selection_ranges) |*range, i| {
|
||||||
|
range.range = offsets.locToRange(handle.text, locs.items[i], server.offset_encoding);
|
||||||
|
range.parent = if (i + 1 < selection_ranges.len) &selection_ranges[i + 1] else null;
|
||||||
|
}
|
||||||
|
result[position_index] = &selection_ranges[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
try send(writer, allocator, types.Response{
|
||||||
|
.id = id,
|
||||||
|
.result = .{ .SelectionRange = result },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shorterLocsFirst(_: void, lhs: offsets.Loc, rhs: offsets.Loc) bool {
|
||||||
|
return (lhs.end - lhs.start) < (rhs.end - rhs.start);
|
||||||
|
}
|
||||||
|
|
||||||
// Needed for the hack seen below.
|
// Needed for the hack seen below.
|
||||||
fn extractErr(val: anytype) anyerror {
|
fn extractErr(val: anytype) anyerror {
|
||||||
val catch |e| return e;
|
val catch |e| return e;
|
||||||
@ -2817,8 +2875,8 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void
|
|||||||
.{ "textDocument/didChange", requests.ChangeDocument, changeDocumentHandler },
|
.{ "textDocument/didChange", requests.ChangeDocument, changeDocumentHandler },
|
||||||
.{ "textDocument/didSave", requests.SaveDocument, saveDocumentHandler },
|
.{ "textDocument/didSave", requests.SaveDocument, saveDocumentHandler },
|
||||||
.{ "textDocument/didClose", requests.CloseDocument, closeDocumentHandler },
|
.{ "textDocument/didClose", requests.CloseDocument, closeDocumentHandler },
|
||||||
.{"textDocument/willSave", requests.WillSave, willSaveHandler},
|
.{ "textDocument/willSave", requests.WillSave, willSaveHandler },
|
||||||
.{"textDocument/willSaveWaitUntil", requests.WillSave, willSaveWaitUntilHandler},
|
.{ "textDocument/willSaveWaitUntil", requests.WillSave, willSaveWaitUntilHandler },
|
||||||
.{ "textDocument/semanticTokens/full", requests.SemanticTokensFull, semanticTokensFullHandler },
|
.{ "textDocument/semanticTokens/full", requests.SemanticTokensFull, semanticTokensFullHandler },
|
||||||
.{ "textDocument/inlayHint", requests.InlayHint, inlayHintHandler },
|
.{ "textDocument/inlayHint", requests.InlayHint, inlayHintHandler },
|
||||||
.{ "textDocument/completion", requests.Completion, completionHandler },
|
.{ "textDocument/completion", requests.Completion, completionHandler },
|
||||||
@ -2836,6 +2894,7 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void
|
|||||||
.{ "textDocument/codeAction", requests.CodeAction, codeActionHandler },
|
.{ "textDocument/codeAction", requests.CodeAction, codeActionHandler },
|
||||||
.{ "workspace/didChangeConfiguration", Config.DidChangeConfigurationParams, didChangeConfigurationHandler },
|
.{ "workspace/didChangeConfiguration", Config.DidChangeConfigurationParams, didChangeConfigurationHandler },
|
||||||
.{ "textDocument/foldingRange", requests.FoldingRange, foldingRangeHandler },
|
.{ "textDocument/foldingRange", requests.FoldingRange, foldingRangeHandler },
|
||||||
|
.{ "textDocument/selectionRange", requests.SelectionRange, selectionRangeHandler },
|
||||||
};
|
};
|
||||||
|
|
||||||
if (zig_builtin.zig_backend == .stage1) {
|
if (zig_builtin.zig_backend == .stage1) {
|
||||||
|
@ -312,3 +312,10 @@ pub const FoldingRange = struct {
|
|||||||
textDocument: TextDocumentIdentifier,
|
textDocument: TextDocumentIdentifier,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const SelectionRange = struct {
|
||||||
|
params: struct {
|
||||||
|
textDocument: TextDocumentIdentifier,
|
||||||
|
positions: []types.Position,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
@ -44,6 +44,7 @@ pub const ResponseParams = union(enum) {
|
|||||||
CodeAction: []CodeAction,
|
CodeAction: []CodeAction,
|
||||||
ApplyEdit: ApplyWorkspaceEditParams,
|
ApplyEdit: ApplyWorkspaceEditParams,
|
||||||
FoldingRange: []FoldingRange,
|
FoldingRange: []FoldingRange,
|
||||||
|
SelectionRange: []*SelectionRange,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Response = struct {
|
pub const Response = struct {
|
||||||
@ -528,3 +529,8 @@ pub const FoldingRange = struct {
|
|||||||
startLine: usize,
|
startLine: usize,
|
||||||
endLine: usize,
|
endLine: usize,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const SelectionRange = struct {
|
||||||
|
range: Range,
|
||||||
|
parent: ?*SelectionRange,
|
||||||
|
};
|
||||||
|
76
tests/lsp_features/selection_range.zig
Normal file
76
tests/lsp_features/selection_range.zig
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const zls = @import("zls");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
const helper = @import("../helper.zig");
|
||||||
|
const Context = @import("../context.zig").Context;
|
||||||
|
const ErrorBuilder = @import("../ErrorBuilder.zig");
|
||||||
|
|
||||||
|
const types = zls.types;
|
||||||
|
const offsets = zls.offsets;
|
||||||
|
const requests = zls.requests;
|
||||||
|
|
||||||
|
const allocator: std.mem.Allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
test "selectionRange - empty" {
|
||||||
|
try testSelectionRange("<>", &.{});
|
||||||
|
}
|
||||||
|
|
||||||
|
test "seletionRange - smoke" {
|
||||||
|
try testSelectionRange(
|
||||||
|
\\fn main() void {
|
||||||
|
\\ const x = 1 <>+ 1;
|
||||||
|
\\}
|
||||||
|
, &.{ "1 + 1", "const x = 1 + 1", "{\n const x = 1 + 1;\n}" });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testSelectionRange(source: []const u8, want: []const []const u8) !void {
|
||||||
|
var phr = try helper.collectClearPlaceholders(allocator, source);
|
||||||
|
defer phr.deinit(allocator);
|
||||||
|
|
||||||
|
var ctx = try Context.init();
|
||||||
|
defer ctx.deinit();
|
||||||
|
|
||||||
|
const test_uri: []const u8 = switch (builtin.os.tag) {
|
||||||
|
.windows => "file:///C:\\test.zig",
|
||||||
|
else => "file:///test.zig",
|
||||||
|
};
|
||||||
|
|
||||||
|
try ctx.requestDidOpen(test_uri, phr.new_source);
|
||||||
|
|
||||||
|
const position = offsets.locToRange(phr.new_source, phr.locations.items(.new)[0], .utf16).start;
|
||||||
|
|
||||||
|
const SelectionRange = struct {
|
||||||
|
range: types.Range,
|
||||||
|
parent: ?*@This(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const request = requests.SelectionRange{ .params = .{
|
||||||
|
.textDocument = .{ .uri = test_uri },
|
||||||
|
.positions = &.{position},
|
||||||
|
} };
|
||||||
|
|
||||||
|
const response = try ctx.requestGetResponse(?[]SelectionRange, "textDocument/selectionRange", request);
|
||||||
|
defer response.deinit();
|
||||||
|
|
||||||
|
const selectionRanges: []SelectionRange = response.result orelse {
|
||||||
|
std.debug.print("Server returned `null` as the result\n", .{});
|
||||||
|
return error.InvalidResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
var got = std.ArrayList([]const u8).init(allocator);
|
||||||
|
defer got.deinit();
|
||||||
|
|
||||||
|
var it: ?*SelectionRange = &selectionRanges[0];
|
||||||
|
while (it) |r| {
|
||||||
|
const slice = offsets.rangeToSlice(phr.new_source, r.range, .utf16);
|
||||||
|
(try got.addOne()).* = slice;
|
||||||
|
it = r.parent;
|
||||||
|
}
|
||||||
|
const last = got.pop();
|
||||||
|
try std.testing.expectEqualStrings(phr.new_source, last);
|
||||||
|
try std.testing.expectEqual(want.len, got.items.len);
|
||||||
|
for (want) |w, i| {
|
||||||
|
try std.testing.expectEqualStrings(w, got.items[i]);
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ comptime {
|
|||||||
_ = @import("lsp_features/inlay_hints.zig");
|
_ = @import("lsp_features/inlay_hints.zig");
|
||||||
_ = @import("lsp_features/references.zig");
|
_ = @import("lsp_features/references.zig");
|
||||||
_ = @import("lsp_features/completion.zig");
|
_ = @import("lsp_features/completion.zig");
|
||||||
|
_ = @import("lsp_features/selection_range.zig");
|
||||||
|
|
||||||
// Language features
|
// Language features
|
||||||
_ = @import("language_features/cimport.zig");
|
_ = @import("language_features/cimport.zig");
|
||||||
|
Loading…
Reference in New Issue
Block a user