4113: Support returning non-hierarchical symbols r=matklad a=kjeremy 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 https://github.com/microsoft/language-server-protocol/pull/538#issuecomment-442510767 for more context. Found while looking at #144 4136: add support for cfg feature attributes on expression #4063 r=matklad a=bnjjj close issue #4063 4141: Fix typo r=matklad a=Veetaha 4142: Remove unnecessary async from vscode language client creation r=matklad a=Veetaha Co-authored-by: kjeremy <kjeremy@gmail.com> Co-authored-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com> Co-authored-by: veetaha <veetaha2@gmail.com>
This commit is contained in:
commit
7021352dc2
7 changed files with 78 additions and 7 deletions
|
@ -27,7 +27,7 @@ use crate::{
|
||||||
AsMacroCall, DefWithBodyId, HasModule, Lookup, ModuleId,
|
AsMacroCall, DefWithBodyId, HasModule, Lookup, ModuleId,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A subser of Exander that only deals with cfg attributes. We only need it to
|
/// A subset of Exander that only deals with cfg attributes. We only need it to
|
||||||
/// avoid cyclic queries in crate def map during enum processing.
|
/// avoid cyclic queries in crate def map during enum processing.
|
||||||
pub(crate) struct CfgExpander {
|
pub(crate) struct CfgExpander {
|
||||||
cfg_options: CfgOptions,
|
cfg_options: CfgOptions,
|
||||||
|
|
|
@ -141,6 +141,10 @@ impl ExprCollector<'_> {
|
||||||
|
|
||||||
fn collect_expr(&mut self, expr: ast::Expr) -> ExprId {
|
fn collect_expr(&mut self, expr: ast::Expr) -> ExprId {
|
||||||
let syntax_ptr = AstPtr::new(&expr);
|
let syntax_ptr = AstPtr::new(&expr);
|
||||||
|
let attrs = self.expander.parse_attrs(&expr);
|
||||||
|
if !self.expander.is_cfg_enabled(&attrs) {
|
||||||
|
return self.missing_expr();
|
||||||
|
}
|
||||||
match expr {
|
match expr {
|
||||||
ast::Expr::IfExpr(e) => {
|
ast::Expr::IfExpr(e) => {
|
||||||
let then_branch = self.collect_block_opt(e.then_branch());
|
let then_branch = self.collect_block_opt(e.then_branch());
|
||||||
|
|
|
@ -390,6 +390,38 @@ fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
|
||||||
assert_snapshot!(diagnostics, @r###""###);
|
assert_snapshot!(diagnostics, @r###""###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_such_field_with_feature_flag_diagnostics_on_block_expr() {
|
||||||
|
let diagnostics = TestDB::with_files(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs crate:foo cfg:feature=foo
|
||||||
|
struct S {
|
||||||
|
#[cfg(feature = "foo")]
|
||||||
|
foo: u32,
|
||||||
|
#[cfg(not(feature = "foo"))]
|
||||||
|
bar: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl S {
|
||||||
|
fn new(bar: u32) -> Self {
|
||||||
|
#[cfg(feature = "foo")]
|
||||||
|
{
|
||||||
|
Self { foo: bar }
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "foo"))]
|
||||||
|
{
|
||||||
|
Self { bar }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.diagnostics()
|
||||||
|
.0;
|
||||||
|
|
||||||
|
assert_snapshot!(diagnostics, @r###""###);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn no_such_field_with_feature_flag_diagnostics_on_struct_fields() {
|
fn no_such_field_with_feature_flag_diagnostics_on_struct_fields() {
|
||||||
let diagnostics = TestDB::with_files(
|
let diagnostics = TestDB::with_files(
|
||||||
|
|
|
@ -69,6 +69,7 @@ pub enum RustfmtConfig {
|
||||||
pub struct ClientCapsConfig {
|
pub struct ClientCapsConfig {
|
||||||
pub location_link: bool,
|
pub location_link: bool,
|
||||||
pub line_folding_only: bool,
|
pub line_folding_only: bool,
|
||||||
|
pub hierarchical_symbols: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
|
@ -215,6 +216,11 @@ impl Config {
|
||||||
if let Some(value) = caps.folding_range.as_ref().and_then(|it| it.line_folding_only) {
|
if let Some(value) = caps.folding_range.as_ref().and_then(|it| it.line_folding_only) {
|
||||||
self.client_caps.line_folding_only = value
|
self.client_caps.line_folding_only = value
|
||||||
}
|
}
|
||||||
|
if let Some(value) =
|
||||||
|
caps.document_symbol.as_ref().and_then(|it| it.hierarchical_document_symbol_support)
|
||||||
|
{
|
||||||
|
self.client_caps.hierarchical_symbols = value
|
||||||
|
}
|
||||||
self.completion.allow_snippets(false);
|
self.completion.allow_snippets(false);
|
||||||
if let Some(completion) = &caps.completion {
|
if let Some(completion) = &caps.completion {
|
||||||
if let Some(completion_item) = &completion.completion_item {
|
if let Some(completion_item) = &completion.completion_item {
|
||||||
|
|
|
@ -16,7 +16,7 @@ use lsp_types::{
|
||||||
Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse,
|
Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse,
|
||||||
Range, RenameParams, SemanticTokensParams, SemanticTokensRangeParams,
|
Range, RenameParams, SemanticTokensParams, SemanticTokensRangeParams,
|
||||||
SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, TextDocumentIdentifier,
|
SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, TextDocumentIdentifier,
|
||||||
TextEdit, WorkspaceEdit,
|
TextEdit, Url, WorkspaceEdit,
|
||||||
};
|
};
|
||||||
use ra_ide::{
|
use ra_ide::{
|
||||||
Assist, AssistId, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind,
|
Assist, AssistId, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind,
|
||||||
|
@ -219,6 +219,7 @@ pub fn handle_document_symbol(
|
||||||
let _p = profile("handle_document_symbol");
|
let _p = profile("handle_document_symbol");
|
||||||
let file_id = params.text_document.try_conv_with(&world)?;
|
let file_id = params.text_document.try_conv_with(&world)?;
|
||||||
let line_index = world.analysis().file_line_index(file_id)?;
|
let line_index = world.analysis().file_line_index(file_id)?;
|
||||||
|
let url = file_id.try_conv_with(&world)?;
|
||||||
|
|
||||||
let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
|
let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
|
||||||
|
|
||||||
|
@ -234,10 +235,10 @@ pub fn handle_document_symbol(
|
||||||
};
|
};
|
||||||
parents.push((doc_symbol, symbol.parent));
|
parents.push((doc_symbol, symbol.parent));
|
||||||
}
|
}
|
||||||
let mut res = Vec::new();
|
let mut document_symbols = Vec::new();
|
||||||
while let Some((node, parent)) = parents.pop() {
|
while let Some((node, parent)) = parents.pop() {
|
||||||
match parent {
|
match parent {
|
||||||
None => res.push(node),
|
None => document_symbols.push(node),
|
||||||
Some(i) => {
|
Some(i) => {
|
||||||
let children = &mut parents[i].0.children;
|
let children = &mut parents[i].0.children;
|
||||||
if children.is_none() {
|
if children.is_none() {
|
||||||
|
@ -248,7 +249,35 @@ pub fn handle_document_symbol(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Some(res.into()))
|
if world.config.client_caps.hierarchical_symbols {
|
||||||
|
Ok(Some(document_symbols.into()))
|
||||||
|
} else {
|
||||||
|
let mut symbol_information = Vec::<SymbolInformation>::new();
|
||||||
|
for symbol in document_symbols {
|
||||||
|
flatten_document_symbol(&symbol, None, &url, &mut symbol_information);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(symbol_information.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flatten_document_symbol(
|
||||||
|
symbol: &DocumentSymbol,
|
||||||
|
container_name: Option<String>,
|
||||||
|
url: &Url,
|
||||||
|
res: &mut Vec<SymbolInformation>,
|
||||||
|
) {
|
||||||
|
res.push(SymbolInformation {
|
||||||
|
name: symbol.name.clone(),
|
||||||
|
kind: symbol.kind,
|
||||||
|
deprecated: symbol.deprecated,
|
||||||
|
location: Location::new(url.clone(), symbol.range),
|
||||||
|
container_name: container_name,
|
||||||
|
});
|
||||||
|
|
||||||
|
for child in symbol.children.iter().flatten() {
|
||||||
|
flatten_document_symbol(child, Some(symbol.name.clone()), url, res);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_workspace_symbol(
|
pub fn handle_workspace_symbol(
|
||||||
|
|
|
@ -4,7 +4,7 @@ import * as vscode from 'vscode';
|
||||||
import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed';
|
import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed';
|
||||||
import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed';
|
import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed';
|
||||||
|
|
||||||
export async function createClient(serverPath: string, cwd: string): Promise<lc.LanguageClient> {
|
export function createClient(serverPath: string, cwd: string): lc.LanguageClient {
|
||||||
// '.' Is the fallback if no folder is open
|
// '.' Is the fallback if no folder is open
|
||||||
// TODO?: Workspace folders support Uri's (eg: file://test.txt).
|
// TODO?: Workspace folders support Uri's (eg: file://test.txt).
|
||||||
// It might be a good idea to test if the uri points to a file.
|
// It might be a good idea to test if the uri points to a file.
|
||||||
|
|
|
@ -21,7 +21,7 @@ export class Ctx {
|
||||||
serverPath: string,
|
serverPath: string,
|
||||||
cwd: string,
|
cwd: string,
|
||||||
): Promise<Ctx> {
|
): Promise<Ctx> {
|
||||||
const client = await createClient(serverPath, cwd);
|
const client = createClient(serverPath, cwd);
|
||||||
const res = new Ctx(config, extCtx, client, serverPath);
|
const res = new Ctx(config, extCtx, client, serverPath);
|
||||||
res.pushCleanup(client.start());
|
res.pushCleanup(client.start());
|
||||||
await client.onReady();
|
await client.onReady();
|
||||||
|
|
Loading…
Add table
Reference in a new issue