-
Notifications
You must be signed in to change notification settings - Fork 30
LSP: implementing goto defitnition/reference/implementation #482
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Basic version of goto-definition. Some speed improvement can be done on package function where we create a temp file to see its source.
…l in queries module This change improves code maintainability and readability by removing redundant query definitions.
…on and definition finding logic for better maintainability feat(lsp/definition.lua): enhance definition search to include enclosing scopes and improve accuracy of symbol resolution refactor(definition.lua): streamline definition search logic and improve code clarity by removing unused functions and comments fix(lsp/init.lua): update require paths for document_symbols and rebuild_index functions to reflect correct module structure feat(workspace.lua): add debug_index function to display workspace index state for easier debugging and verification of indexed symbols
…emoving custom query for enclosing scopes feat(scope.lua): enhance symbol definition lookup with custom query for <<- assignments and improve function signature for better clarity refactor(scope.lua): rename find_definition_in_scope to include cursor position parameters for more precise searching and maintainability
…ee-sitter for better accuracy and robustness fix(lsp/definition.lua): update goto_definition function to handle symbol parsing and response sending more efficiently
4c5f7b4 to
abe1708
Compare
0aa8c14 to
abe1708
Compare
|
What is the status of this pull request? I tried the branch, and I can jump to references and definitions, even if the functions are defined in other R scripts. It's a great job so far! And, with more than 2500 lines of code, it certainly has taken a lot of your time. Thank you! When starting nvim, I get this warning: Jumping to implementation doesn't work, but it also doesn't work in Lua or C. |
|
Thanks for pointing it out for the warnings. These are option to op in/out of the implementation (in case someone would prefer to use My plan is to finish this PR by the end of the week or this weekend ;) For the implementation, it should go to the s3 implementation. I will post an example here when I am back home this afternoon. |
…ders to the configuration
…he top level feat(utils.lua): add top_level_only option to extract_symbols for filtering symbols at the top level only
… list, used to build symbol tree to display document symbols
…res with languageserver package Add new options to disable definition, references, and implementation providers in the languageserver package. Update R.nvim config defaults to include these new providers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements LSP goto definition, find references, and find implementations functionality for R.nvim. It builds on the existing LSP framework to provide scope-aware symbol resolution, workspace-wide search capabilities, and package source lookup.
Changes:
- Added scope-aware symbol resolution using tree-sitter queries and lexical scoping rules
- Implemented workspace indexing with file modification tracking for cross-file navigation
- Added LSP handlers for textDocument/definition, textDocument/references, textDocument/implementation, and textDocument/documentSymbol
Reviewed changes
Copilot reviewed 20 out of 21 changed files in this pull request and generated 29 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_scope_spec.lua | Comprehensive tests for scope resolution and symbol visibility |
| tests/test_goto_definition_spec.lua | Tests for goto definition with scope awareness |
| tests/test_helper.lua | Test helper for tree-sitter parser setup |
| nvimcom/src/apps/tcp.c | Message parsing handlers for definition responses from R |
| nvimcom/src/apps/rnvimserver.c | LSP request handlers and response builders for new features |
| nvimcom/bin/rnvimserver | Compiled binary (should not be committed) |
| nvimcom/R/info.R | R-side definition lookup with package source resolution |
| lua/r/server.lua | Configuration integration for new LSP features |
| lua/r/lsp/workspace.lua | Workspace-wide symbol indexing and caching |
| lua/r/lsp/utils.lua | Shared utilities for symbol extraction and file operations |
| lua/r/lsp/symbols.lua | Document symbol extraction with hierarchical tree building |
| lua/r/lsp/scope.lua | Scope management using tree-sitter locals queries |
| lua/r/lsp/references.lua | Scope-aware reference finding across workspace |
| lua/r/lsp/queries.lua | Centralized tree-sitter query management |
| lua/r/lsp/init.lua | Integration points for new LSP features |
| lua/r/lsp/implementation.lua | S3/S4 method implementation finding |
| lua/r/lsp/definition.lua | Goto definition with workspace and package lookup |
| lua/r/lsp/buffer.lua | Buffer management for parsing external files |
| lua/r/lsp/ast.lua | AST traversal utilities extracted from init.lua |
| lua/r/config.lua | Configuration options for new LSP features |
| doc/R.nvim.txt | Documentation for new LSP features and configuration |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| function M.index_workspace(force) | ||
| if indexed and not force then return end | ||
|
|
||
| local cwd = vim.fn.getcwd() | ||
| local files = {} | ||
| utils.find_r_files(cwd, files) | ||
|
|
||
| workspace_index = {} | ||
| file_mtimes = {} | ||
|
|
||
| for _, filepath in ipairs(files) do | ||
| local stat = vim.uv.fs_stat(filepath) | ||
| if stat then | ||
| file_mtimes[filepath] = stat.mtime.sec | ||
|
|
||
| local definitions = utils.parse_file_definitions(filepath) | ||
| for symbol, locations in pairs(definitions) do | ||
| if not workspace_index[symbol] then workspace_index[symbol] = {} end | ||
| for _, loc in ipairs(locations) do | ||
| table.insert(workspace_index[symbol], loc) | ||
| end | ||
| end | ||
| end | ||
| end | ||
|
|
||
| indexed = true | ||
| end |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Workspace indexing happens synchronously on first call without user feedback. The index_workspace() function (line 21) scans all R files in the workspace, which could be slow for large projects. This happens during the first LSP operation (goto definition, references, etc.), potentially causing unexpected lag. Consider adding a progress notification or making the initial indexing asynchronous.
| # Check if cached file already exists | ||
| if (!file.exists(tmpfile)) { | ||
| header <- sprintf( | ||
| "# %s::%s (no source available - deparsed)", | ||
| pkg_name, | ||
| symbol | ||
| ) | ||
| body <- deparse(fn) | ||
| writeLines(c(header, "", body), tmpfile) | ||
| } |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential race condition in file-based caching. Line 178 checks if the tmpfile exists, and if not, creates it (lines 179-186). However, in a concurrent environment (e.g., multiple Neovim instances), two processes could both find that the file doesn't exist and try to create it simultaneously, leading to corruption or undefined behavior. Consider using atomic file operations or file locking.
| # Check if cached file already exists | |
| if (!file.exists(tmpfile)) { | |
| header <- sprintf( | |
| "# %s::%s (no source available - deparsed)", | |
| pkg_name, | |
| symbol | |
| ) | |
| body <- deparse(fn) | |
| writeLines(c(header, "", body), tmpfile) | |
| } | |
| # Always (re)generate the deparsed cache file to avoid race conditions | |
| header <- sprintf( | |
| "# %s::%s (no source available - deparsed)", | |
| pkg_name, | |
| symbol | |
| ) | |
| body <- deparse(fn) | |
| writeLines(c(header, "", body), tmpfile) |
| for _, filepath in ipairs(files) do | ||
| if utils.normalize_path(filepath) ~= utils.normalize_path(file) then | ||
| local temp_bufnr, _, cleanup = buffer.load_file_to_buffer(filepath) | ||
|
|
||
| if temp_bufnr then | ||
| local _, temp_root = ast.get_parser_and_root(temp_bufnr) | ||
|
|
||
| if temp_root then | ||
| for _, node in query:iter_captures(temp_root, temp_bufnr) do | ||
| local text = vim.treesitter.get_node_text(node, temp_bufnr) | ||
| if text == word then | ||
| local in_function = ast.find_ancestor( | ||
| node, | ||
| "function_definition" | ||
| ) | ||
| if not in_function then | ||
| local start_row, start_col = node:start() | ||
| table.insert(all_refs, { | ||
| file = filepath, | ||
| line = start_row, | ||
| col = start_col, | ||
| }) | ||
| end | ||
| end | ||
| end | ||
| end | ||
|
|
||
| if cleanup then cleanup() end | ||
| end | ||
| end | ||
| end |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential performance issue with nested loops. In the worst case, for each file in the workspace (outer loop at line 159), we iterate through all captures from the query (inner loop at line 167). For a workspace with many files and many references per file, this could be O(n*m) where n is number of files and m is captures per file. Consider adding early termination or limiting the search scope.
| --- Find implementations for R.nvim LSP | ||
| --- Provides textDocument/implementation functionality for S3/S4 methods | ||
|
|
||
| local M = {} | ||
|
|
||
| local workspace = require("r.lsp.workspace") | ||
| local utils = require("r.lsp.utils") | ||
|
|
||
| --- Find S3 method implementations for a generic function | ||
| ---@param word string The generic function name | ||
| ---@return table[] List of locations {file, line, col} | ||
| local function find_s3_methods(word) | ||
| -- Pattern: word.* (e.g., print.default, print.factor) | ||
| local pattern = "^" .. vim.pesc(word) .. "%." | ||
| return workspace.find_symbols_matching(pattern) | ||
| end | ||
|
|
||
| --- Find implementations of a generic function (S3/S4 methods) | ||
| ---@param req_id string LSP request ID | ||
| function M.find_implementations(req_id) | ||
| local word, err = utils.get_keyword_safe() | ||
| if err or not word then | ||
| utils.send_null(req_id) | ||
| return | ||
| end | ||
|
|
||
| utils.prepare_workspace() | ||
|
|
||
| local implementations = find_s3_methods(word) | ||
|
|
||
| -- Strategy 2: Dynamic lookup via nvimcom (if R is running) | ||
| -- TODO: Implement async R query for runtime method discovery | ||
| -- if vim.g.R_Nvim_status == 7 then | ||
| -- local cmd = string.format( | ||
| -- "nvimcom:::send_implementations('%s', '%s')", | ||
| -- req_id, word | ||
| -- ) | ||
| -- require("r.run").send_to_nvimcom("E", cmd) | ||
| -- return | ||
| -- end | ||
|
|
||
| -- Return static results | ||
| if #implementations > 0 then | ||
| utils.send_response("I", req_id, { locations = implementations }) | ||
| else | ||
| utils.send_null(req_id) | ||
| end | ||
| end | ||
|
|
||
| return M |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing test coverage for the find_implementations functionality. The implementation.lua module (which implements textDocument/implementation for S3/S4 methods) lacks tests. This is particularly important given that S3/S4 method resolution has complex edge cases and the pattern-based matching could have subtle bugs that wouldn't be caught without tests.
Co-authored-by: Copilot <[email protected]>
|
@PMassicotte I've opened a new pull request, #493, to work on those changes. Once the pull request is ready, I'll request review from you. |
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
|
@PMassicotte I've opened a new pull request, #494, to work on those changes. Once the pull request is ready, I'll request review from you. |
* Initial plan * refactor: consolidate location-based LSP response functions into single parameterized function Co-authored-by: PMassicotte <[email protected]> * Final update: refactoring complete and validated Co-authored-by: PMassicotte <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: PMassicotte <[email protected]>
* Initial plan * Remove debug logging statements from send_implementation_result Co-authored-by: PMassicotte <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: PMassicotte <[email protected]> Co-authored-by: Philippe Massicotte <[email protected]>
Co-authored-by: Copilot <[email protected]>
This is a first draft of a goto definition/reference based on the LSP internal framework developed in 9f21d6c.
Features
Goto Definition (textDocument/definition)
.R,.Rmd,.qmd)pkg::fnandpkg:::fn)<-,=) and super-assignment (<<-) operatorsFind References (textDocument/references)
Implementation Details
Testing
To test, create this R file with this content:
To test, use lsp keymap directly, or these following lua commands:
Todos
Goto definition on package function is slow the first time you use it
Implement rename functionality. The current infrastructure will allow doing it very easily. We will have to think a bit more about the scoping. For example, renaming a function parameter should only rename it within the function body or renaming a file-level function should rename all its references across the workspace?
Add tests, especially for edge cases like nested functions, anonymous functions, etc.
Add documentation
Use workspace markers to limit the search scope for references?
Remove
debug_index()before merging (see also other helper functions)Add configuration options to let the user to enable/disable these lsp features. Reuse