Skip to content

Commit 45ad65d

Browse files
committed
feat(service): document highlight
1 parent 8085514 commit 45ad65d

34 files changed

+2817
-6
lines changed

crates/server/src/server.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ use lsp_types::{
99
},
1010
request::{
1111
CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare,
12-
CodeActionRequest, Completion, DocumentDiagnosticRequest, DocumentSymbolRequest,
13-
FoldingRangeRequest, Formatting, GotoDeclaration, GotoDefinition, GotoTypeDefinition,
14-
HoverRequest, InlayHintRequest, PrepareRenameRequest, RangeFormatting, References,
15-
RegisterCapability, Rename, Request as _, SelectionRangeRequest, SemanticTokensFullRequest,
16-
SemanticTokensRangeRequest, SignatureHelpRequest, WorkspaceConfiguration,
17-
WorkspaceDiagnosticRefresh,
12+
CodeActionRequest, Completion, DocumentDiagnosticRequest, DocumentHighlightRequest,
13+
DocumentSymbolRequest, FoldingRangeRequest, Formatting, GotoDeclaration, GotoDefinition,
14+
GotoTypeDefinition, HoverRequest, InlayHintRequest, PrepareRenameRequest, RangeFormatting,
15+
References, RegisterCapability, Rename, Request as _, SelectionRangeRequest,
16+
SemanticTokensFullRequest, SemanticTokensRangeRequest, SignatureHelpRequest,
17+
WorkspaceConfiguration, WorkspaceDiagnosticRefresh,
1818
},
1919
ConfigurationItem, ConfigurationParams, DidChangeConfigurationParams,
2020
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
@@ -163,6 +163,20 @@ impl Server {
163163
Err(ExtractError::MethodMismatch(r)) => req = r,
164164
Err(ExtractError::JsonError { .. }) => continue,
165165
}
166+
match cast_req::<DocumentHighlightRequest>(req) {
167+
Ok((id, params)) => {
168+
conn.sender.send(Message::Response(Response {
169+
id,
170+
result: Some(serde_json::to_value(
171+
self.service.document_highlight(params),
172+
)?),
173+
error: None,
174+
}))?;
175+
continue;
176+
}
177+
Err(ExtractError::MethodMismatch(r)) => req = r,
178+
Err(ExtractError::JsonError { .. }) => continue,
179+
}
166180
match cast_req::<FoldingRangeRequest>(req) {
167181
Ok((id, params)) => {
168182
conn.sender.send(Message::Response(Response {
Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
use crate::{
2+
binder::{SymbolItem, SymbolItemKey, SymbolItemKind, SymbolTable, SymbolTablesCtx},
3+
files::FilesCtx,
4+
helpers, LanguageService,
5+
};
6+
use line_index::LineIndex;
7+
use lsp_types::{DocumentHighlight, DocumentHighlightKind, DocumentHighlightParams};
8+
use rowan::Direction;
9+
use smallvec::SmallVec;
10+
use wat_syntax::{SyntaxElement, SyntaxKind, SyntaxNode};
11+
12+
impl LanguageService {
13+
/// Handler for `textDocument/documentHighlight` request.
14+
pub fn document_highlight(
15+
&self,
16+
params: DocumentHighlightParams,
17+
) -> Option<Vec<DocumentHighlight>> {
18+
let uri = self.uri(params.text_document_position_params.text_document.uri);
19+
let line_index = self.line_index(uri);
20+
let root = SyntaxNode::new_root(self.root(uri));
21+
let token = super::find_meaningful_token(
22+
self,
23+
uri,
24+
&root,
25+
params.text_document_position_params.position,
26+
)?;
27+
let kind = token.kind();
28+
match kind {
29+
SyntaxKind::KEYWORD
30+
| SyntaxKind::INSTR_NAME
31+
| SyntaxKind::NUM_TYPE
32+
| SyntaxKind::VEC_TYPE
33+
| SyntaxKind::REF_TYPE
34+
| SyntaxKind::HEAP_TYPE
35+
| SyntaxKind::MEM_ARG
36+
| SyntaxKind::FLOAT
37+
| SyntaxKind::SHARE => {
38+
let text = token.text();
39+
Some(
40+
root.descendants_with_tokens()
41+
.filter_map(|element| match element {
42+
SyntaxElement::Token(other)
43+
if other.kind() == kind && other.text() == text =>
44+
{
45+
Some(DocumentHighlight {
46+
range: helpers::rowan_range_to_lsp_range(
47+
&line_index,
48+
other.text_range(),
49+
),
50+
kind: Some(DocumentHighlightKind::TEXT),
51+
})
52+
}
53+
_ => None,
54+
})
55+
.collect(),
56+
)
57+
}
58+
SyntaxKind::IDENT | SyntaxKind::INT | SyntaxKind::UNSIGNED_INT => {
59+
let symbol_table = self.symbol_table(uri);
60+
let key = token.parent()?.into();
61+
let current_symbol = symbol_table
62+
.symbols
63+
.iter()
64+
.find(|symbol| symbol.key == key)?;
65+
match &current_symbol.kind {
66+
SymbolItemKind::Module => None,
67+
SymbolItemKind::Func
68+
| SymbolItemKind::Param
69+
| SymbolItemKind::Local
70+
| SymbolItemKind::Type
71+
| SymbolItemKind::GlobalDef
72+
| SymbolItemKind::MemoryDef
73+
| SymbolItemKind::TableDef => {
74+
let ref_kind = match current_symbol.kind {
75+
SymbolItemKind::Func => SymbolItemKind::Call,
76+
SymbolItemKind::Param | SymbolItemKind::Local => {
77+
SymbolItemKind::LocalRef
78+
}
79+
SymbolItemKind::Type => SymbolItemKind::TypeUse,
80+
SymbolItemKind::GlobalDef => SymbolItemKind::GlobalRef,
81+
SymbolItemKind::MemoryDef => SymbolItemKind::MemoryRef,
82+
SymbolItemKind::TableDef => SymbolItemKind::TableRef,
83+
_ => return None,
84+
};
85+
Some(
86+
symbol_table
87+
.symbols
88+
.iter()
89+
.filter(|symbol| {
90+
if symbol.kind == current_symbol.kind {
91+
current_symbol.idx == symbol.idx
92+
&& symbol.region == current_symbol.region
93+
} else if symbol.kind == ref_kind {
94+
symbol.idx.is_defined_by(&current_symbol.idx)
95+
&& symbol.region == current_symbol.region
96+
} else {
97+
false
98+
}
99+
})
100+
.filter_map(|symbol| {
101+
create_symbol_highlight(symbol, &root, &line_index)
102+
})
103+
.collect(),
104+
)
105+
}
106+
SymbolItemKind::Call
107+
| SymbolItemKind::TypeUse
108+
| SymbolItemKind::GlobalRef
109+
| SymbolItemKind::MemoryRef
110+
| SymbolItemKind::TableRef => {
111+
let def_kind = match current_symbol.kind {
112+
SymbolItemKind::Call => SymbolItemKind::Func,
113+
SymbolItemKind::TypeUse => SymbolItemKind::Type,
114+
SymbolItemKind::GlobalRef => SymbolItemKind::GlobalDef,
115+
SymbolItemKind::MemoryRef => SymbolItemKind::MemoryDef,
116+
SymbolItemKind::TableRef => SymbolItemKind::TableDef,
117+
_ => return None,
118+
};
119+
let defs = symbol_table
120+
.find_defs(&current_symbol.key)?
121+
.collect::<SmallVec<[_; 1]>>();
122+
Some(
123+
symbol_table
124+
.symbols
125+
.iter()
126+
.filter(|symbol| {
127+
if symbol.kind == def_kind {
128+
current_symbol.idx.is_defined_by(&symbol.idx)
129+
&& symbol.region == current_symbol.region
130+
} else if symbol.kind == current_symbol.kind {
131+
defs.iter().any(|func| symbol.idx.is_defined_by(&func.idx))
132+
&& symbol.region == current_symbol.region
133+
} else {
134+
false
135+
}
136+
})
137+
.filter_map(|symbol| {
138+
create_symbol_highlight(symbol, &root, &line_index)
139+
})
140+
.collect(),
141+
)
142+
}
143+
SymbolItemKind::LocalRef => {
144+
let param_or_local =
145+
symbol_table.find_param_or_local_def(&current_symbol.key)?;
146+
Some(
147+
symbol_table
148+
.symbols
149+
.iter()
150+
.filter(|symbol| match &symbol.kind {
151+
SymbolItemKind::Param | SymbolItemKind::Local => {
152+
current_symbol.idx.is_defined_by(&symbol.idx)
153+
&& symbol.region == current_symbol.region
154+
}
155+
SymbolItemKind::LocalRef => {
156+
symbol.idx.is_defined_by(&param_or_local.idx)
157+
&& symbol.region == current_symbol.region
158+
}
159+
_ => false,
160+
})
161+
.filter_map(|symbol| {
162+
create_symbol_highlight(symbol, &root, &line_index)
163+
})
164+
.collect(),
165+
)
166+
}
167+
SymbolItemKind::BlockDef => Some(create_block_highlights(
168+
&current_symbol.key,
169+
&symbol_table,
170+
&line_index,
171+
&root,
172+
)),
173+
SymbolItemKind::BlockRef => {
174+
let def_key = symbol_table.find_block_def(&key)?;
175+
Some(create_block_highlights(
176+
def_key,
177+
&symbol_table,
178+
&line_index,
179+
&root,
180+
))
181+
}
182+
}
183+
}
184+
_ => None,
185+
}
186+
}
187+
}
188+
189+
fn create_symbol_highlight(
190+
symbol: &SymbolItem,
191+
root: &SyntaxNode,
192+
line_index: &LineIndex,
193+
) -> Option<DocumentHighlight> {
194+
let node = symbol.key.ptr.to_node(root);
195+
node.children_with_tokens()
196+
.find_map(|element| match element {
197+
SyntaxElement::Token(token)
198+
if matches!(
199+
token.kind(),
200+
SyntaxKind::IDENT | SyntaxKind::INT | SyntaxKind::UNSIGNED_INT
201+
) =>
202+
{
203+
Some(DocumentHighlight {
204+
range: helpers::rowan_range_to_lsp_range(line_index, token.text_range()),
205+
kind: get_highlight_kind_of_symbol(symbol, root),
206+
})
207+
}
208+
_ => None,
209+
})
210+
}
211+
212+
fn create_block_highlights(
213+
def_key: &SymbolItemKey,
214+
symbol_table: &SymbolTable,
215+
line_index: &LineIndex,
216+
root: &SyntaxNode,
217+
) -> Vec<DocumentHighlight> {
218+
let mut highlights = Vec::with_capacity(1);
219+
if let Some(highlight) = symbol_table
220+
.symbols
221+
.iter()
222+
.find(|symbol| symbol.key == *def_key)
223+
.and_then(|def_symbol| create_symbol_highlight(def_symbol, root, line_index))
224+
{
225+
highlights.push(highlight);
226+
}
227+
highlights.extend(
228+
symbol_table
229+
.blocks
230+
.iter()
231+
.filter(|block| {
232+
block.def_key == *def_key && block.ref_idx.is_defined_by(&block.def_idx)
233+
})
234+
.map(|block| DocumentHighlight {
235+
range: helpers::rowan_range_to_lsp_range(
236+
line_index,
237+
block.ref_key.ptr.text_range(),
238+
),
239+
kind: Some(DocumentHighlightKind::READ),
240+
}),
241+
);
242+
highlights
243+
}
244+
245+
fn get_highlight_kind_of_symbol(
246+
symbol: &SymbolItem,
247+
root: &SyntaxNode,
248+
) -> Option<DocumentHighlightKind> {
249+
match symbol.kind {
250+
SymbolItemKind::Func
251+
| SymbolItemKind::Param
252+
| SymbolItemKind::Local
253+
| SymbolItemKind::Type
254+
| SymbolItemKind::GlobalDef
255+
| SymbolItemKind::MemoryDef
256+
| SymbolItemKind::TableDef
257+
| SymbolItemKind::BlockDef => Some(DocumentHighlightKind::WRITE),
258+
SymbolItemKind::Call
259+
| SymbolItemKind::TypeUse
260+
| SymbolItemKind::MemoryRef
261+
| SymbolItemKind::BlockRef => Some(DocumentHighlightKind::READ),
262+
SymbolItemKind::LocalRef | SymbolItemKind::GlobalRef | SymbolItemKind::TableRef => {
263+
let node = symbol.key.ptr.to_node(root);
264+
if node
265+
.siblings_with_tokens(Direction::Prev)
266+
.any(|element| is_write_access_instr(element, &node))
267+
{
268+
Some(DocumentHighlightKind::WRITE)
269+
} else {
270+
Some(DocumentHighlightKind::READ)
271+
}
272+
}
273+
SymbolItemKind::Module => None,
274+
}
275+
}
276+
277+
fn is_write_access_instr(element: SyntaxElement, node: &SyntaxNode) -> bool {
278+
if let SyntaxElement::Token(token) = element {
279+
if token.kind() != SyntaxKind::INSTR_NAME {
280+
return false;
281+
}
282+
let text = token.text();
283+
if text == "table.copy" {
284+
// The first operand in `table.copy` is the destination table.
285+
node.siblings_with_tokens(Direction::Prev)
286+
.skip(1)
287+
.all(|element| element.kind() != SyntaxKind::OPERAND)
288+
} else {
289+
matches!(
290+
text,
291+
"local.set"
292+
| "global.set"
293+
| "table.init"
294+
| "table.set"
295+
| "table.grow"
296+
| "table.fill"
297+
)
298+
}
299+
} else {
300+
false
301+
}
302+
}

crates/service/src/features/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod code_action;
33
mod completion;
44
mod definition;
55
mod diagnostics;
6+
mod document_highlight;
67
mod document_symbol;
78
mod folding_range;
89
mod formatting;

crates/service/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ impl LanguageService {
138138
type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)),
139139
declaration_provider: Some(DeclarationCapability::Simple(true)),
140140
document_formatting_provider: Some(OneOf::Left(true)),
141+
document_highlight_provider: Some(OneOf::Left(true)),
141142
document_range_formatting_provider: Some(OneOf::Left(true)),
142143
document_symbol_provider: Some(OneOf::Left(true)),
143144
folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),

0 commit comments

Comments
 (0)