Skip to content

Commit

Permalink
Merge pull request #35 from nolanderc/32-completion-off-by-one
Browse files Browse the repository at this point in the history
Completion off-by-one
  • Loading branch information
nolanderc authored Oct 17, 2023
2 parents b4bb93c + c932f8d commit d3bdc4d
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 33 deletions.
21 changes: 3 additions & 18 deletions src/Document.zig
Original file line number Diff line number Diff line change
Expand Up @@ -83,23 +83,8 @@ pub fn nodeRange(self: *@This(), node: u32) !lsp.Range {
};
}

pub fn wordUnderCursor(self: *@This(), cursor: lsp.Position) []const u8 {
const offset = self.utf8FromPosition(cursor);
const bytes = self.contents.items;

if (!isIdentifierChar(bytes[offset])) return "";

var start = offset;
var end = offset;

while (start > 0 and isIdentifierChar(bytes[start - 1])) start -= 1;
while (end < bytes.len and isIdentifierChar(bytes[end])) end += 1;

return bytes[start..end];
}

/// Return the node right under the cursor.
pub fn nodeUnderCursor(self: *@This(), cursor: lsp.Position) !?u32 {
pub fn tokenUnderCursor(self: *@This(), cursor: lsp.Position) !?u32 {
const offset = self.utf8FromPosition(cursor);
const parsed = try self.parseTree();
const tree = parsed.tree;
Expand All @@ -114,7 +99,7 @@ pub fn nodeUnderCursor(self: *@This(), cursor: lsp.Position) !?u32 {
}

/// Return the node closest to left of the cursor.
pub fn nodeBeforeCursor(self: *@This(), cursor: lsp.Position) !?u32 {
pub fn tokenBeforeCursor(self: *@This(), cursor: lsp.Position) !?u32 {
const offset = self.utf8FromPosition(cursor);
const parsed = try self.parseTree();
const tree = parsed.tree;
Expand All @@ -128,7 +113,7 @@ pub fn nodeBeforeCursor(self: *@This(), cursor: lsp.Position) !?u32 {
if (span.start == span.end) continue;

// ignore tokens after the cursor
if (offset < span.start) continue;
if (offset <= span.start) continue;

if (span.end > best_end) {
// found a token further to the right
Expand Down
16 changes: 16 additions & 0 deletions src/analysis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,22 @@ test "find definition global" {
});
}

test "find definition field" {
try expectDefinition(
\\struct Foo { int /*1*/bar, /*2*/baz; };
\\void main() {
\\ Foo foo;
\\ foo./*3*/bar;
\\ foo./*4*/baz;
\\}
, &.{
.{ .source = "/*3*/", .target = "/*1*/", .should_exist = true },
.{ .source = "/*3*/", .target = "/*2*/", .should_exist = false },
.{ .source = "/*4*/", .target = "/*1*/", .should_exist = false },
.{ .source = "/*4*/", .target = "/*2*/", .should_exist = true },
});
}

fn expectDefinition(
source: []const u8,
cases: []const struct {
Expand Down
41 changes: 26 additions & 15 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -545,10 +545,12 @@ pub const Dispatch = struct {
var symbol_arena = std.heap.ArenaAllocator.init(state.allocator);
defer symbol_arena.deinit();

try completionsAtPosition(
const token = try document.tokenBeforeCursor(params.value.position);

try completionsAtToken(
state,
document,
params.value.position,
token,
&completions,
symbol_arena.allocator(),
.{ .ignore_current = true },
Expand All @@ -557,28 +559,28 @@ pub const Dispatch = struct {
try state.success(request.id, completions.items);
}

fn completionsAtPosition(
fn completionsAtToken(
state: *State,
document: *Workspace.Document,
position: lsp.Position,
start_token: ?u32,
completions: *std.ArrayList(lsp.CompletionItem),
arena: std.mem.Allocator,
options: struct { ignore_current: bool },
) !void {
var has_fields = false;

if (try document.nodeBeforeCursor(position)) |node| {
var symbols = std.ArrayList(analysis.Reference).init(arena);
var symbols = std.ArrayList(analysis.Reference).init(arena);

try analysis.visibleFields(arena, document, node, &symbols);
if (start_token) |token| {
try analysis.visibleFields(arena, document, token, &symbols);
has_fields = symbols.items.len != 0;

if (!has_fields) try analysis.visibleSymbols(arena, document, node, &symbols);
if (!has_fields) try analysis.visibleSymbols(arena, document, token, &symbols);

try completions.ensureUnusedCapacity(symbols.items.len);

for (symbols.items) |symbol| {
if (options.ignore_current and symbol.document == document and symbol.node == node) {
if (options.ignore_current and symbol.document == document and symbol.node == token) {
continue;
}

Expand Down Expand Up @@ -635,20 +637,29 @@ pub const Dispatch = struct {
std.log.debug("hover: {} {s}", .{ params.value.position, params.value.textDocument.uri });

const document = try getDocumentOrFail(state, request, params.value.textDocument);
const parsed = try document.parseTree();

const word = document.wordUnderCursor(params.value.position);
std.log.debug("hover word: '{'}'", .{std.zig.fmtEscapes(word)});
const token = try document.tokenUnderCursor(params.value.position) orelse {
return state.success(request.id, null);
};

if (parsed.tree.tag(token) != .identifier) {
return state.success(request.id, null);
}

const token_span = parsed.tree.token(token);
const token_text = document.source()[token_span.start..token_span.end];

var completions = std.ArrayList(lsp.CompletionItem).init(state.allocator);
defer completions.deinit();

var symbol_arena = std.heap.ArenaAllocator.init(state.allocator);
defer symbol_arena.deinit();

try completionsAtPosition(
try completionsAtToken(
state,
document,
params.value.position,
token,
&completions,
symbol_arena.allocator(),
.{ .ignore_current = false },
Expand All @@ -658,7 +669,7 @@ pub const Dispatch = struct {
defer text.deinit();

for (completions.items) |*completion| {
if (std.mem.eql(u8, completion.label, word)) {
if (std.mem.eql(u8, completion.label, token_text)) {
if (text.items.len != 0) {
try text.appendSlice("\n\n---\n\n");
}
Expand Down Expand Up @@ -732,7 +743,7 @@ pub const Dispatch = struct {
});

const document = try state.workspace.getOrLoadDocument(params.value.textDocument);
const source_node = try document.nodeUnderCursor(params.value.position) orelse {
const source_node = try document.tokenUnderCursor(params.value.position) orelse {
std.log.debug("no node under cursor", .{});
return state.success(request.id, null);
};
Expand Down

0 comments on commit d3bdc4d

Please sign in to comment.