Skip to content

Commit 9f0363b

Browse files
authored
Prefer current file in jump to definition (#886)
1 parent cdb2ff5 commit 9f0363b

File tree

4 files changed

+143
-15
lines changed

4 files changed

+143
-15
lines changed

crates/ark/src/lsp/definitions.rs

Lines changed: 120 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,20 @@ pub fn goto_definition<'a>(
4242
let end = convert_point_to_position(contents, node.end_position());
4343
let range = Range { start, end };
4444

45-
// search for a reference in the document index
45+
// Search for a reference in the document index
4646
if node.is_identifier() {
4747
let symbol = document.contents.node_slice(&node)?.to_string();
48-
if let Some((path, entry)) = indexer::find(symbol.as_str()) {
48+
49+
let uri = &params.text_document_position_params.text_document.uri;
50+
let info = if let Ok(preferred_path) = uri.to_file_path() {
51+
// First search in current file, then in all files
52+
indexer::find_in_file(symbol.as_str(), &preferred_path)
53+
.or_else(|| indexer::find(symbol.as_str()))
54+
} else {
55+
indexer::find(symbol.as_str())
56+
};
57+
58+
if let Some((path, entry)) = info {
4959
let link = LocationLink {
5060
origin_selection_range: None,
5161
target_uri: Url::from_file_path(path).unwrap(),
@@ -95,7 +105,7 @@ foo <- 42
95105
print(foo)
96106
"#;
97107
let doc = Document::new(code, None);
98-
let (path, uri) = test_path();
108+
let (path, uri) = test_path("test.R");
99109

100110
indexer::update(&doc, &path).unwrap();
101111

@@ -132,7 +142,7 @@ foo <- 1
132142
print(foo)
133143
"#;
134144
let doc = Document::new(code, None);
135-
let (path, uri) = test_path();
145+
let (path, uri) = test_path("test.R");
136146

137147
indexer::update(&doc, &path).unwrap();
138148

@@ -159,4 +169,110 @@ print(foo)
159169
}
160170
);
161171
}
172+
173+
#[test]
174+
fn test_goto_definition_prefers_local_symbol() {
175+
let _guard = indexer::ResetIndexerGuard;
176+
177+
// Both files define the same symbol
178+
let code1 = r#"
179+
foo <- 1
180+
foo
181+
"#;
182+
let code2 = r#"
183+
foo <- 2
184+
foo
185+
"#;
186+
187+
let doc1 = Document::new(code1, None);
188+
let doc2 = Document::new(code2, None);
189+
190+
let (path1, uri1) = test_path("file1.R");
191+
let (path2, uri2) = test_path("file2.R");
192+
193+
indexer::update(&doc1, &path1).unwrap();
194+
indexer::update(&doc2, &path2).unwrap();
195+
196+
// Go to definition for foo in file1
197+
let params1 = GotoDefinitionParams {
198+
text_document_position_params: lsp_types::TextDocumentPositionParams {
199+
text_document: lsp_types::TextDocumentIdentifier { uri: uri1.clone() },
200+
position: lsp_types::Position::new(2, 0),
201+
},
202+
work_done_progress_params: Default::default(),
203+
partial_result_params: Default::default(),
204+
};
205+
assert_matches!(
206+
goto_definition(&doc1, params1).unwrap(),
207+
Some(GotoDefinitionResponse::Link(ref links)) => {
208+
// Should jump to foo in file1
209+
assert_eq!(links[0].target_uri, uri1);
210+
}
211+
);
212+
213+
// Go to definition for foo in file2
214+
let params2 = GotoDefinitionParams {
215+
text_document_position_params: lsp_types::TextDocumentPositionParams {
216+
text_document: lsp_types::TextDocumentIdentifier { uri: uri2.clone() },
217+
position: lsp_types::Position::new(2, 0),
218+
},
219+
work_done_progress_params: Default::default(),
220+
partial_result_params: Default::default(),
221+
};
222+
assert_matches!(
223+
goto_definition(&doc2, params2).unwrap(),
224+
Some(GotoDefinitionResponse::Link(ref links)) => {
225+
// Should jump to foo in file2
226+
assert_eq!(links[0].target_uri, uri2);
227+
}
228+
);
229+
}
230+
231+
#[test]
232+
fn test_goto_definition_falls_back_to_other_file() {
233+
let _guard = indexer::ResetIndexerGuard;
234+
235+
// file1 defines foo, file2 does not
236+
let code1 = r#"
237+
foo <- 1
238+
"#;
239+
let code2 = r#"
240+
foo
241+
"#;
242+
243+
let doc1 = Document::new(code1, None);
244+
let doc2 = Document::new(code2, None);
245+
246+
// Use test_path for cross-platform compatibility
247+
let (path1, uri1) = crate::lsp::util::test_path("file1.R");
248+
let (path2, uri2) = crate::lsp::util::test_path("file2.R");
249+
250+
indexer::update(&doc1, &path1).unwrap();
251+
indexer::update(&doc2, &path2).unwrap();
252+
253+
// Go to definition for foo in file2 (should jump to file1)
254+
let params2 = GotoDefinitionParams {
255+
text_document_position_params: lsp_types::TextDocumentPositionParams {
256+
text_document: lsp_types::TextDocumentIdentifier { uri: uri2.clone() },
257+
position: lsp_types::Position::new(1, 0),
258+
},
259+
work_done_progress_params: Default::default(),
260+
partial_result_params: Default::default(),
261+
};
262+
let result2 = goto_definition(&doc2, params2).unwrap();
263+
assert_matches!(
264+
result2,
265+
Some(GotoDefinitionResponse::Link(ref links)) => {
266+
// Should jump to foo in file1
267+
assert_eq!(links[0].target_uri, uri1);
268+
assert_eq!(
269+
links[0].target_range,
270+
lsp_types::Range {
271+
start: lsp_types::Position::new(1, 0),
272+
end: lsp_types::Position::new(1, 3),
273+
}
274+
);
275+
}
276+
);
277+
}
162278
}

crates/ark/src/lsp/indexer.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ pub fn start(folders: Vec<String>) {
9191
);
9292
}
9393

