Skip to content

Commit ad7ba22

Browse files
Copilotcoder3101
andcommitted
Add workspace symbols support to protols LSP server
- Add WorkspaceSymbolRequest handler registration in server.rs - Implement workspace_symbol method in lsp.rs to handle workspace symbol requests - Add workspace_symbol_provider capability in initialize response - Create find_workspace_symbols method in state.rs to collect and filter symbols from all parsed trees - Add comprehensive test for workspace symbols functionality with snapshots - Sort symbols by name and URI for consistent ordering Co-authored-by: coder3101 <[email protected]>
1 parent ce10722 commit ad7ba22

8 files changed

+316
-2
lines changed

src/lsp.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use async_lsp::lsp_types::{
1414
RenameParams, ServerCapabilities, ServerInfo, TextDocumentPositionParams,
1515
TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit, Url, WorkspaceEdit,
1616
WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities,
17-
WorkspaceServerCapabilities,
17+
WorkspaceServerCapabilities, WorkspaceSymbolParams, WorkspaceSymbolResponse,
1818
};
1919
use async_lsp::{LanguageClient, ResponseError};
2020
use futures::future::BoxFuture;
@@ -113,6 +113,7 @@ impl ProtoLanguageServer {
113113
definition_provider: Some(OneOf::Left(true)),
114114
hover_provider: Some(HoverProviderCapability::Simple(true)),
115115
document_symbol_provider: Some(OneOf::Left(true)),
116+
workspace_symbol_provider: Some(OneOf::Left(true)),
116117
completion_provider: Some(CompletionOptions::default()),
117118
rename_provider: Some(rename_provider),
118119
document_formatting_provider: Some(OneOf::Left(true)),
@@ -379,6 +380,23 @@ impl ProtoLanguageServer {
379380
Box::pin(async move { Ok(Some(response)) })
380381
}
381382

383+
pub(super) fn workspace_symbol(
384+
&mut self,
385+
params: WorkspaceSymbolParams,
386+
) -> BoxFuture<'static, Result<Option<WorkspaceSymbolResponse>, ResponseError>> {
387+
let query = params.query.to_lowercase();
388+
389+
let symbols = self.state.find_workspace_symbols(&query);
390+
391+
Box::pin(async move {
392+
if symbols.is_empty() {
393+
Ok(None)
394+
} else {
395+
Ok(Some(WorkspaceSymbolResponse::Nested(symbols)))
396+
}
397+
})
398+
}
399+
382400
pub(super) fn formatting(
383401
&mut self,
384402
params: DocumentFormattingParams,

src/server.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use async_lsp::{
99
request::{
1010
Completion, DocumentSymbolRequest, Formatting, GotoDefinition, HoverRequest,
1111
Initialize, PrepareRenameRequest, RangeFormatting, References, Rename,
12+
WorkspaceSymbolRequest,
1213
},
1314
},
1415
router::Router,
@@ -59,6 +60,7 @@ impl ProtoLanguageServer {
5960
router.request::<References, _>(|st, params| st.references(params));
6061
router.request::<GotoDefinition, _>(|st, params| st.definition(params));
6162
router.request::<DocumentSymbolRequest, _>(|st, params| st.document_symbol(params));
63+
router.request::<WorkspaceSymbolRequest, _>(|st, params| st.workspace_symbol(params));
6264
router.request::<Formatting, _>(|st, params| st.formatting(params));
6365
router.request::<RangeFormatting, _>(|st, params| st.range_formatting(params));
6466

src/state.rs

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ use std::{
66
use tracing::info;
77

88
use async_lsp::lsp_types::ProgressParamsValue;
9-
use async_lsp::lsp_types::{CompletionItem, CompletionItemKind, PublishDiagnosticsParams, Url};
9+
use async_lsp::lsp_types::{
10+
CompletionItem, CompletionItemKind, Location, OneOf, PublishDiagnosticsParams, Url,
11+
WorkspaceSymbol,
12+
};
1013
use std::sync::mpsc::Sender;
1114
use tree_sitter::Node;
1215
use walkdir::WalkDir;
@@ -73,6 +76,73 @@ impl ProtoLanguageState {
7376
.collect()
7477
}
7578

79+
pub fn find_workspace_symbols(&self, query: &str) -> Vec<WorkspaceSymbol> {
80+
let mut symbols = Vec::new();
81+
82+
for tree in self.get_trees() {
83+
let content = self.get_content(&tree.uri);
84+
let doc_symbols = tree.find_document_locations(content.as_bytes());
85+
86+
for doc_symbol in doc_symbols {
87+
self.collect_workspace_symbols(&doc_symbol, &tree.uri, query, None, &mut symbols);
88+
}
89+
}
90+
91+
// Sort symbols by name and then by URI for consistent ordering
92+
symbols.sort_by(|a, b| {
93+
let name_cmp = a.name.cmp(&b.name);
94+
if name_cmp != std::cmp::Ordering::Equal {
95+
return name_cmp;
96+
}
97+
// Extract URI from location
98+
match (&a.location, &b.location) {
99+
(OneOf::Left(loc_a), OneOf::Left(loc_b)) => {
100+
loc_a.uri.as_str().cmp(loc_b.uri.as_str())
101+
}
102+
_ => std::cmp::Ordering::Equal,
103+
}
104+
});
105+
106+
symbols
107+
}
108+
109+
fn collect_workspace_symbols(
110+
&self,
111+
doc_symbol: &async_lsp::lsp_types::DocumentSymbol,
112+
uri: &Url,
113+
query: &str,
114+
container_name: Option<String>,
115+
symbols: &mut Vec<WorkspaceSymbol>,
116+
) {
117+
let symbol_name_lower = doc_symbol.name.to_lowercase();
118+
119+
if query.is_empty() || symbol_name_lower.contains(query) {
120+
symbols.push(WorkspaceSymbol {
121+
name: doc_symbol.name.clone(),
122+
kind: doc_symbol.kind,
123+
tags: doc_symbol.tags.clone(),
124+
container_name: container_name.clone(),
125+
location: OneOf::Left(Location {
126+
uri: uri.clone(),
127+
range: doc_symbol.range,
128+
}),
129+
data: None,
130+
});
131+
}
132+
133+
if let Some(children) = &doc_symbol.children {
134+
for child in children {
135+
self.collect_workspace_symbols(
136+
child,
137+
uri,
138+
query,
139+
Some(doc_symbol.name.clone()),
140+
symbols,
141+
);
142+
}
143+
}
144+
}
145+
76146
fn upsert_content_impl(
77147
&mut self,
78148
uri: &Url,

src/workspace/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
mod definition;
22
mod hover;
33
mod rename;
4+
mod workspace_symbol;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
source: src/workspace/workspace_symbol.rs
3+
expression: address_symbols
4+
---
5+
- name: Address
6+
kind: 23
7+
containerName: Author
8+
location:
9+
uri: "file:///home/runner/work/protols/protols/src/workspace/input/b.proto"
10+
range:
11+
start:
12+
line: 9
13+
character: 3
14+
end:
15+
line: 11
16+
character: 4
17+
- name: Address
18+
kind: 23
19+
containerName: Author
20+
location:
21+
uri: "file://input/b.proto"
22+
range:
23+
start:
24+
line: 9
25+
character: 3
26+
end:
27+
line: 11
28+
character: 4
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
---
2+
source: src/workspace/workspace_symbol.rs
3+
expression: all_symbols
4+
---
5+
- name: Address
6+
kind: 23
7+
containerName: Author
8+
location:
9+
uri: "file:///home/runner/work/protols/protols/src/workspace/input/b.proto"
10+
range:
11+
start:
12+
line: 9
13+
character: 3
14+
end:
15+
line: 11
16+
character: 4
17+
- name: Address
18+
kind: 23
19+
containerName: Author
20+
location:
21+
uri: "file://input/b.proto"
22+
range:
23+
start:
24+
line: 9
25+
character: 3
26+
end:
27+
line: 11
28+
character: 4
29+
- name: Author
30+
kind: 23
31+
location:
32+
uri: "file:///home/runner/work/protols/protols/src/workspace/input/b.proto"
33+
range:
34+
start:
35+
line: 5
36+
character: 0
37+
end:
38+
line: 14
39+
character: 1
40+
- name: Author
41+
kind: 23
42+
location:
43+
uri: "file://input/b.proto"
44+
range:
45+
start:
46+
line: 5
47+
character: 0
48+
end:
49+
line: 14
50+
character: 1
51+
- name: Baz
52+
kind: 23
53+
containerName: Foobar
54+
location:
55+
uri: "file:///home/runner/work/protols/protols/src/workspace/input/c.proto"
56+
range:
57+
start:
58+
line: 8
59+
character: 3
60+
end:
61+
line: 10
62+
character: 4
63+
- name: Baz
64+
kind: 23
65+
containerName: Foobar
66+
location:
67+
uri: "file://input/c.proto"
68+
range:
69+
start:
70+
line: 8
71+
character: 3
72+
end:
73+
line: 10
74+
character: 4
75+
- name: Book
76+
kind: 23
77+
location:
78+
uri: "file://input/a.proto"
79+
range:
80+
start:
81+
line: 9
82+
character: 0
83+
end:
84+
line: 14
85+
character: 1
86+
- name: Foobar
87+
kind: 23
88+
location:
89+
uri: "file:///home/runner/work/protols/protols/src/workspace/input/c.proto"
90+
range:
91+
start:
92+
line: 5
93+
character: 0
94+
end:
95+
line: 13
96+
character: 1
97+
- name: Foobar
98+
kind: 23
99+
location:
100+
uri: "file://input/c.proto"
101+
range:
102+
start:
103+
line: 5
104+
character: 0
105+
end:
106+
line: 13
107+
character: 1
108+
- name: SomeSecret
109+
kind: 23
110+
location:
111+
uri: "file:///home/runner/work/protols/protols/src/workspace/input/inner/secret/y.proto"
112+
range:
113+
start:
114+
line: 5
115+
character: 0
116+
end:
117+
line: 7
118+
character: 1
119+
- name: Why
120+
kind: 23
121+
location:
122+
uri: "file:///home/runner/work/protols/protols/src/workspace/input/inner/x.proto"
123+
range:
124+
start:
125+
line: 7
126+
character: 0
127+
end:
128+
line: 11
129+
character: 1
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
source: src/workspace/workspace_symbol.rs
3+
expression: author_symbols
4+
---
5+
- name: Author
6+
kind: 23
7+
location:
8+
uri: "file:///home/runner/work/protols/protols/src/workspace/input/b.proto"
9+
range:
10+
start:
11+
line: 5
12+
character: 0
13+
end:
14+
line: 14
15+
character: 1
16+
- name: Author
17+
kind: 23
18+
location:
19+
uri: "file://input/b.proto"
20+
range:
21+
start:
22+
line: 5
23+
character: 0
24+
end:
25+
line: 14
26+
character: 1

src/workspace/workspace_symbol.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#[cfg(test)]
2+
mod test {
3+
use insta::assert_yaml_snapshot;
4+
5+
use crate::config::Config;
6+
use crate::state::ProtoLanguageState;
7+
8+
#[test]
9+
fn test_workspace_symbols() {
10+
let ipath = vec![std::env::current_dir().unwrap().join("src/workspace/input")];
11+
let a_uri = "file://input/a.proto".parse().unwrap();
12+
let b_uri = "file://input/b.proto".parse().unwrap();
13+
let c_uri = "file://input/c.proto".parse().unwrap();
14+
15+
let a = include_str!("input/a.proto");
16+
let b = include_str!("input/b.proto");
17+
let c = include_str!("input/c.proto");
18+
19+
let mut state: ProtoLanguageState = ProtoLanguageState::new();
20+
state.upsert_file(&a_uri, a.to_owned(), &ipath, 3, &Config::default(), false);
21+
state.upsert_file(&b_uri, b.to_owned(), &ipath, 2, &Config::default(), false);
22+
state.upsert_file(&c_uri, c.to_owned(), &ipath, 2, &Config::default(), false);
23+
24+
// Test empty query - should return all symbols
25+
let all_symbols = state.find_workspace_symbols("");
26+
assert_yaml_snapshot!("all_symbols", all_symbols);
27+
28+
// Test query for "author" - should match Author and Address
29+
let author_symbols = state.find_workspace_symbols("author");
30+
assert_yaml_snapshot!("author_symbols", author_symbols);
31+
32+
// Test query for "address" - should match Address
33+
let address_symbols = state.find_workspace_symbols("address");
34+
assert_yaml_snapshot!("address_symbols", address_symbols);
35+
36+
// Test query that should not match anything
37+
let no_match = state.find_workspace_symbols("nonexistent");
38+
assert!(no_match.is_empty());
39+
}
40+
}

0 commit comments

Comments
 (0)