rust-analyzer/crates/ra_lsp_server/src/main_loop/handlers.rs
Aleksey Kladov b0a4765059 Revert "Revert "Support LSP 3.15""
This reverts commit 1e0bf205ef.
2019-12-11 18:34:01 +01:00

908 lines
31 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! FIXME: write short doc here
use std::{fmt::Write as _, io::Write as _};
use lsp_server::ErrorCode;
use lsp_types::{
CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic,
DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams,
Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse,
Range, RenameParams, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit,
};
use ra_ide::{
AssistId, FileId, FilePosition, FileRange, Query, Runnable, RunnableKind, SearchScope,
};
use ra_prof::profile;
use ra_syntax::{AstNode, SyntaxKind, TextRange, TextUnit};
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use serde_json::to_value;
use crate::{
cargo_target_spec::{runnable_args, CargoTargetSpec},
conv::{to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith, TryConvWithToVec},
req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind},
world::WorldSnapshot,
LspError, Result,
};
pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> {
let _p = profile("handle_analyzer_status");
let mut buf = world.status();
writeln!(buf, "\n\nrequests:").unwrap();
let requests = world.latest_requests.read();
for (is_last, r) in requests.iter() {
let mark = if is_last { "*" } else { " " };
writeln!(buf, "{}{:4} {:<36}{}ms", mark, r.id, r.method, r.duration.as_millis()).unwrap();
}
Ok(buf)
}
pub fn handle_syntax_tree(world: WorldSnapshot, params: req::SyntaxTreeParams) -> Result<String> {
let _p = profile("handle_syntax_tree");
let id = params.text_document.try_conv_with(&world)?;
let line_index = world.analysis().file_line_index(id)?;
let text_range = params.range.map(|p| p.conv_with(&line_index));
let res = world.analysis().syntax_tree(id, text_range)?;
Ok(res)
}
pub fn handle_expand_macro(
world: WorldSnapshot,
params: req::ExpandMacroParams,
) -> Result<Option<req::ExpandedMacro>> {
let _p = profile("handle_expand_macro");
let file_id = params.text_document.try_conv_with(&world)?;
let line_index = world.analysis().file_line_index(file_id)?;
let offset = params.position.map(|p| p.conv_with(&line_index));
match offset {
None => Ok(None),
Some(offset) => {
let res = world.analysis().expand_macro(FilePosition { file_id, offset })?;
Ok(res.map(|it| req::ExpandedMacro { name: it.name, expansion: it.expansion }))
}
}
}
pub fn handle_selection_range(
world: WorldSnapshot,
params: req::SelectionRangeParams,
) -> Result<Vec<req::SelectionRange>> {
let _p = profile("handle_selection_range");
let file_id = params.text_document.try_conv_with(&world)?;
let line_index = world.analysis().file_line_index(file_id)?;
params
.positions
.into_iter()
.map_conv_with(&line_index)
.map(|position| {
let mut ranges = Vec::new();
{
let mut range = TextRange::from_to(position, position);
loop {
ranges.push(range);
let frange = FileRange { file_id, range };
let next = world.analysis().extend_selection(frange)?;
if next == range {
break;
} else {
range = next
}
}
}
let mut range = req::SelectionRange {
range: ranges.last().unwrap().conv_with(&line_index),
parent: None,
};
for r in ranges.iter().rev().skip(1) {
range = req::SelectionRange {
range: r.conv_with(&line_index),
parent: Some(Box::new(range)),
}
}
Ok(range)
})
.collect()
}
pub fn handle_find_matching_brace(
world: WorldSnapshot,
params: req::FindMatchingBraceParams,
) -> Result<Vec<Position>> {
let _p = profile("handle_find_matching_brace");
let file_id = params.text_document.try_conv_with(&world)?;
let line_index = world.analysis().file_line_index(file_id)?;
let res = params
.offsets
.into_iter()
.map_conv_with(&line_index)
.map(|offset| {
if let Ok(Some(matching_brace_offset)) =
world.analysis().matching_brace(FilePosition { file_id, offset })
{
matching_brace_offset
} else {
offset
}
})
.map_conv_with(&line_index)
.collect();
Ok(res)
}
pub fn handle_join_lines(
world: WorldSnapshot,
params: req::JoinLinesParams,
) -> Result<req::SourceChange> {
let _p = profile("handle_join_lines");
let frange = (&params.text_document, params.range).try_conv_with(&world)?;
world.analysis().join_lines(frange)?.try_conv_with(&world)
}
pub fn handle_on_enter(
world: WorldSnapshot,
params: req::TextDocumentPositionParams,
) -> Result<Option<req::SourceChange>> {
let _p = profile("handle_on_enter");
let position = params.try_conv_with(&world)?;
match world.analysis().on_enter(position)? {
None => Ok(None),
Some(edit) => Ok(Some(edit.try_conv_with(&world)?)),
}
}
// Don't forget to add new trigger characters to `ServerCapabilities` in `caps.rs`.
pub fn handle_on_type_formatting(
world: WorldSnapshot,
params: req::DocumentOnTypeFormattingParams,
) -> Result<Option<Vec<TextEdit>>> {
let _p = profile("handle_on_type_formatting");
let mut position = params.text_document_position.try_conv_with(&world)?;
let line_index = world.analysis().file_line_index(position.file_id)?;
let line_endings = world.file_line_endings(position.file_id);
// in `ra_ide`, the `on_type` invariant is that
// `text.char_at(position) == typed_char`.
position.offset = position.offset - TextUnit::of_char('.');
let char_typed = params.ch.chars().next().unwrap_or('\0');
// We have an assist that inserts ` ` after typing `->` in `fn foo() ->{`,
// but it requires precise cursor positioning to work, and one can't
// position the cursor with on_type formatting. So, let's just toggle this
// feature off here, hoping that we'll enable it one day, 😿.
if char_typed == '>' {
return Ok(None);
}
let edit = world.analysis().on_char_typed(position, char_typed)?;
let mut edit = match edit {
Some(it) => it,
None => return Ok(None),
};
// This should be a single-file edit
let edit = edit.source_file_edits.pop().unwrap();
let change: Vec<TextEdit> = edit.edit.conv_with((&line_index, line_endings));
Ok(Some(change))
}
pub fn handle_document_symbol(
world: WorldSnapshot,
params: req::DocumentSymbolParams,
) -> Result<Option<req::DocumentSymbolResponse>> {
let _p = profile("handle_document_symbol");
let file_id = params.text_document.try_conv_with(&world)?;
let line_index = world.analysis().file_line_index(file_id)?;
let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
for symbol in world.analysis().file_structure(file_id)? {
let doc_symbol = DocumentSymbol {
name: symbol.label,
detail: symbol.detail,
kind: symbol.kind.conv(),
deprecated: Some(symbol.deprecated),
range: symbol.node_range.conv_with(&line_index),
selection_range: symbol.navigation_range.conv_with(&line_index),
children: None,
};
parents.push((doc_symbol, symbol.parent));
}
let mut res = Vec::new();
while let Some((node, parent)) = parents.pop() {
match parent {
None => res.push(node),
Some(i) => {
let children = &mut parents[i].0.children;
if children.is_none() {
*children = Some(Vec::new());
}
children.as_mut().unwrap().push(node);
}
}
}
Ok(Some(res.into()))
}
pub fn handle_workspace_symbol(
world: WorldSnapshot,
params: req::WorkspaceSymbolParams,
) -> Result<Option<Vec<SymbolInformation>>> {
let _p = profile("handle_workspace_symbol");
let all_symbols = params.query.contains('#');
let libs = params.query.contains('*');
let query = {
let query: String = params.query.chars().filter(|&c| c != '#' && c != '*').collect();
let mut q = Query::new(query);
if !all_symbols {
q.only_types();
}
if libs {
q.libs();
}
q.limit(128);
q
};
let mut res = exec_query(&world, query)?;
if res.is_empty() && !all_symbols {
let mut query = Query::new(params.query);
query.limit(128);
res = exec_query(&world, query)?;
}
return Ok(Some(res));
fn exec_query(world: &WorldSnapshot, query: Query) -> Result<Vec<SymbolInformation>> {
let mut res = Vec::new();
for nav in world.analysis().symbol_search(query)? {
let info = SymbolInformation {
name: nav.name().to_string(),
kind: nav.kind().conv(),
location: nav.try_conv_with(world)?,
container_name: nav.container_name().map(|v| v.to_string()),
deprecated: None,
};
res.push(info);
}
Ok(res)
}
}
pub fn handle_goto_definition(
world: WorldSnapshot,
params: req::TextDocumentPositionParams,
) -> Result<Option<req::GotoDefinitionResponse>> {
let _p = profile("handle_goto_definition");
let position = params.try_conv_with(&world)?;
let nav_info = match world.analysis().goto_definition(position)? {
None => return Ok(None),
Some(it) => it,
};
let res = (position.file_id, nav_info).try_conv_with(&world)?;
Ok(Some(res))
}
pub fn handle_goto_implementation(
world: WorldSnapshot,
params: req::TextDocumentPositionParams,
) -> Result<Option<req::GotoImplementationResponse>> {
let _p = profile("handle_goto_implementation");
let position = params.try_conv_with(&world)?;
let nav_info = match world.analysis().goto_implementation(position)? {
None => return Ok(None),
Some(it) => it,
};
let res = (position.file_id, nav_info).try_conv_with(&world)?;
Ok(Some(res))
}
pub fn handle_goto_type_definition(
world: WorldSnapshot,
params: req::TextDocumentPositionParams,
) -> Result<Option<req::GotoTypeDefinitionResponse>> {
let _p = profile("handle_goto_type_definition");
let position = params.try_conv_with(&world)?;
let nav_info = match world.analysis().goto_type_definition(position)? {
None => return Ok(None),
Some(it) => it,
};
let res = (position.file_id, nav_info).try_conv_with(&world)?;
Ok(Some(res))
}
pub fn handle_parent_module(
world: WorldSnapshot,
params: req::TextDocumentPositionParams,
) -> Result<Vec<Location>> {
let _p = profile("handle_parent_module");
let position = params.try_conv_with(&world)?;
world.analysis().parent_module(position)?.iter().try_conv_with_to_vec(&world)
}
pub fn handle_runnables(
world: WorldSnapshot,
params: req::RunnablesParams,
) -> Result<Vec<req::Runnable>> {
let _p = profile("handle_runnables");
let file_id = params.text_document.try_conv_with(&world)?;
let line_index = world.analysis().file_line_index(file_id)?;
let offset = params.position.map(|it| it.conv_with(&line_index));
let mut res = Vec::new();
let workspace_root = world.workspace_root_for(file_id);
for runnable in world.analysis().runnables(file_id)? {
if let Some(offset) = offset {
if !runnable.range.contains_inclusive(offset) {
continue;
}
}
res.push(to_lsp_runnable(&world, file_id, runnable)?);
}
let mut check_args = vec!["check".to_string()];
let label;
match CargoTargetSpec::for_file(&world, file_id)? {
Some(spec) => {
label = format!("cargo check -p {}", spec.package);
spec.push_to(&mut check_args);
}
None => {
label = "cargo check --all".to_string();
check_args.push("--all".to_string())
}
}
// Always add `cargo check`.
res.push(req::Runnable {
range: Default::default(),
label,
bin: "cargo".to_string(),
args: check_args,
env: FxHashMap::default(),
cwd: workspace_root.map(|root| root.to_string_lossy().to_string()),
});
Ok(res)
}
pub fn handle_decorations(
world: WorldSnapshot,
params: TextDocumentIdentifier,
) -> Result<Vec<Decoration>> {
let _p = profile("handle_decorations");
let file_id = params.try_conv_with(&world)?;
highlight(&world, file_id)
}
pub fn handle_completion(
world: WorldSnapshot,
params: req::CompletionParams,
) -> Result<Option<req::CompletionResponse>> {
let _p = profile("handle_completion");
let position = params.text_document_position.try_conv_with(&world)?;
let completion_triggered_after_single_colon = {
let mut res = false;
if let Some(ctx) = params.context {
if ctx.trigger_character.unwrap_or_default() == ":" {
let source_file = world.analysis().parse(position.file_id)?;
let syntax = source_file.syntax();
let text = syntax.text();
if let Some(next_char) = text.char_at(position.offset) {
let diff = TextUnit::of_char(next_char) + TextUnit::of_char(':');
let prev_char = position.offset - diff;
if text.char_at(prev_char) != Some(':') {
res = true;
}
}
}
}
res
};
if completion_triggered_after_single_colon {
return Ok(None);
}
let items = match world.analysis().completions(position)? {
None => return Ok(None),
Some(items) => items,
};
let line_index = world.analysis().file_line_index(position.file_id)?;
let line_endings = world.file_line_endings(position.file_id);
let items: Vec<CompletionItem> =
items.into_iter().map(|item| item.conv_with((&line_index, line_endings))).collect();
Ok(Some(items.into()))
}
pub fn handle_folding_range(
world: WorldSnapshot,
params: FoldingRangeParams,
) -> Result<Option<Vec<FoldingRange>>> {
let _p = profile("handle_folding_range");
let file_id = params.text_document.try_conv_with(&world)?;
let folds = world.analysis().folding_ranges(file_id)?;
let text = world.analysis().file_text(file_id)?;
let line_index = world.analysis().file_line_index(file_id)?;
let ctx = FoldConvCtx {
text: &text,
line_index: &line_index,
line_folding_only: world.options.line_folding_only,
};
let res = Some(folds.into_iter().map_conv_with(&ctx).collect());
Ok(res)
}
pub fn handle_signature_help(
world: WorldSnapshot,
params: req::TextDocumentPositionParams,
) -> Result<Option<req::SignatureHelp>> {
let _p = profile("handle_signature_help");
let position = params.try_conv_with(&world)?;
if let Some(call_info) = world.analysis().call_info(position)? {
let active_parameter = call_info.active_parameter.map(|it| it as i64);
let sig_info = call_info.signature.conv();
Ok(Some(req::SignatureHelp {
signatures: vec![sig_info],
active_signature: Some(0),
active_parameter,
}))
} else {
Ok(None)
}
}
pub fn handle_hover(
world: WorldSnapshot,
params: req::TextDocumentPositionParams,
) -> Result<Option<Hover>> {
let _p = profile("handle_hover");
let position = params.try_conv_with(&world)?;
let info = match world.analysis().hover(position)? {
None => return Ok(None),
Some(info) => info,
};
let line_index = world.analysis.file_line_index(position.file_id)?;
let range = info.range.conv_with(&line_index);
let res = Hover {
contents: HoverContents::Markup(MarkupContent {
kind: MarkupKind::Markdown,
value: crate::markdown::format_docs(&info.info.to_markup()),
}),
range: Some(range),
};
Ok(Some(res))
}
pub fn handle_prepare_rename(
world: WorldSnapshot,
params: req::TextDocumentPositionParams,
) -> Result<Option<PrepareRenameResponse>> {
let _p = profile("handle_prepare_rename");
let position = params.try_conv_with(&world)?;
let optional_change = world.analysis().rename(position, "dummy")?;
let range = match optional_change {
None => return Ok(None),
Some(it) => it.range,
};
let file_id = params.text_document.try_conv_with(&world)?;
let line_index = world.analysis().file_line_index(file_id)?;
let range = range.conv_with(&line_index);
Ok(Some(PrepareRenameResponse::Range(range)))
}
pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
let _p = profile("handle_rename");
let position = params.text_document_position.try_conv_with(&world)?;
if params.new_name.is_empty() {
return Err(LspError::new(
ErrorCode::InvalidParams as i32,
"New Name cannot be empty".into(),
)
.into());
}
let optional_change = world.analysis().rename(position, &*params.new_name)?;
let change = match optional_change {
None => return Ok(None),
Some(it) => it.info,
};
let source_change_req = change.try_conv_with(&world)?;
Ok(Some(source_change_req.workspace_edit))
}
pub fn handle_references(
world: WorldSnapshot,
params: req::ReferenceParams,
) -> Result<Option<Vec<Location>>> {
let _p = profile("handle_references");
let position = params.text_document_position.try_conv_with(&world)?;
let refs = match world.analysis().find_all_refs(position, None)? {
None => return Ok(None),
Some(refs) => refs,
};
let locations = if params.context.include_declaration {
refs.into_iter()
.filter_map(|r| {
let line_index = world.analysis().file_line_index(r.file_id).ok()?;
to_location(r.file_id, r.range, &world, &line_index).ok()
})
.collect()
} else {
// Only iterate over the references if include_declaration was false
refs.references()
.iter()
.filter_map(|r| {
let line_index = world.analysis().file_line_index(r.file_id).ok()?;
to_location(r.file_id, r.range, &world, &line_index).ok()
})
.collect()
};
Ok(Some(locations))
}
pub fn handle_formatting(
world: WorldSnapshot,
params: DocumentFormattingParams,
) -> Result<Option<Vec<TextEdit>>> {
let _p = profile("handle_formatting");
let file_id = params.text_document.try_conv_with(&world)?;
let file = world.analysis().file_text(file_id)?;
let crate_ids = world.analysis().crate_for(file_id)?;
let file_line_index = world.analysis().file_line_index(file_id)?;
let end_position = TextUnit::of_str(&file).conv_with(&file_line_index);
use std::process;
let mut rustfmt = process::Command::new("rustfmt");
if let Some(&crate_id) = crate_ids.first() {
// Assume all crates are in the same edition
let edition = world.analysis().crate_edition(crate_id)?;
rustfmt.args(&["--edition", &edition.to_string()]);
}
rustfmt.stdin(process::Stdio::piped()).stdout(process::Stdio::piped());
if let Ok(path) = params.text_document.uri.to_file_path() {
if let Some(parent) = path.parent() {
rustfmt.current_dir(parent);
}
}
let mut rustfmt = rustfmt.spawn()?;
rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
let output = rustfmt.wait_with_output()?;
let captured_stdout = String::from_utf8(output.stdout)?;
if !output.status.success() {
match output.status.code() {
Some(1) => {
// While `rustfmt` doesn't have a specific exit code for parse errors this is the
// likely cause exiting with 1. Most Language Servers swallow parse errors on
// formatting because otherwise an error is surfaced to the user on top of the
// syntax error diagnostics they're already receiving. This is especially jarring
// if they have format on save enabled.
log::info!("rustfmt exited with status 1, assuming parse error and ignoring");
return Ok(None);
}
_ => {
// Something else happened - e.g. `rustfmt` is missing or caught a signal
return Err(LspError::new(
-32900,
format!(
r#"rustfmt exited with:
Status: {}
stdout: {}"#,
output.status, captured_stdout,
),
)
.into());
}
}
}
Ok(Some(vec![TextEdit {
range: Range::new(Position::new(0, 0), end_position),
new_text: captured_stdout,
}]))
}
pub fn handle_code_action(
world: WorldSnapshot,
params: req::CodeActionParams,
) -> Result<Option<CodeActionResponse>> {
let _p = profile("handle_code_action");
let file_id = params.text_document.try_conv_with(&world)?;
let line_index = world.analysis().file_line_index(file_id)?;
let range = params.range.conv_with(&line_index);
let assists = world.analysis().assists(FileRange { file_id, range })?.into_iter();
let diagnostics = world.analysis().diagnostics(file_id)?;
let mut res = CodeActionResponse::default();
let fixes_from_diagnostics = diagnostics
.into_iter()
.filter_map(|d| Some((d.range, d.fix?)))
.filter(|(diag_range, _fix)| diag_range.intersection(&range).is_some())
.map(|(_range, fix)| fix);
for source_edit in fixes_from_diagnostics {
let title = source_edit.label.clone();
let edit = source_edit.try_conv_with(&world)?;
let command = Command {
title,
command: "rust-analyzer.applySourceChange".to_string(),
arguments: Some(vec![to_value(edit).unwrap()]),
};
let action = CodeAction {
title: command.title.clone(),
kind: None,
diagnostics: None,
edit: None,
command: Some(command),
is_preferred: None,
};
res.push(action.into());
}
for assist in assists {
let title = assist.change.label.clone();
let edit = assist.change.try_conv_with(&world)?;
let command = Command {
title,
command: "rust-analyzer.applySourceChange".to_string(),
arguments: Some(vec![to_value(edit).unwrap()]),
};
let action = CodeAction {
title: command.title.clone(),
kind: match assist.id {
AssistId("introduce_variable") => Some("refactor.extract.variable".to_string()),
_ => None,
},
diagnostics: None,
edit: None,
command: Some(command),
is_preferred: None,
};
res.push(action.into());
}
Ok(Some(res))
}
pub fn handle_code_lens(
world: WorldSnapshot,
params: req::CodeLensParams,
) -> Result<Option<Vec<CodeLens>>> {
let _p = profile("handle_code_lens");
let file_id = params.text_document.try_conv_with(&world)?;
let line_index = world.analysis().file_line_index(file_id)?;
let mut lenses: Vec<CodeLens> = Default::default();
// Gather runnables
for runnable in world.analysis().runnables(file_id)? {
let title = match &runnable.kind {
RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "Run Test",
RunnableKind::Bench { .. } => "Run Bench",
RunnableKind::Bin => "Run",
}
.to_string();
let r = to_lsp_runnable(&world, file_id, runnable)?;
let lens = CodeLens {
range: r.range,
command: Some(Command {
title,
command: "rust-analyzer.runSingle".into(),
arguments: Some(vec![to_value(r).unwrap()]),
}),
data: None,
};
lenses.push(lens);
}
// Handle impls
lenses.extend(
world
.analysis()
.file_structure(file_id)?
.into_iter()
.filter(|it| match it.kind {
SyntaxKind::TRAIT_DEF | SyntaxKind::STRUCT_DEF | SyntaxKind::ENUM_DEF => true,
_ => false,
})
.map(|it| {
let range = it.node_range.conv_with(&line_index);
let pos = range.start;
let lens_params =
req::TextDocumentPositionParams::new(params.text_document.clone(), pos);
CodeLens {
range,
command: None,
data: Some(to_value(CodeLensResolveData::Impls(lens_params)).unwrap()),
}
}),
);
Ok(Some(lenses))
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum CodeLensResolveData {
Impls(req::TextDocumentPositionParams),
}
pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Result<CodeLens> {
let _p = profile("handle_code_lens_resolve");
let data = code_lens.data.unwrap();
let resolve = serde_json::from_value(data)?;
match resolve {
Some(CodeLensResolveData::Impls(lens_params)) => {
let locations: Vec<Location> =
match handle_goto_implementation(world, lens_params.clone())? {
Some(req::GotoDefinitionResponse::Scalar(loc)) => vec![loc],
Some(req::GotoDefinitionResponse::Array(locs)) => locs,
Some(req::GotoDefinitionResponse::Link(links)) => links
.into_iter()
.map(|link| Location::new(link.target_uri, link.target_selection_range))
.collect(),
_ => vec![],
};
let title = if locations.len() == 1 {
"1 implementation".into()
} else {
format!("{} implementations", locations.len())
};
// We cannot use the 'editor.action.showReferences' command directly
// because that command requires vscode types which we convert in the handler
// on the client side.
let cmd = Command {
title,
command: "rust-analyzer.showReferences".into(),
arguments: Some(vec![
to_value(&lens_params.text_document.uri).unwrap(),
to_value(code_lens.range.start).unwrap(),
to_value(locations).unwrap(),
]),
};
Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
}
None => Ok(CodeLens {
range: code_lens.range,
command: Some(Command { title: "Error".into(), ..Default::default() }),
data: None,
}),
}
}
pub fn handle_document_highlight(
world: WorldSnapshot,
params: req::TextDocumentPositionParams,
) -> Result<Option<Vec<DocumentHighlight>>> {
let _p = profile("handle_document_highlight");
let file_id = params.text_document.try_conv_with(&world)?;
let line_index = world.analysis().file_line_index(file_id)?;
let refs = match world
.analysis()
.find_all_refs(params.try_conv_with(&world)?, Some(SearchScope::single_file(file_id)))?
{
None => return Ok(None),
Some(refs) => refs,
};
Ok(Some(
refs.into_iter()
.filter(|r| r.file_id == file_id)
.map(|r| DocumentHighlight { range: r.range.conv_with(&line_index), kind: None })
.collect(),
))
}
pub fn publish_diagnostics(
world: &WorldSnapshot,
file_id: FileId,
) -> Result<req::PublishDiagnosticsParams> {
let _p = profile("publish_diagnostics");
let uri = world.file_id_to_uri(file_id)?;
let line_index = world.analysis().file_line_index(file_id)?;
let diagnostics = world
.analysis()
.diagnostics(file_id)?
.into_iter()
.map(|d| Diagnostic {
range: d.range.conv_with(&line_index),
severity: Some(d.severity.conv()),
code: None,
source: Some("rust-analyzer".to_string()),
message: d.message,
related_information: None,
tags: None,
})
.collect();
Ok(req::PublishDiagnosticsParams { uri, diagnostics, version: None })
}
pub fn publish_decorations(
world: &WorldSnapshot,
file_id: FileId,
) -> Result<req::PublishDecorationsParams> {
let _p = profile("publish_decorations");
let uri = world.file_id_to_uri(file_id)?;
Ok(req::PublishDecorationsParams { uri, decorations: highlight(&world, file_id)? })
}
fn to_lsp_runnable(
world: &WorldSnapshot,
file_id: FileId,
runnable: Runnable,
) -> Result<req::Runnable> {
let args = runnable_args(world, file_id, &runnable.kind)?;
let line_index = world.analysis().file_line_index(file_id)?;
let label = match &runnable.kind {
RunnableKind::Test { name } => format!("test {}", name),
RunnableKind::TestMod { path } => format!("test-mod {}", path),
RunnableKind::Bench { name } => format!("bench {}", name),
RunnableKind::Bin => "run binary".to_string(),
};
Ok(req::Runnable {
range: runnable.range.conv_with(&line_index),
label,
bin: "cargo".to_string(),
args,
env: {
let mut m = FxHashMap::default();
m.insert("RUST_BACKTRACE".to_string(), "short".to_string());
m
},
cwd: world.workspace_root_for(file_id).map(|root| root.to_string_lossy().to_string()),
})
}
fn highlight(world: &WorldSnapshot, file_id: FileId) -> Result<Vec<Decoration>> {
let line_index = world.analysis().file_line_index(file_id)?;
let res = world
.analysis()
.highlight(file_id)?
.into_iter()
.map(|h| Decoration {
range: h.range.conv_with(&line_index),
tag: h.tag,
binding_hash: h.binding_hash.map(|x| x.to_string()),
})
.collect();
Ok(res)
}
pub fn handle_inlay_hints(
world: WorldSnapshot,
params: InlayHintsParams,
) -> Result<Vec<InlayHint>> {
let _p = profile("handle_inlay_hints");
let file_id = params.text_document.try_conv_with(&world)?;
let analysis = world.analysis();
let line_index = analysis.file_line_index(file_id)?;
Ok(analysis
.inlay_hints(file_id, world.options.max_inlay_hint_length)?
.into_iter()
.map(|api_type| InlayHint {
label: api_type.label.to_string(),
range: api_type.range.conv_with(&line_index),
kind: match api_type.kind {
ra_ide::InlayKind::TypeHint => InlayKind::TypeHint,
},
})
.collect())
}