94+
/// Search the workspace files and return the first symbol match
9495
pub fn find(symbol: &str) -> Option<(String, IndexEntry)> {
9596
let index = WORKSPACE_INDEX.lock().unwrap();
9697

@@ -103,6 +104,21 @@ pub fn find(symbol: &str) -> Option<(String, IndexEntry)> {
103104
None
104105
}
105106

107+
/// Search a specific workspace file for a symbol
108+
pub fn find_in_file(symbol: &str, path: &std::path::Path) -> Option<(String, IndexEntry)> {
109+
let index = WORKSPACE_INDEX.lock().unwrap();
110+
111+
if let Ok(path_str) = str_from_path(path) {
112+
if let Some(index) = index.get(path_str) {
113+
if let Some(entry) = index.get(symbol) {
114+
return Some((path_str.to_string(), entry.clone()));
115+
}
116+
}
117+
}
118+
119+
None
120+
}
121+
106122
pub fn map(mut callback: impl FnMut(&Path, &String, &IndexEntry)) {
107123
let index = WORKSPACE_INDEX.lock().unwrap();
108124

@@ -194,7 +210,7 @@ impl Drop for ResetIndexerGuard {
194210
fn str_from_path(path: &Path) -> anyhow::Result<&str> {
195211
path.to_str().ok_or(anyhow!(
196212
"Couldn't convert path {} to string",
197-
path.display()
213+
path.to_string_lossy()
198214
))
199215
}
200216

crates/ark/src/lsp/symbols.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -990,7 +990,7 @@ outer <- 4
990990

991991
// Index the document
992992
let doc = Document::new(code, None);
993-
let (path, _) = test_path();
993+
let (path, _) = test_path("test.R");
994994
indexer::update(&doc, &path).unwrap();
995995

996996
// Query for all symbols

crates/ark/src/lsp/util.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,12 @@ pub unsafe extern "C-unwind" fn ps_object_id(object: SEXP) -> anyhow::Result<SEX
2929
return Ok(Rf_mkString(value.as_ptr() as *const c_char));
3030
}
3131

32+
/// Create an absolute path from a file name.
33+
/// File name is joined to the temp directory in a cross-platform way.
34+
/// Returns both a `PathBuf` and a `Url`. Does not create the file.
3235
#[cfg(test)]
33-
pub(crate) fn test_path() -> (std::path::PathBuf, url::Url) {
34-
use std::path::PathBuf;
35-
36-
let path = if cfg!(windows) {
37-
PathBuf::from(r"C:\test.R")
38-
} else {
39-
PathBuf::from("/test.R")
40-
};
36+
pub(crate) fn test_path(file_name: &str) -> (std::path::PathBuf, url::Url) {
37+
let path = std::env::temp_dir().join(file_name);
4138
let uri = url::Url::from_file_path(&path).unwrap();
42-
4339
(path, uri)
4440
}

0 commit comments

Comments
 (0)