1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use crate::{
SemanticRequest,
prelude::*,
syntax::{LexicalHierarchy, LexicalScopeKind, get_lexical_hierarchy},
};
/// The [`workspace/symbol`] request is sent from the client to the server to
/// list project-wide symbols matching the given query string.
///
/// [`workspace/symbol`]: https://microsoft.github.io/language-server-protocol/specification#workspace_symbol
///
/// # Compatibility
///
/// Since 3.17.0, servers can also provider a handler for
/// [`workspaceSymbol/resolve`] requests. This allows servers to return
/// workspace symbols without a range for a `workspace/symbol` request. Clients
/// then need to resolve the range when necessary using the `workspaceSymbol/
/// resolve` request.
///
/// // [`workspaceSymbol/resolve`]: Self::symbol_resolve
///
/// Servers can only use this new model if clients advertise support for it via
/// the `workspace.symbol.resolve_support` capability.
#[derive(Debug, Clone)]
pub struct SymbolRequest {
/// The query string to filter symbols by. It is usually the exact content
/// of the user's input box in the UI.
pub pattern: Option<String>,
}
impl SemanticRequest for SymbolRequest {
type Response = Vec<SymbolInformation>;
fn request(self, ctx: &mut LocalContext) -> Option<Self::Response> {
let mut symbols = vec![];
for id in ctx.depended_files() {
let Ok(source) = ctx.source_by_id(id) else {
continue;
};
let uri = ctx.uri_for_id(id).unwrap();
let res = get_lexical_hierarchy(&source, LexicalScopeKind::Symbol).map(|symbols| {
filter_document_symbols(
&symbols,
self.pattern.as_deref(),
&source,
&uri,
ctx.position_encoding(),
)
});
if let Some(mut res) = res {
symbols.append(&mut res)
}
}
Some(symbols)
}
}
#[allow(deprecated)]
fn filter_document_symbols(
hierarchy: &[LexicalHierarchy],
query_string: Option<&str>,
source: &Source,
uri: &Url,
position_encoding: PositionEncoding,
) -> Vec<SymbolInformation> {
hierarchy
.iter()
.flat_map(|hierarchy| {
[hierarchy]
.into_iter()
.chain(hierarchy.children.as_deref().into_iter().flatten())
})
.filter(|hierarchy| hierarchy.info.kind.is_valid_lsp_symbol())
.flat_map(|hierarchy| {
if query_string.is_some_and(|s| !hierarchy.info.name.contains(s)) {
return None;
}
let rng = to_lsp_range(hierarchy.info.range.clone(), source, position_encoding);
Some(SymbolInformation {
name: hierarchy.info.name.to_string(),
kind: hierarchy.info.kind.clone().into(),
tags: None,
deprecated: None,
location: LspLocation {
uri: uri.clone(),
range: rng,
},
container_name: None,
})
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::syntax::find_module_level_docs;
use crate::tests::*;
#[test]
fn test() {
// need to compile the doc to get the dependencies
let opts = Opts { need_compile: true };
snapshot_testing_with("symbols", opts, &|ctx, path| {
let source = ctx.source_by_path(&path).unwrap();
let docs = find_module_level_docs(&source).unwrap_or_default();
let properties = get_test_properties(&docs);
let request = SymbolRequest {
pattern: properties.get("pattern").copied().map(str::to_owned),
};
let mut result = request.request(ctx);
if let Some(result) = &mut result {
// Sort the symbols by name for consistent output
result.sort_by(|x, y| {
x.name
.cmp(&y.name)
.then_with(|| x.location.uri.cmp(&y.location.uri))
});
}
assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
});
}
}