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

909 lines
31 KiB
Rust
Raw Normal View History

//! FIXME: write short doc here
use std::{fmt::Write as _, io::Write as _};
2019-05-29 12:42:14 +00:00
use lsp_server::ErrorCode;
2019-01-14 10:55:56 +00:00
use lsp_types::{
2019-07-07 21:28:21 +00:00
CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic,
2019-09-19 15:23:12 +00:00
DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams,
Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse,
Range, RenameParams, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit,
2019-01-05 23:58:03 +00:00
};
2019-11-27 18:32:33 +00:00
use ra_ide::{
AssistId, FileId, FilePosition, FileRange, Query, Runnable, RunnableKind, SearchScope,
};
2019-04-14 20:28:10 +00:00
use ra_prof::profile;
2019-11-29 14:52:12 +00:00
use ra_syntax::{AstNode, SyntaxKind, TextRange, TextUnit};
2018-10-31 20:41:43 +00:00
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
2018-08-29 15:03:14 +00:00
use serde_json::to_value;
2018-08-10 20:30:11 +00:00
2018-10-15 17:15:53 +00:00
use crate::{
2019-01-12 23:40:54 +00:00
cargo_target_spec::{runnable_args, CargoTargetSpec},
conv::{to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith, TryConvWithToVec},
2019-07-22 18:52:47 +00:00
req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind},
2019-06-01 07:31:40 +00:00
world::WorldSnapshot,
2018-10-31 20:41:43 +00:00
LspError, Result,
2018-08-10 21:12:31 +00:00
};
2018-08-10 18:13:39 +00:00
2019-06-01 07:31:40 +00:00
pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> {
2019-10-29 20:08:36 +00:00
let _p = profile("handle_analyzer_status");
2019-05-29 12:42:14 +00:00
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();
2019-05-29 12:42:14 +00:00
}
Ok(buf)
2019-01-22 21:15:03 +00:00
}
2019-06-01 07:31:40 +00:00
pub fn handle_syntax_tree(world: WorldSnapshot, params: req::SyntaxTreeParams) -> Result<String> {
2019-10-29 20:08:36 +00:00
let _p = profile("handle_syntax_tree");
2018-08-17 16:54:08 +00:00
let id = params.text_document.try_conv_with(&world)?;
2019-07-25 17:22:41 +00:00
let line_index = world.analysis().file_line_index(id)?;
let text_range = params.range.map(|p| p.conv_with(&line_index));
2019-07-25 17:22:41 +00:00
let res = world.analysis().syntax_tree(id, text_range)?;
2018-08-29 15:03:14 +00:00
Ok(res)
2018-08-10 18:13:39 +00:00
}
2018-08-10 19:23:17 +00:00
2019-11-17 18:47:50 +00:00
pub fn handle_expand_macro(
world: WorldSnapshot,
params: req::ExpandMacroParams,
2019-11-19 14:56:48 +00:00
) -> Result<Option<req::ExpandedMacro>> {
2019-11-17 18:47:50 +00:00
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),
2019-11-19 14:56:48 +00:00
Some(offset) => {
let res = world.analysis().expand_macro(FilePosition { file_id, offset })?;
Ok(res.map(|it| req::ExpandedMacro { name: it.name, expansion: it.expansion }))
}
2019-11-17 18:47:50 +00:00
}
}
pub fn handle_selection_range(
2019-06-01 07:31:40 +00:00
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)?;
2019-07-25 17:22:41 +00:00
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()
}
2018-08-15 21:23:22 +00:00
pub fn handle_find_matching_brace(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
2018-08-15 21:23:22 +00:00
params: req::FindMatchingBraceParams,
) -> Result<Vec<Position>> {
let _p = profile("handle_find_matching_brace");
2018-08-17 16:54:08 +00:00
let file_id = params.text_document.try_conv_with(&world)?;
2019-07-25 17:22:41 +00:00
let line_index = world.analysis().file_line_index(file_id)?;
let res = params
.offsets
2018-08-15 21:23:22 +00:00
.into_iter()
.map_conv_with(&line_index)
.map(|offset| {
2019-07-25 17:22:41 +00:00
if let Ok(Some(matching_brace_offset)) =
world.analysis().matching_brace(FilePosition { file_id, offset })
{
matching_brace_offset
} else {
offset
}
2018-08-15 21:23:22 +00:00
})
.map_conv_with(&line_index)
.collect();
Ok(res)
}
2018-08-23 19:14:51 +00:00
pub fn handle_join_lines(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
2018-08-23 19:14:51 +00:00
params: req::JoinLinesParams,
2018-08-29 15:03:14 +00:00
) -> Result<req::SourceChange> {
let _p = profile("handle_join_lines");
2018-12-28 15:15:19 +00:00
let frange = (&params.text_document, params.range).try_conv_with(&world)?;
2019-07-25 17:22:41 +00:00
world.analysis().join_lines(frange)?.try_conv_with(&world)
2018-08-29 15:03:14 +00:00
}
pub fn handle_on_enter(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
params: req::TextDocumentPositionParams,
) -> Result<Option<req::SourceChange>> {
let _p = profile("handle_on_enter");
2018-11-05 11:57:41 +00:00
let position = params.try_conv_with(&world)?;
2019-07-25 17:22:41 +00:00
match world.analysis().on_enter(position)? {
None => Ok(None),
Some(edit) => Ok(Some(edit.try_conv_with(&world)?)),
}
}
2019-10-25 09:16:56 +00:00
// Don't forget to add new trigger characters to `ServerCapabilities` in `caps.rs`.
2018-08-29 15:03:14 +00:00
pub fn handle_on_type_formatting(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
2018-08-29 15:03:14 +00:00
params: req::DocumentOnTypeFormattingParams,
) -> Result<Option<Vec<TextEdit>>> {
let _p = profile("handle_on_type_formatting");
2019-07-07 18:13:13 +00:00
let mut position = params.text_document_position.try_conv_with(&world)?;
2019-07-25 17:22:41 +00:00
let line_index = world.analysis().file_line_index(position.file_id)?;
2019-08-20 15:53:59 +00:00
let line_endings = world.file_line_endings(position.file_id);
2019-07-07 18:13:13 +00:00
2019-11-27 18:32:33 +00:00
// in `ra_ide`, the `on_type` invariant is that
2019-07-07 18:13:13 +00:00
// `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)?;
2019-01-11 12:05:40 +00:00
let mut edit = match edit {
Some(it) => it,
None => return Ok(None),
};
2019-01-05 23:58:03 +00:00
2019-01-11 12:05:40 +00:00
// This should be a single-file edit
let edit = edit.source_file_edits.pop().unwrap();
2019-01-05 23:58:03 +00:00
2019-08-20 15:53:59 +00:00
let change: Vec<TextEdit> = edit.edit.conv_with((&line_index, line_endings));
2019-02-06 20:50:26 +00:00
Ok(Some(change))
2018-08-23 19:14:51 +00:00
}
2018-08-11 11:44:12 +00:00
pub fn handle_document_symbol(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
2018-08-11 11:44:12 +00:00
params: req::DocumentSymbolParams,
) -> Result<Option<req::DocumentSymbolResponse>> {
2019-10-29 20:08:36 +00:00
let _p = profile("handle_document_symbol");
2018-08-17 16:54:08 +00:00
let file_id = params.text_document.try_conv_with(&world)?;
2019-07-25 17:22:41 +00:00
let line_index = world.analysis().file_line_index(file_id)?;
2018-08-11 11:44:12 +00:00
2018-08-14 08:20:09 +00:00
let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
2018-08-11 11:44:12 +00:00
2019-07-25 17:22:41 +00:00
for symbol in world.analysis().file_structure(file_id)? {
2018-08-11 11:44:12 +00:00
let doc_symbol = DocumentSymbol {
2018-08-14 08:20:09 +00:00
name: symbol.label,
2019-01-24 17:21:17 +00:00
detail: symbol.detail,
2018-08-12 18:02:56 +00:00
kind: symbol.kind.conv(),
2019-02-05 22:05:46 +00:00
deprecated: Some(symbol.deprecated),
2018-08-12 18:02:56 +00:00
range: symbol.node_range.conv_with(&line_index),
2018-08-14 08:20:09 +00:00
selection_range: symbol.navigation_range.conv_with(&line_index),
2018-08-11 11:44:12 +00:00
children: None,
};
2018-08-14 08:20:09 +00:00
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);
2018-08-11 11:44:12 +00:00
}
}
}
2018-08-14 08:20:09 +00:00
2019-07-07 21:28:21 +00:00
Ok(Some(res.into()))
2018-08-11 11:44:12 +00:00
}
2018-08-13 12:35:53 +00:00
pub fn handle_workspace_symbol(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
2018-08-13 12:35:53 +00:00
params: req::WorkspaceSymbolParams,
) -> Result<Option<Vec<SymbolInformation>>> {
2019-10-29 20:08:36 +00:00
let _p = profile("handle_workspace_symbol");
let all_symbols = params.query.contains('#');
let libs = params.query.contains('*');
2018-08-13 13:07:05 +00:00
let query = {
2019-02-08 11:49:43 +00:00
let query: String = params.query.chars().filter(|&c| c != '#' && c != '*').collect();
2018-08-13 13:07:05 +00:00
let mut q = Query::new(query);
if !all_symbols {
q.only_types();
}
2018-09-03 18:03:37 +00:00
if libs {
q.libs();
}
2018-08-13 14:19:27 +00:00
q.limit(128);
2018-08-13 13:07:05 +00:00
q
};
2018-10-20 19:02:41 +00:00
let mut res = exec_query(&world, query)?;
2018-08-14 10:33:44 +00:00
if res.is_empty() && !all_symbols {
let mut query = Query::new(params.query);
query.limit(128);
2018-10-20 19:02:41 +00:00
res = exec_query(&world, query)?;
2018-08-14 10:33:44 +00:00
}
2018-08-13 13:07:05 +00:00
2018-08-14 10:33:44 +00:00
return Ok(Some(res));
2019-06-01 07:31:40 +00:00
fn exec_query(world: &WorldSnapshot, query: Query) -> Result<Vec<SymbolInformation>> {
2018-08-14 10:33:44 +00:00
let mut res = Vec::new();
2019-01-02 14:09:39 +00:00
for nav in world.analysis().symbol_search(query)? {
2018-08-14 10:33:44 +00:00
let info = SymbolInformation {
2019-01-02 20:35:51 +00:00
name: nav.name().to_string(),
2019-01-02 14:09:39 +00:00
kind: nav.kind().conv(),
location: nav.try_conv_with(world)?,
container_name: nav.container_name().map(|v| v.to_string()),
2018-09-23 15:13:27 +00:00
deprecated: None,
2018-08-14 10:33:44 +00:00
};
res.push(info);
}
2018-08-14 10:33:44 +00:00
Ok(res)
}
2018-08-13 12:35:53 +00:00
}
2018-08-13 13:35:17 +00:00
pub fn handle_goto_definition(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
2018-08-13 13:35:17 +00:00
params: req::TextDocumentPositionParams,
) -> Result<Option<req::GotoDefinitionResponse>> {
2019-10-29 20:08:36 +00:00
let _p = profile("handle_goto_definition");
2018-11-05 11:57:41 +00:00
let position = params.try_conv_with(&world)?;
2019-01-11 11:14:09 +00:00
let nav_info = match world.analysis().goto_definition(position)? {
None => return Ok(None),
Some(it) => it,
};
2019-07-08 10:47:02 +00:00
let res = (position.file_id, nav_info).try_conv_with(&world)?;
Ok(Some(res))
2018-08-13 13:35:17 +00:00
}
pub fn handle_goto_implementation(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
params: req::TextDocumentPositionParams,
) -> Result<Option<req::GotoImplementationResponse>> {
2019-10-29 20:08:36 +00:00
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,
};
2019-07-08 10:47:02 +00:00
let res = (position.file_id, nav_info).try_conv_with(&world)?;
Ok(Some(res))
2019-04-23 18:11:27 +00:00
}
pub fn handle_goto_type_definition(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
2019-04-23 18:11:27 +00:00
params: req::TextDocumentPositionParams,
) -> Result<Option<req::GotoTypeDefinitionResponse>> {
2019-10-29 20:08:36 +00:00
let _p = profile("handle_goto_type_definition");
2019-04-23 18:11:27 +00:00
let position = params.try_conv_with(&world)?;
let nav_info = match world.analysis().goto_type_definition(position)? {
None => return Ok(None),
Some(it) => it,
};
2019-07-08 10:47:02 +00:00
let res = (position.file_id, nav_info).try_conv_with(&world)?;
Ok(Some(res))
2018-08-13 13:35:17 +00:00
}
2018-08-22 07:18:58 +00:00
pub fn handle_parent_module(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
params: req::TextDocumentPositionParams,
2018-08-22 07:18:58 +00:00
) -> Result<Vec<Location>> {
2019-10-29 20:08:36 +00:00
let _p = profile("handle_parent_module");
2018-11-05 11:57:41 +00:00
let position = params.try_conv_with(&world)?;
2019-07-08 10:39:16 +00:00
world.analysis().parent_module(position)?.iter().try_conv_with_to_vec(&world)
2018-08-22 07:18:58 +00:00
}
2018-08-29 15:03:14 +00:00
pub fn handle_runnables(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
2018-08-29 15:03:14 +00:00
params: req::RunnablesParams,
) -> Result<Vec<req::Runnable>> {
2019-10-29 20:08:36 +00:00
let _p = profile("handle_runnables");
2018-08-29 15:03:14 +00:00
let file_id = params.text_document.try_conv_with(&world)?;
2019-07-25 17:22:41 +00:00
let line_index = world.analysis().file_line_index(file_id)?;
2018-08-29 15:03:14 +00:00
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);
2018-10-20 19:02:41 +00:00
for runnable in world.analysis().runnables(file_id)? {
2018-08-29 15:03:14 +00:00
if let Some(offset) = offset {
2019-01-08 18:50:04 +00:00
if !runnable.range.contains_inclusive(offset) {
2018-08-29 15:03:14 +00:00
continue;
}
}
2019-08-09 20:34:14 +00:00
res.push(to_lsp_runnable(&world, file_id, runnable)?);
2018-08-29 15:03:14 +00:00
}
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())
}
}
2018-10-25 07:29:39 +00:00
// Always add `cargo check`.
res.push(req::Runnable {
range: Default::default(),
label,
2018-10-25 07:29:39 +00:00
bin: "cargo".to_string(),
args: check_args,
2018-10-25 07:29:39 +00:00
env: FxHashMap::default(),
cwd: workspace_root.map(|root| root.to_string_lossy().to_string()),
2018-10-25 07:29:39 +00:00
});
2019-02-06 20:50:26 +00:00
Ok(res)
2018-08-29 15:03:14 +00:00
}
pub fn handle_decorations(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
2018-08-29 15:03:14 +00:00
params: TextDocumentIdentifier,
) -> Result<Vec<Decoration>> {
2019-10-29 20:08:36 +00:00
let _p = profile("handle_decorations");
2018-08-29 15:03:14 +00:00
let file_id = params.try_conv_with(&world)?;
2018-10-20 19:02:41 +00:00
highlight(&world, file_id)
2018-08-29 15:03:14 +00:00
}
2018-08-26 09:51:45 +00:00
pub fn handle_completion(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
2018-08-26 09:51:45 +00:00
params: req::CompletionParams,
) -> Result<Option<req::CompletionResponse>> {
2019-04-14 20:28:10 +00:00
let _p = profile("handle_completion");
2019-07-07 18:13:13 +00:00
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 {
2018-11-29 20:30:49 +00:00
if ctx.trigger_character.unwrap_or_default() == ":" {
2019-07-25 17:22:41 +00:00
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);
}
2018-11-05 11:57:41 +00:00
let items = match world.analysis().completions(position)? {
2018-08-26 09:51:45 +00:00
None => return Ok(None),
Some(items) => items,
};
2019-07-25 17:22:41 +00:00
let line_index = world.analysis().file_line_index(position.file_id)?;
2019-08-20 15:53:59 +00:00
let line_endings = world.file_line_endings(position.file_id);
2019-07-07 21:28:21 +00:00
let items: Vec<CompletionItem> =
2019-08-20 15:53:59 +00:00
items.into_iter().map(|item| item.conv_with((&line_index, line_endings))).collect();
2018-08-26 09:51:45 +00:00
2019-07-07 21:28:21 +00:00
Ok(Some(items.into()))
2018-08-26 09:51:45 +00:00
}
2018-09-23 15:13:27 +00:00
pub fn handle_folding_range(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
2018-09-23 15:13:27 +00:00
params: FoldingRangeParams,
) -> Result<Option<Vec<FoldingRange>>> {
2019-10-29 20:08:36 +00:00
let _p = profile("handle_folding_range");
2018-09-23 15:13:27 +00:00
let file_id = params.text_document.try_conv_with(&world)?;
2019-09-19 15:23:12 +00:00
let folds = world.analysis().folding_ranges(file_id)?;
let text = world.analysis().file_text(file_id)?;
2019-07-25 17:22:41 +00:00
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)
2018-09-23 15:13:27 +00:00
}
pub fn handle_signature_help(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
params: req::TextDocumentPositionParams,
) -> Result<Option<req::SignatureHelp>> {
2019-10-29 20:08:36 +00:00
let _p = profile("handle_signature_help");
2018-11-05 11:57:41 +00:00
let position = params.try_conv_with(&world)?;
2019-01-08 15:27:44 +00:00
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)
}
}
2018-11-05 21:37:27 +00:00
pub fn handle_hover(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
2018-11-05 21:37:27 +00:00
params: req::TextDocumentPositionParams,
) -> Result<Option<Hover>> {
2019-10-29 20:08:36 +00:00
let _p = profile("handle_hover");
2018-11-05 21:37:27 +00:00
let position = params.try_conv_with(&world)?;
let info = match world.analysis().hover(position)? {
None => return Ok(None),
Some(info) => info,
};
2019-07-25 17:22:41 +00:00
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),
2019-01-04 10:06:46 +00:00
};
Ok(Some(res))
2018-11-05 21:37:27 +00:00
}
2018-10-19 19:25:10 +00:00
pub fn handle_prepare_rename(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
2018-10-19 19:25:10 +00:00
params: req::TextDocumentPositionParams,
) -> Result<Option<PrepareRenameResponse>> {
2019-10-23 15:26:43 +00:00
let _p = profile("handle_prepare_rename");
2018-11-05 11:57:41 +00:00
let position = params.try_conv_with(&world)?;
2018-10-19 19:25:10 +00:00
2019-09-05 18:36:40 +00:00
let optional_change = world.analysis().rename(position, "dummy")?;
let range = match optional_change {
2018-11-05 11:57:41 +00:00
None => return Ok(None),
2019-09-05 18:36:40 +00:00
Some(it) => it.range,
2018-11-05 11:57:41 +00:00
};
2018-11-05 11:57:41 +00:00
let file_id = params.text_document.try_conv_with(&world)?;
2019-07-25 17:22:41 +00:00
let line_index = world.analysis().file_line_index(file_id)?;
2019-09-05 18:36:40 +00:00
let range = range.conv_with(&line_index);
Ok(Some(PrepareRenameResponse::Range(range)))
2018-10-19 19:25:10 +00:00
}
2019-06-01 07:31:40 +00:00
pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
2019-10-23 15:26:43 +00:00
let _p = profile("handle_rename");
2019-07-07 18:13:13 +00:00
let position = params.text_document_position.try_conv_with(&world)?;
2018-10-18 21:56:22 +00:00
if params.new_name.is_empty() {
2018-10-31 20:41:43 +00:00
return Err(LspError::new(
ErrorCode::InvalidParams as i32,
"New Name cannot be empty".into(),
)
.into());
2018-10-18 21:56:22 +00:00
}
2019-07-07 18:13:13 +00:00
let optional_change = world.analysis().rename(position, &*params.new_name)?;
2019-01-18 08:32:36 +00:00
let change = match optional_change {
None => return Ok(None),
2019-09-05 18:36:40 +00:00
Some(it) => it.info,
2019-01-18 08:32:36 +00:00
};
2018-10-18 21:56:22 +00:00
2019-01-18 08:32:36 +00:00
let source_change_req = change.try_conv_with(&world)?;
2019-01-14 16:09:03 +00:00
2019-01-17 11:57:24 +00:00
Ok(Some(source_change_req.workspace_edit))
2018-10-18 21:56:22 +00:00
}
pub fn handle_references(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
params: req::ReferenceParams,
) -> Result<Option<Vec<Location>>> {
2019-10-23 15:26:43 +00:00
let _p = profile("handle_references");
2019-07-07 18:13:13 +00:00
let position = params.text_document_position.try_conv_with(&world)?;
2019-10-12 15:47:17 +00:00
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(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
params: DocumentFormattingParams,
) -> Result<Option<Vec<TextEdit>>> {
2019-10-29 20:08:36 +00:00
let _p = profile("handle_formatting");
let file_id = params.text_document.try_conv_with(&world)?;
2019-07-25 17:22:41 +00:00
let file = world.analysis().file_text(file_id)?;
let crate_ids = world.analysis().crate_for(file_id)?;
2019-07-25 17:22:41 +00:00
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()]);
}
2019-02-08 11:49:43 +00:00
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,
}]))
}
2018-08-29 15:03:14 +00:00
pub fn handle_code_action(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
2018-08-29 15:03:14 +00:00
params: req::CodeActionParams,
2018-09-23 15:10:57 +00:00
) -> Result<Option<CodeActionResponse>> {
2019-04-14 20:28:10 +00:00
let _p = profile("handle_code_action");
2018-08-28 08:12:42 +00:00
let file_id = params.text_document.try_conv_with(&world)?;
2019-07-25 17:22:41 +00:00
let line_index = world.analysis().file_line_index(file_id)?;
2018-09-05 21:59:07 +00:00
let range = params.range.conv_with(&line_index);
2018-08-12 23:38:34 +00:00
2019-02-08 11:49:43 +00:00
let assists = world.analysis().assists(FileRange { file_id, range })?.into_iter();
2019-02-24 10:53:35 +00:00
let diagnostics = world.analysis().diagnostics(file_id)?;
2019-07-04 20:52:29 +00:00
let mut res = CodeActionResponse::default();
2019-02-24 10:53:35 +00:00
let fixes_from_diagnostics = diagnostics
.into_iter()
2018-08-29 15:03:14 +00:00
.filter_map(|d| Some((d.range, d.fix?)))
2019-01-08 18:50:04 +00:00
.filter(|(diag_range, _fix)| diag_range.intersection(&range).is_some())
2018-08-29 15:03:14 +00:00
.map(|(_range, fix)| fix);
2018-08-12 23:38:34 +00:00
2019-02-24 10:53:35 +00:00
for source_edit in fixes_from_diagnostics {
2018-08-29 15:03:14 +00:00
let title = source_edit.label.clone();
let edit = source_edit.try_conv_with(&world)?;
2019-02-04 15:26:43 +00:00
2019-02-24 10:53:35 +00:00
let command = Command {
2018-08-29 15:03:14 +00:00
title,
2019-01-28 11:43:07 +00:00
command: "rust-analyzer.applySourceChange".to_string(),
2018-08-29 15:03:14 +00:00
arguments: Some(vec![to_value(edit).unwrap()]),
};
2019-02-24 10:53:35 +00:00
let action = CodeAction {
title: command.title.clone(),
kind: None,
diagnostics: None,
edit: None,
command: Some(command),
is_preferred: None,
2019-02-24 10:53:35 +00:00
};
2019-07-04 22:04:46 +00:00
res.push(action.into());
2019-02-24 10:53:35 +00:00
}
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,
2019-02-24 10:53:35 +00:00
};
2019-07-04 22:04:46 +00:00
res.push(action.into());
2018-08-12 18:02:56 +00:00
}
2019-07-04 18:57:14 +00:00
Ok(Some(res))
2018-08-11 11:44:12 +00:00
}
2019-01-11 20:16:55 +00:00
pub fn handle_code_lens(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
2019-01-11 20:16:55 +00:00
params: req::CodeLensParams,
) -> Result<Option<Vec<CodeLens>>> {
2019-10-29 20:08:36 +00:00
let _p = profile("handle_code_lens");
2019-01-11 20:16:55 +00:00
let file_id = params.text_document.try_conv_with(&world)?;
2019-07-25 17:22:41 +00:00
let line_index = world.analysis().file_line_index(file_id)?;
2019-01-11 20:16:55 +00:00
let mut lenses: Vec<CodeLens> = Default::default();
// Gather runnables
2019-07-21 20:48:54 +00:00
for runnable in world.analysis().runnables(file_id)? {
2019-01-12 23:40:54 +00:00
let title = match &runnable.kind {
2019-08-09 19:18:47 +00:00
RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "Run Test",
RunnableKind::Bench { .. } => "Run Bench",
RunnableKind::Bin => "Run",
2019-08-09 20:34:14 +00:00
}
.to_string();
let r = to_lsp_runnable(&world, file_id, runnable)?;
2019-08-09 19:18:47 +00:00
let lens = CodeLens {
range: r.range,
command: Some(Command {
2019-08-09 20:34:14 +00:00
title,
2019-08-09 19:18:47 +00:00
command: "rust-analyzer.runSingle".into(),
arguments: Some(vec![to_value(r).unwrap()]),
}),
data: None,
2019-01-12 23:40:54 +00:00
};
2019-01-11 20:16:55 +00:00
2019-08-09 19:18:47 +00:00
lenses.push(lens);
2019-01-11 20:16:55 +00:00
}
2019-07-19 21:20:09 +00:00
// Handle impls
lenses.extend(
2019-07-21 20:48:54 +00:00
world
.analysis()
2019-07-25 17:22:41 +00:00
.file_structure(file_id)?
2019-07-19 21:20:09 +00:00
.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);
2019-07-19 21:20:09 +00:00
CodeLens {
range,
command: None,
data: Some(to_value(CodeLensResolveData::Impls(lens_params)).unwrap()),
2019-07-19 21:20:09 +00:00
}
}),
);
2019-02-06 20:50:26 +00:00
Ok(Some(lenses))
2019-01-11 20:16:55 +00:00
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum CodeLensResolveData {
Impls(req::TextDocumentPositionParams),
}
2019-06-01 07:31:40 +00:00
pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Result<CodeLens> {
2019-10-29 20:08:36 +00:00
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())
};
2019-02-04 21:34:02 +00:00
// 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![
2019-08-17 18:34:31 +00:00
to_value(&lens_params.text_document.uri).unwrap(),
2019-02-04 21:34:02 +00:00
to_value(code_lens.range.start).unwrap(),
to_value(locations).unwrap(),
]),
};
2019-02-08 11:49:43 +00:00
Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
}
2019-02-06 20:50:26 +00:00
None => Ok(CodeLens {
range: code_lens.range,
2019-02-08 11:49:43 +00:00
command: Some(Command { title: "Error".into(), ..Default::default() }),
2019-02-06 20:50:26 +00:00
data: None,
}),
}
}
2018-12-31 11:08:44 +00:00
pub fn handle_document_highlight(
2019-06-01 07:31:40 +00:00
world: WorldSnapshot,
2018-12-31 11:08:44 +00:00
params: req::TextDocumentPositionParams,
) -> Result<Option<Vec<DocumentHighlight>>> {
2019-10-23 15:26:43 +00:00
let _p = profile("handle_document_highlight");
2018-12-31 11:08:44 +00:00
let file_id = params.text_document.try_conv_with(&world)?;
2019-07-25 17:22:41 +00:00
let line_index = world.analysis().file_line_index(file_id)?;
2018-12-31 11:08:44 +00:00
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,
};
2018-12-31 11:08:44 +00:00
Ok(Some(
refs.into_iter()
2019-10-15 16:25:57 +00:00
.filter(|r| r.file_id == file_id)
.map(|r| DocumentHighlight { range: r.range.conv_with(&line_index), kind: None })
2018-12-31 11:08:44 +00:00
.collect(),
))
}
2018-08-15 14:24:20 +00:00
pub fn publish_diagnostics(
2019-06-01 07:31:40 +00:00
world: &WorldSnapshot,
2018-08-30 13:27:09 +00:00
file_id: FileId,
2018-08-15 14:24:20 +00:00
) -> Result<req::PublishDiagnosticsParams> {
2019-10-29 20:08:36 +00:00
let _p = profile("publish_diagnostics");
2018-08-30 13:27:09 +00:00
let uri = world.file_id_to_uri(file_id)?;
2019-07-25 17:22:41 +00:00
let line_index = world.analysis().file_line_index(file_id)?;
let diagnostics = world
.analysis()
2018-10-20 19:02:41 +00:00
.diagnostics(file_id)?
2018-08-10 20:30:11 +00:00
.into_iter()
2018-08-29 15:03:14 +00:00
.map(|d| Diagnostic {
2018-08-12 18:02:56 +00:00
range: d.range.conv_with(&line_index),
2019-08-06 07:29:06 +00:00
severity: Some(d.severity.conv()),
2018-08-10 20:30:11 +00:00
code: None,
2018-09-18 21:40:33 +00:00
source: Some("rust-analyzer".to_string()),
2018-08-29 15:03:14 +00:00
message: d.message,
2018-08-10 20:30:11 +00:00
related_information: None,
tags: None,
})
.collect();
Ok(req::PublishDiagnosticsParams { uri, diagnostics, version: None })
2018-08-10 20:30:11 +00:00
}
2018-08-15 14:24:20 +00:00
pub fn publish_decorations(
2019-06-01 07:31:40 +00:00
world: &WorldSnapshot,
2018-08-30 13:27:09 +00:00
file_id: FileId,
2018-08-15 14:24:20 +00:00
) -> Result<req::PublishDecorationsParams> {
2019-10-29 20:08:36 +00:00
let _p = profile("publish_decorations");
2018-08-30 13:27:09 +00:00
let uri = world.file_id_to_uri(file_id)?;
2019-02-08 11:49:43 +00:00
Ok(req::PublishDecorationsParams { uri, decorations: highlight(&world, file_id)? })
2018-08-27 21:20:59 +00:00
}
2019-08-09 19:18:47 +00:00
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)?;
2019-08-09 20:34:14 +00:00
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(),
};
2019-08-09 19:18:47 +00:00
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()),
})
}
2019-06-01 07:31:40 +00:00
fn highlight(world: &WorldSnapshot, file_id: FileId) -> Result<Vec<Decoration>> {
2019-07-25 17:22:41 +00:00
let line_index = world.analysis().file_line_index(file_id)?;
2018-10-20 19:02:41 +00:00
let res = world
.analysis()
2018-10-20 19:02:41 +00:00
.highlight(file_id)?
2018-08-10 21:55:32 +00:00
.into_iter()
.map(|h| Decoration {
range: h.range.conv_with(&line_index),
tag: h.tag,
2019-05-26 13:39:57 +00:00
binding_hash: h.binding_hash.map(|x| x.to_string()),
})
2018-10-20 19:02:41 +00:00
.collect();
Ok(res)
2018-08-10 21:55:32 +00:00
}
2018-12-23 16:39:33 +00:00
2019-07-22 18:52:47 +00:00
pub fn handle_inlay_hints(
world: WorldSnapshot,
params: InlayHintsParams,
) -> Result<Vec<InlayHint>> {
2019-10-29 20:08:36 +00:00
let _p = profile("handle_inlay_hints");
2019-07-22 18:52:47 +00:00
let file_id = params.text_document.try_conv_with(&world)?;
let analysis = world.analysis();
2019-07-25 17:22:41 +00:00
let line_index = analysis.file_line_index(file_id)?;
2019-07-22 18:52:47 +00:00
Ok(analysis
.inlay_hints(file_id, world.options.max_inlay_hint_length)?
2019-07-22 18:52:47 +00:00
.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 {
2019-11-27 18:32:33 +00:00
ra_ide::InlayKind::TypeHint => InlayKind::TypeHint,
2019-07-22 18:52:47 +00:00
},
})
.collect())
}