Skip to content

Commit 0db5ddf

Browse files
authored
Merge branch 'main' into ashar/config
2 parents 78808b7 + bb41aab commit 0db5ddf

21 files changed

+473
-40
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "protols"
33
description = "Language server for proto3 files"
4-
version = "0.7.1"
4+
version = "0.8.0"
55
edition = "2021"
66
license = "MIT"
77
homepage = "https://github.com/coder3101/protols"

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@
1616
- ✅ Go to Definition
1717
- ✅ Hover Information
1818
- ✅ Rename Symbols
19+
- ✅ Find references
1920

2021
## 🚀 Getting Started
2122

2223
### Installation
2324

2425
#### For Neovim
2526

26-
To install Protols, run:
27+
You can install [protols with mason.nvim](https://github.com/mason-org/mason-registry/blob/main/packages/protols/package.yaml) or directly from crates.io with:
2728

2829
```bash
2930
cargo install protols
@@ -71,6 +72,10 @@ Displays comments and documentation for protobuf symbols on hover. Works seamles
7172

7273
Allows renaming of symbols like messages and enums, along with all their usages across packages. Currently, renaming fields within symbols is not supported directly.
7374

75+
### Find References
76+
77+
Allows user defined types like messages and enums can be checked for references. Nested fields are completely supported.
78+
7479
---
7580

7681
Protols is designed to supercharge your workflow with **proto** files. We welcome contributions and feedback from the community! Feel free to check out the [repository](https://github.com/coder3101/protols) and join in on improving this tool! 🎉

src/lsp.rs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use async_lsp::lsp_types::{
1212
DocumentSymbolParams, DocumentSymbolResponse, FileOperationFilter, FileOperationPattern,
1313
FileOperationPatternKind, FileOperationRegistrationOptions, GotoDefinitionParams,
1414
GotoDefinitionResponse, Hover, HoverContents, HoverParams, HoverProviderCapability,
15-
InitializeParams, InitializeResult, OneOf, PrepareRenameResponse, ProgressParams,
16-
RenameFilesParams, RenameOptions, RenameParams, ServerCapabilities, ServerInfo,
15+
InitializeParams, InitializeResult, Location, OneOf, PrepareRenameResponse, ProgressParams,
16+
ReferenceParams, RenameFilesParams, RenameOptions, RenameParams, ServerCapabilities, ServerInfo,
1717
TextDocumentPositionParams, TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit, Url,
1818
WorkspaceEdit, WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities,
1919
WorkspaceServerCapabilities,
@@ -126,6 +126,7 @@ impl LanguageServer for ProtoLanguageServer {
126126
rename_provider: Some(rename_provider),
127127
document_formatting_provider: Some(OneOf::Left(true)),
128128
document_range_formatting_provider: Some(OneOf::Left(true)),
129+
references_provider: Some(OneOf::Left(true)),
129130

130131
..ServerCapabilities::default()
131132
},
@@ -151,7 +152,7 @@ impl LanguageServer for ProtoLanguageServer {
151152
};
152153

153154
let content = self.state.get_content(&uri);
154-
let identifier = tree.get_actionable_node_text_at_position(&pos, content.as_bytes());
155+
let identifier = tree.get_hoverable_node_text_at_position(&pos, content.as_bytes());
155156
let current_package_name = tree.get_package_name(content.as_bytes());
156157

157158
let Some(identifier) = identifier else {
@@ -267,6 +268,43 @@ impl LanguageServer for ProtoLanguageServer {
267268
Box::pin(async move { Ok(response) })
268269
}
269270

271+
fn references(
272+
&mut self,
273+
param: ReferenceParams,
274+
) -> BoxFuture<'static, Result<Option<Vec<Location>>, ResponseError>> {
275+
let uri = param.text_document_position.text_document.uri;
276+
let pos = param.text_document_position.position;
277+
278+
let Some(tree) = self.state.get_tree(&uri) else {
279+
error!(uri=%uri, "failed to get tree");
280+
return Box::pin(async move { Ok(None) });
281+
};
282+
283+
let content = self.state.get_content(&uri);
284+
285+
let Some(current_package) = tree.get_package_name(content.as_bytes()) else {
286+
error!(uri=%uri, "failed to get package name");
287+
return Box::pin(async move { Ok(None) });
288+
};
289+
290+
let Some((mut refs, otext)) = tree.reference_tree(&pos, content.as_bytes()) else {
291+
error!(uri=%uri, "failed to find references in a tree");
292+
return Box::pin(async move { Ok(None) });
293+
};
294+
295+
if let Some(v) = self.state.reference_fields(current_package, &otext) {
296+
refs.extend(v);
297+
}
298+
299+
Box::pin(async move {
300+
if refs.is_empty() {
301+
Ok(None)
302+
} else {
303+
Ok(Some(refs))
304+
}
305+
})
306+
}
307+
270308
fn definition(
271309
&mut self,
272310
param: GotoDefinitionParams,
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
syntax = "proto3";
2+
3+
package com.parser;
4+
5+
// A Book is book
6+
message Book {
7+
8+
// This is represents author
9+
// A author is a someone who writes books
10+
//
11+
// Author has a name and a country where they were born
12+
message Author {
13+
string name = 1;
14+
string country = 2;
15+
};
16+
Author author = 1;
17+
int price_usd = 2;
18+
}
19+
20+
message BookShelf {}
21+
22+
message Library {
23+
repeated Book books = 1;
24+
Book.Author collection = 2;
25+
BookShelf shelf = 3;
26+
}
27+
28+
service Myservice {
29+
rpc GetBook(Empty) returns (Book);
30+
rpc GetAuthor(Empty) returns (Book.Author)
31+
}

src/parser/input/test_rename.proto

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@ message Book {
1717
int price_usd = 2;
1818
}
1919

20+
message BookShelf {}
21+
2022
message Library {
2123
repeated Book books = 1;
2224
Book.Author collection = 2;
25+
BookShelf shelf = 3;
2326
}
2427

2528
service Myservice {

src/parser/rename.rs

Lines changed: 97 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use async_lsp::lsp_types::{Position, Range, TextEdit};
1+
use async_lsp::lsp_types::{Location, Position, Range, TextEdit};
22
use tree_sitter::Node;
33

44
use crate::{nodekind::NodeKind, utils::ts_to_lsp_position};
@@ -21,28 +21,52 @@ impl ParsedTree {
2121
})
2222
}
2323

24-
fn rename_within(
24+
fn nodes_within<'a>(
2525
&self,
26-
n: Node<'_>,
26+
n: Node<'a>,
2727
identifier: &str,
28-
new_identifier: &str,
2928
content: impl AsRef<[u8]>,
30-
) -> Option<Vec<TextEdit>> {
29+
) -> Option<Vec<Node<'a>>> {
3130
n.parent().map(|p| {
3231
self.filter_nodes_from(p, NodeKind::is_field_name)
3332
.into_iter()
3433
.filter(|i| i.utf8_text(content.as_ref()).expect("utf-8 parse error") == identifier)
35-
.map(|i| TextEdit {
36-
range: Range {
37-
start: ts_to_lsp_position(&i.start_position()),
38-
end: ts_to_lsp_position(&i.end_position()),
39-
},
40-
new_text: new_identifier.to_string(),
41-
})
4234
.collect()
4335
})
4436
}
4537

38+
pub fn reference_tree(
39+
&self,
40+
pos: &Position,
41+
content: impl AsRef<[u8]>,
42+
) -> Option<(Vec<Location>, String)> {
43+
let rename_range = self.can_rename(pos)?;
44+
45+
let mut res = vec![Location {
46+
uri: self.uri.clone(),
47+
range: rename_range,
48+
}];
49+
50+
let nodes = self.get_ancestor_nodes_at_position(pos);
51+
let mut i = 1;
52+
let mut otext = nodes.first()?.utf8_text(content.as_ref()).ok()?.to_owned();
53+
while nodes.len() > i {
54+
let id = nodes[i].utf8_text(content.as_ref()).ok()?;
55+
if let Some(inodes) = self.nodes_within(nodes[i], &otext, content.as_ref()) {
56+
res.extend(inodes.into_iter().map(|n| Location {
57+
uri: self.uri.clone(),
58+
range: Range {
59+
start: ts_to_lsp_position(&n.start_position()),
60+
end: ts_to_lsp_position(&n.end_position()),
61+
},
62+
}))
63+
}
64+
otext = format!("{id}.{otext}");
65+
i += 1
66+
}
67+
Some((res, otext))
68+
}
69+
4670
pub fn rename_tree(
4771
&self,
4872
pos: &Position,
@@ -65,8 +89,14 @@ impl ParsedTree {
6589
while nodes.len() > i {
6690
let id = nodes[i].utf8_text(content.as_ref()).ok()?;
6791

68-
if let Some(edit) = self.rename_within(nodes[i], &otext, &ntext, content.as_ref()) {
69-
v.extend(edit);
92+
if let Some(inodes) = self.nodes_within(nodes[i], &otext, content.as_ref()) {
93+
v.extend(inodes.into_iter().map(|n| TextEdit {
94+
range: Range {
95+
start: ts_to_lsp_position(&n.start_position()),
96+
end: ts_to_lsp_position(&n.end_position()),
97+
},
98+
new_text: ntext.to_owned(),
99+
}));
70100
}
71101

72102
otext = format!("{id}.{otext}");
@@ -87,9 +117,9 @@ impl ParsedTree {
87117
self.filter_nodes(NodeKind::is_field_name)
88118
.into_iter()
89119
.filter(|n| {
90-
n.utf8_text(content.as_ref())
91-
.expect("utf-8 parse error")
92-
.starts_with(old_identifier)
120+
let ntext = n.utf8_text(content.as_ref()).expect("utf-8 parse error");
121+
let sc = format!("{old_identifier}.");
122+
return ntext == old_identifier || ntext.starts_with(&sc);
93123
})
94124
.map(|n| {
95125
let text = n.utf8_text(content.as_ref()).expect("utf-8 parse error");
@@ -103,6 +133,20 @@ impl ParsedTree {
103133
})
104134
.collect()
105135
}
136+
137+
pub fn reference_field(&self, id: &str, content: impl AsRef<[u8]>) -> Vec<Location> {
138+
self.filter_nodes(NodeKind::is_field_name)
139+
.into_iter()
140+
.filter(|n| n.utf8_text(content.as_ref()).expect("utf-8 parse error") == id)
141+
.map(|n| Location {
142+
uri: self.uri.clone(),
143+
range: Range {
144+
start: ts_to_lsp_position(&n.start_position()),
145+
end: ts_to_lsp_position(&n.end_position()),
146+
},
147+
})
148+
.collect()
149+
}
106150
}
107151

108152
#[cfg(test)]
@@ -148,6 +192,42 @@ mod test {
148192
assert_yaml_snapshot!(rename_fn("xyx", &pos_non_rename));
149193
}
150194

195+
#[test]
196+
fn test_reference() {
197+
let uri: Url = "file://foo/bar.proto".parse().unwrap();
198+
let pos_book = Position {
199+
line: 5,
200+
character: 9,
201+
};
202+
let pos_author = Position {
203+
line: 11,
204+
character: 14,
205+
};
206+
let pos_non_ref = Position {
207+
line: 21,
208+
character: 5,
209+
};
210+
let contents = include_str!("input/test_reference.proto");
211+
212+
let parsed = ProtoParser::new().parse(uri.clone(), contents);
213+
assert!(parsed.is_some());
214+
let tree = parsed.unwrap();
215+
216+
let reference_fn = |pos: &Position| {
217+
if let Some(k) = tree.reference_tree(pos, contents) {
218+
let mut v = tree.reference_field(&k.1, contents);
219+
v.extend(k.0);
220+
v
221+
} else {
222+
vec![]
223+
}
224+
};
225+
226+
assert_yaml_snapshot!(reference_fn(&pos_book));
227+
assert_yaml_snapshot!(reference_fn(&pos_author));
228+
assert_yaml_snapshot!(reference_fn(&pos_non_ref));
229+
}
230+
151231
#[test]
152232
fn test_can_rename() {
153233
let uri: Url = "file://foo/bar/test.proto".parse().unwrap();
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
source: src/parser/rename.rs
3+
expression: reference_fn(&pos_author)
4+
---
5+
- uri: "file://foo/bar.proto"
6+
range:
7+
start:
8+
line: 23
9+
character: 2
10+
end:
11+
line: 23
12+
character: 13
13+
- uri: "file://foo/bar.proto"
14+
range:
15+
start:
16+
line: 29
17+
character: 32
18+
end:
19+
line: 29
20+
character: 43
21+
- uri: "file://foo/bar.proto"
22+
range:
23+
start:
24+
line: 11
25+
character: 10
26+
end:
27+
line: 11
28+
character: 16
29+
- uri: "file://foo/bar.proto"
30+
range:
31+
start:
32+
line: 15
33+
character: 2
34+
end:
35+
line: 15
36+
character: 8
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
source: src/parser/rename.rs
3+
expression: reference_fn(&pos_non_ref)
4+
---
5+
[]
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
source: src/parser/rename.rs
3+
expression: reference_fn(&pos_book)
4+
---
5+
- uri: "file://foo/bar.proto"
6+
range:
7+
start:
8+
line: 22
9+
character: 11
10+
end:
11+
line: 22
12+
character: 15
13+
- uri: "file://foo/bar.proto"
14+
range:
15+
start:
16+
line: 28
17+
character: 30
18+
end:
19+
line: 28
20+
character: 34
21+
- uri: "file://foo/bar.proto"
22+
range:
23+
start:
24+
line: 5
25+
character: 8
26+
end:
27+
line: 5
28+
character: 12

0 commit comments

Comments
 (0)