Skip to content

Commit f392094

Browse files
authored
feat: Jump to definition for imports take to file (#55)
closes #50
1 parent 0f7079b commit f392094

File tree

6 files changed

+132
-37
lines changed

6 files changed

+132
-37
lines changed

src/context/jumpable.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pub enum Jumpable {
2+
Import(String),
3+
Identifier(String)
4+
}

src/context/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod hoverable;
2+
pub mod jumpable;

src/lsp.rs

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@ use tracing::{error, info};
55
use async_lsp::lsp_types::{
66
CompletionItem, CompletionItemKind, CompletionOptions, CompletionParams, CompletionResponse,
77
CreateFilesParams, DeleteFilesParams, DidChangeConfigurationParams,
8-
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
9-
DidSaveTextDocumentParams, DocumentFormattingParams, DocumentRangeFormattingParams,
10-
DocumentSymbolParams, DocumentSymbolResponse, FileOperationFilter, FileOperationPattern,
11-
FileOperationPatternKind, FileOperationRegistrationOptions, GotoDefinitionParams,
12-
GotoDefinitionResponse, Hover, HoverContents, HoverParams, HoverProviderCapability,
13-
InitializeParams, InitializeResult, Location, OneOf, PrepareRenameResponse, ReferenceParams,
14-
RenameFilesParams, RenameOptions, RenameParams, ServerCapabilities, ServerInfo,
15-
TextDocumentPositionParams, TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit, Url,
16-
WorkspaceEdit, WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities,
8+
DidChangeTextDocumentParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams,
9+
DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentFormattingParams,
10+
DocumentRangeFormattingParams, DocumentSymbolParams, DocumentSymbolResponse,
11+
FileOperationFilter, FileOperationPattern, FileOperationPatternKind,
12+
FileOperationRegistrationOptions, GotoDefinitionParams, GotoDefinitionResponse, Hover,
13+
HoverContents, HoverParams, HoverProviderCapability, InitializeParams, InitializeResult,
14+
Location, OneOf, PrepareRenameResponse, ReferenceParams, RenameFilesParams, RenameOptions,
15+
RenameParams, ServerCapabilities, ServerInfo, TextDocumentPositionParams,
16+
TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit, Url, WorkspaceEdit,
17+
WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities,
1718
WorkspaceServerCapabilities,
1819
};
1920
use async_lsp::{LanguageClient, LanguageServer, ResponseError};
@@ -134,7 +135,7 @@ impl LanguageServer for ProtoLanguageServer {
134135
let current_package_name = tree.get_package_name(content.as_bytes());
135136

136137
let Some(hv) = hv else {
137-
error!(uri=%uri, "failed to get identifier");
138+
error!(uri=%uri, "failed to get hoverable identifier");
138139
return Box::pin(async move { Ok(None) });
139140
};
140141

@@ -153,6 +154,7 @@ impl LanguageServer for ProtoLanguageServer {
153154
}))
154155
})
155156
}
157+
156158
fn completion(
157159
&mut self,
158160
params: CompletionParams,
@@ -288,11 +290,11 @@ impl LanguageServer for ProtoLanguageServer {
288290
};
289291

290292
let content = self.state.get_content(&uri);
291-
let identifier = tree.get_user_defined_text(&pos, content.as_bytes());
293+
let jump = tree.get_jumpable_at_position(&pos, content.as_bytes());
292294
let current_package_name = tree.get_package_name(content.as_bytes());
293295

294-
let Some(identifier) = identifier else {
295-
error!(uri=%uri, "failed to get identifier");
296+
let Some(jump) = jump else {
297+
error!(uri=%uri, "failed to get jump identifier");
296298
return Box::pin(async move { Ok(None) });
297299
};
298300

@@ -301,9 +303,10 @@ impl LanguageServer for ProtoLanguageServer {
301303
return Box::pin(async move { Ok(None) });
302304
};
303305

306+
let ipath = self.configs.get_include_paths(&uri).unwrap_or_default();
304307
let locations = self
305308
.state
306-
.definition(current_package_name.as_ref(), identifier.as_ref());
309+
.definition(&ipath, current_package_name.as_ref(), jump);
307310

308311
let response = match locations.len() {
309312
0 => None,
@@ -464,4 +467,12 @@ impl LanguageServer for ProtoLanguageServer {
464467
fn did_change_configuration(&mut self, _: DidChangeConfigurationParams) -> Self::NotifyResult {
465468
ControlFlow::Continue(())
466469
}
470+
471+
// Required because when jumping to outside the workspace; this is triggered
472+
fn did_change_workspace_folders(
473+
&mut self,
474+
_: DidChangeWorkspaceFoldersParams,
475+
) -> Self::NotifyResult {
476+
ControlFlow::Continue(())
477+
}
467478
}

src/parser/tree.rs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use async_lsp::lsp_types::{Position, Range};
22
use tree_sitter::{Node, TreeCursor};
33

44
use crate::{
5-
context::hoverable::Hoverables,
5+
context::{hoverable::Hoverables, jumpable::Jumpable},
66
nodekind::NodeKind,
77
utils::{lsp_to_ts_point, ts_to_lsp_position},
88
};
@@ -63,10 +63,31 @@ impl ParsedTree {
6363
pos: &Position,
6464
content: &'a [u8],
6565
) -> Option<&'a str> {
66-
self.get_actionable_node_at_position(pos)
66+
self.get_user_defined_node(pos)
6767
.map(|n| n.utf8_text(content.as_ref()).expect("utf-8 parse error"))
6868
}
6969

70+
pub fn get_jumpable_at_position(&self, pos: &Position, content: &[u8]) -> Option<Jumpable> {
71+
let n = self.get_node_at_position(pos)?;
72+
73+
// If node is import path. return the whole path, removing the quotes
74+
if n.parent().filter(NodeKind::is_import_path).is_some() {
75+
return Some(Jumpable::Import(
76+
n.utf8_text(content)
77+
.expect("utf-8 parse error")
78+
.trim_matches('"')
79+
.to_string(),
80+
));
81+
}
82+
83+
// If node is user defined enum/message
84+
if let Some(identifier) = self.get_user_defined_text(pos, content) {
85+
return Some(Jumpable::Identifier(identifier.to_string()));
86+
}
87+
88+
None
89+
}
90+
7091
pub fn get_hoverable_at_position<'a>(
7192
&'a self,
7293
pos: &Position,
@@ -94,7 +115,7 @@ impl ParsedTree {
94115
}
95116

96117
pub fn get_ancestor_nodes_at_position<'a>(&'a self, pos: &Position) -> Vec<Node<'a>> {
97-
let Some(mut n) = self.get_actionable_node_at_position(pos) else {
118+
let Some(mut n) = self.get_user_defined_node(pos) else {
98119
return vec![];
99120
};
100121

@@ -113,7 +134,7 @@ impl ParsedTree {
113134
nodes
114135
}
115136

116-
pub fn get_actionable_node_at_position<'a>(&'a self, pos: &Position) -> Option<Node<'a>> {
137+
pub fn get_user_defined_node<'a>(&'a self, pos: &Position) -> Option<Node<'a>> {
117138
self.get_node_at_position(pos)
118139
.map(|n| {
119140
if NodeKind::is_actionable(&n) {
@@ -161,7 +182,7 @@ impl ParsedTree {
161182
.collect()
162183
}
163184

164-
pub fn get_import_path<'a>(&self, content: &'a [u8]) -> Vec<&'a str> {
185+
pub fn get_import_paths<'a>(&self, content: &'a [u8]) -> Vec<&'a str> {
165186
self.get_import_node()
166187
.into_iter()
167188
.map(|n| {
@@ -218,7 +239,7 @@ mod test {
218239

219240
let package_name = tree.get_package_name(contents.as_ref());
220241
assert_yaml_snapshot!(package_name);
221-
let imports = tree.get_import_path(contents.as_ref());
242+
let imports = tree.get_import_paths(contents.as_ref());
222243
assert_yaml_snapshot!(imports);
223244
}
224245
}

src/state.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ impl ProtoLanguageState {
9898
// After content is upserted, those imports which couldn't be located
9999
// are flagged as import error
100100
self.get_tree(uri)
101-
.map(|t| t.get_import_path(content.as_ref()))
101+
.map(|t| t.get_import_paths(content.as_ref()))
102102
.unwrap_or_default()
103103
.into_iter()
104104
.map(ToOwned::to_owned)

src/workspace/definition.rs

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,53 @@
1-
use async_lsp::lsp_types::Location;
1+
use std::path::PathBuf;
22

3-
use crate::{state::ProtoLanguageState, utils::split_identifier_package};
3+
use async_lsp::lsp_types::{Location, Range, Url};
4+
5+
use crate::{
6+
context::jumpable::Jumpable, state::ProtoLanguageState, utils::split_identifier_package,
7+
};
48

59
impl ProtoLanguageState {
6-
pub fn definition(&self, curr_package: &str, identifier: &str) -> Vec<Location> {
7-
let (mut package, identifier) = split_identifier_package(identifier);
8-
if package.is_empty() {
9-
package = curr_package;
10+
pub fn definition(
11+
&self,
12+
ipath: &[PathBuf],
13+
curr_package: &str,
14+
jump: Jumpable,
15+
) -> Vec<Location> {
16+
match jump {
17+
Jumpable::Import(path) => {
18+
let Some(p) = ipath.iter().map(|p| p.join(&path)).find(|p| p.exists()) else {
19+
return vec![];
20+
};
21+
22+
let Ok(uri) = Url::from_file_path(p) else {
23+
return vec![];
24+
};
25+
26+
vec![Location {
27+
uri,
28+
range: Range::default(), // just start of the file
29+
}]
30+
}
31+
Jumpable::Identifier(identifier) => {
32+
let (mut package, identifier) = split_identifier_package(identifier.as_str());
33+
if package.is_empty() {
34+
package = curr_package;
35+
}
36+
37+
self.get_trees_for_package(package)
38+
.into_iter()
39+
.fold(vec![], |mut v, tree| {
40+
v.extend(tree.definition(identifier, self.get_content(&tree.uri)));
41+
v
42+
})
43+
}
1044
}
11-
self.get_trees_for_package(package)
12-
.into_iter()
13-
.fold(vec![], |mut v, tree| {
14-
v.extend(tree.definition(identifier, self.get_content(&tree.uri)));
15-
v
16-
})
1745
}
1846
}
1947

2048
#[cfg(test)]
2149
mod test {
50+
use crate::context::jumpable::Jumpable;
2251
use std::path::PathBuf;
2352

2453
use insta::assert_yaml_snapshot;
@@ -41,9 +70,38 @@ mod test {
4170
state.upsert_file(&b_uri, b.to_owned(), &ipath);
4271
state.upsert_file(&c_uri, c.to_owned(), &ipath);
4372

44-
assert_yaml_snapshot!(state.definition("com.workspace", "Author"));
45-
assert_yaml_snapshot!(state.definition("com.workspace", "Author.Address"));
46-
assert_yaml_snapshot!(state.definition("com.workspace", "com.utility.Foobar.Baz"));
47-
assert_yaml_snapshot!(state.definition("com.utility", "Baz"));
73+
assert_yaml_snapshot!(state.definition(
74+
&ipath,
75+
"com.workspace",
76+
Jumpable::Identifier("Author".to_owned())
77+
));
78+
assert_yaml_snapshot!(state.definition(
79+
&ipath,
80+
"com.workspace",
81+
Jumpable::Identifier("Author.Address".to_owned())
82+
));
83+
assert_yaml_snapshot!(state.definition(
84+
&ipath,
85+
"com.workspace",
86+
Jumpable::Identifier("com.utility.Foobar.Baz".to_owned())
87+
));
88+
assert_yaml_snapshot!(state.definition(
89+
&ipath,
90+
"com.utility",
91+
Jumpable::Identifier("Baz".to_owned())
92+
));
93+
94+
let loc = state.definition(
95+
&vec![std::env::current_dir().unwrap().join(&ipath[0])],
96+
"com.workspace",
97+
Jumpable::Import("c.proto".to_owned()),
98+
);
99+
100+
assert_eq!(loc.len(), 1);
101+
assert!(loc[0]
102+
.uri
103+
.to_file_path()
104+
.unwrap()
105+
.ends_with(ipath[0].join("c.proto")))
48106
}
49107
}

0 commit comments

Comments
 (0)