Skip to content

Commit af4a605

Browse files
committed
Support returning non-hierarchical symbols
If `hierarchicalDocumentSymbolSupport` is not true in the client capabilites then it does not support the `DocumentSymbol[]` return type from the `textDocument/documentSymbol` request and we must fall back to `SymbolInformation[]`. This is one of the few requests that use the client capabilities to differentiate between return types and could cause problems for clients. See microsoft/language-server-protocol#538 (comment) for more context. Found while looking at #144
1 parent 189ac4a commit af4a605

File tree

3 files changed

+87
-40
lines changed

3 files changed

+87
-40
lines changed

crates/rust-analyzer/src/config.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ pub enum RustfmtConfig {
6969
pub struct ClientCapsConfig {
7070
pub location_link: bool,
7171
pub line_folding_only: bool,
72+
pub hierarchical_symbols: bool,
7273
}
7374

7475
impl Default for Config {
@@ -214,5 +215,10 @@ impl Config {
214215
if let Some(value) = caps.folding_range.as_ref().and_then(|it| it.line_folding_only) {
215216
self.client_caps.line_folding_only = value
216217
}
218+
if let Some(value) =
219+
caps.document_symbol.as_ref().and_then(|it| it.hierarchical_document_symbol_support)
220+
{
221+
self.client_caps.hierarchical_symbols = value
222+
}
217223
}
218224
}

crates/rust-analyzer/src/conv.rs

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@
22
//! and LSP types.
33
44
use lsp_types::{
5-
self, CreateFile, DiagnosticSeverity, DocumentChangeOperation, DocumentChanges, Documentation,
6-
Location, LocationLink, MarkupContent, MarkupKind, ParameterInformation, ParameterLabel,
7-
Position, Range, RenameFile, ResourceOp, SemanticTokenModifier, SemanticTokenType,
8-
SignatureInformation, SymbolKind, TextDocumentEdit, TextDocumentIdentifier, TextDocumentItem,
9-
TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier, WorkspaceEdit,
5+
self, CreateFile, DiagnosticSeverity, DocumentChangeOperation, DocumentChanges, DocumentSymbol,
6+
Documentation, Location, LocationLink, MarkupContent, MarkupKind, ParameterInformation,
7+
ParameterLabel, Position, Range, RenameFile, ResourceOp, SemanticTokenModifier,
8+
SemanticTokenType, SignatureInformation, SymbolInformation, SymbolKind, TextDocumentEdit,
9+
TextDocumentIdentifier, TextDocumentItem, TextDocumentPositionParams, Url,
10+
VersionedTextDocumentIdentifier, WorkspaceEdit,
1011
};
1112
use ra_ide::{
1213
translate_offset_with_edit, CompletionItem, CompletionItemKind, FileId, FilePosition,
1314
FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HighlightModifier, HighlightTag,
1415
InlayHint, InlayKind, InsertTextFormat, LineCol, LineIndex, NavigationTarget, RangeInfo,
15-
ReferenceAccess, Severity, SourceChange, SourceFileEdit,
16+
ReferenceAccess, Severity, SourceChange, SourceFileEdit, StructureNode,
1617
};
1718
use ra_syntax::{SyntaxKind, TextRange, TextUnit};
1819
use ra_text_edit::{AtomTextEdit, TextEdit};
@@ -325,6 +326,75 @@ impl ConvWith<&FoldConvCtx<'_>> for Fold {
325326
}
326327
}
327328

329+
impl ConvWith<(&FileId, &WorldSnapshot, &LineIndex)> for Vec<StructureNode> {
330+
type Output = lsp_types::DocumentSymbolResponse;
331+
332+
fn conv_with(self, ctx: (&FileId, &WorldSnapshot, &LineIndex)) -> Self::Output {
333+
let file_id = ctx.0;
334+
let world = ctx.1;
335+
let line_index = ctx.2;
336+
let supports_hierarchy = world.config.client_caps.hierarchical_symbols;
337+
338+
if supports_hierarchy {
339+
let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
340+
341+
for symbol in self.into_iter() {
342+
let doc_symbol = DocumentSymbol {
343+
name: symbol.label,
344+
detail: symbol.detail,
345+
kind: symbol.kind.conv(),
346+
deprecated: Some(symbol.deprecated),
347+
range: symbol.node_range.conv_with(line_index),
348+
selection_range: symbol.navigation_range.conv_with(line_index),
349+
children: None,
350+
};
351+
parents.push((doc_symbol, symbol.parent));
352+
}
353+
let mut res = Vec::new();
354+
while let Some((node, parent)) = parents.pop() {
355+
match parent {
356+
None => res.push(node),
357+
Some(i) => {
358+
let children = &mut parents[i].0.children;
359+
if children.is_none() {
360+
*children = Some(Vec::new());
361+
}
362+
children.as_mut().unwrap().push(node);
363+
}
364+
}
365+
}
366+
res.into()
367+
} else {
368+
let mut parents: Vec<(SymbolInformation, Option<usize>)> = Vec::new();
369+
for symbol in self.into_iter() {
370+
parents.push((
371+
SymbolInformation {
372+
name: symbol.label,
373+
kind: symbol.kind.conv(),
374+
deprecated: Some(symbol.deprecated),
375+
location: to_location(*file_id, symbol.navigation_range, world, line_index)
376+
.unwrap(),
377+
container_name: None,
378+
},
379+
symbol.parent,
380+
))
381+
}
382+
let mut res = Vec::new();
383+
while let Some((mut node, parent)) = parents.pop() {
384+
match parent {
385+
None => res.push(node),
386+
Some(i) => {
387+
node.container_name = Some(parents[i].0.name.clone());
388+
res.push(node)
389+
}
390+
}
391+
}
392+
393+
res.into()
394+
}
395+
}
396+
}
397+
328398
impl ConvWith<&LineIndex> for InlayHint {
329399
type Output = req::InlayHint;
330400
fn conv_with(self, line_index: &LineIndex) -> Self::Output {

crates/rust-analyzer/src/main_loop/handlers.rs

Lines changed: 5 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@ use lsp_types::{
1212
CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem,
1313
CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
1414
CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic,
15-
DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams,
16-
Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse,
17-
Range, RenameParams, SemanticTokensParams, SemanticTokensRangeParams,
18-
SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, TextDocumentIdentifier,
19-
TextEdit, WorkspaceEdit,
15+
DocumentFormattingParams, DocumentHighlight, FoldingRange, FoldingRangeParams, Hover,
16+
HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse, Range,
17+
RenameParams, SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult,
18+
SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit,
2019
};
2120
use ra_ide::{
2221
Assist, AssistId, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind,
@@ -220,35 +219,7 @@ pub fn handle_document_symbol(
220219
let file_id = params.text_document.try_conv_with(&world)?;
221220
let line_index = world.analysis().file_line_index(file_id)?;
222221

223-
let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
224-
225-
for symbol in world.analysis().file_structure(file_id)? {
226-
let doc_symbol = DocumentSymbol {
227-
name: symbol.label,
228-
detail: symbol.detail,
229-
kind: symbol.kind.conv(),
230-
deprecated: Some(symbol.deprecated),
231-
range: symbol.node_range.conv_with(&line_index),
232-
selection_range: symbol.navigation_range.conv_with(&line_index),
233-
children: None,
234-
};
235-
parents.push((doc_symbol, symbol.parent));
236-
}
237-
let mut res = Vec::new();
238-
while let Some((node, parent)) = parents.pop() {
239-
match parent {
240-
None => res.push(node),
241-
Some(i) => {
242-
let children = &mut parents[i].0.children;
243-
if children.is_none() {
244-
*children = Some(Vec::new());
245-
}
246-
children.as_mut().unwrap().push(node);
247-
}
248-
}
249-
}
250-
251-
Ok(Some(res.into()))
222+
Ok(Some(world.analysis().file_structure(file_id)?.conv_with((&file_id, &world, &line_index))))
252223
}
253224

254225
pub fn handle_workspace_symbol(

0 commit comments

Comments
 (0)