1 use gen_lsp_server::ErrorCode;
3 CodeActionResponse, CodeLens, Command, Diagnostic, DiagnosticSeverity,
4 DocumentFormattingParams, DocumentHighlight, DocumentSymbol, Documentation, FoldingRange,
5 FoldingRangeKind, FoldingRangeParams, Hover, HoverContents, Location, MarkupContent,
6 MarkupKind, ParameterInformation, ParameterLabel, Position, PrepareRenameResponse, Range,
7 RenameParams, SignatureInformation, SymbolInformation, TextDocumentIdentifier, TextEdit,
11 FileId, FilePosition, FileRange, FoldKind, Query, RangeInfo, RunnableKind, Severity, Cancelable,
13 use ra_syntax::{AstNode, TextUnit};
14 use rustc_hash::FxHashMap;
15 use serde_json::to_value;
19 cargo_target_spec::{runnable_args, CargoTargetSpec},
20 conv::{to_location, to_location_link, Conv, ConvWith, MapConvWith, TryConvWith},
21 req::{self, Decoration},
22 server_world::ServerWorld,
26 pub fn handle_analyzer_status(world: ServerWorld, _: ()) -> Result<String> {
30 pub fn handle_syntax_tree(world: ServerWorld, params: req::SyntaxTreeParams) -> Result<String> {
31 let id = params.text_document.try_conv_with(&world)?;
32 let res = world.analysis().syntax_tree(id);
36 pub fn handle_extend_selection(
38 params: req::ExtendSelectionParams,
39 ) -> Result<req::ExtendSelectionResult> {
40 let file_id = params.text_document.try_conv_with(&world)?;
41 let line_index = world.analysis().file_line_index(file_id);
42 let selections = params
45 .map_conv_with(&line_index)
46 .map(|range| FileRange { file_id, range })
50 .extend_selection(frange)
51 .map(|it| it.conv_with(&line_index))
53 .collect::<Cancelable<Vec<_>>>()?;
54 Ok(req::ExtendSelectionResult { selections })
57 pub fn handle_find_matching_brace(
59 params: req::FindMatchingBraceParams,
60 ) -> Result<Vec<Position>> {
61 let file_id = params.text_document.try_conv_with(&world)?;
62 let line_index = world.analysis().file_line_index(file_id);
66 .map_conv_with(&line_index)
70 .matching_brace(FilePosition { file_id, offset })
73 .map_conv_with(&line_index)
78 pub fn handle_join_lines(
80 params: req::JoinLinesParams,
81 ) -> Result<req::SourceChange> {
82 let frange = (¶ms.text_document, params.range).try_conv_with(&world)?;
83 world.analysis().join_lines(frange).try_conv_with(&world)
86 pub fn handle_on_enter(
88 params: req::TextDocumentPositionParams,
89 ) -> Result<Option<req::SourceChange>> {
90 let position = params.try_conv_with(&world)?;
91 match world.analysis().on_enter(position) {
93 Some(edit) => Ok(Some(edit.try_conv_with(&world)?)),
97 pub fn handle_on_type_formatting(
99 params: req::DocumentOnTypeFormattingParams,
100 ) -> Result<Option<Vec<TextEdit>>> {
101 let file_id = params.text_document.try_conv_with(&world)?;
102 let line_index = world.analysis().file_line_index(file_id);
103 let position = FilePosition {
105 /// in `ra_ide_api`, the `on_type` invariant is that
106 /// `text.char_at(position) == typed_char`.
107 offset: params.position.conv_with(&line_index) - TextUnit::of_char('.'),
110 let edit = match params.ch.as_str() {
111 "=" => world.analysis().on_eq_typed(position),
112 "." => world.analysis().on_dot_typed(position),
113 _ => return Ok(None),
115 let mut edit = match edit {
117 None => return Ok(None),
120 // This should be a single-file edit
121 let edit = edit.source_file_edits.pop().unwrap();
123 let change: Vec<TextEdit> = edit.edit.conv_with(&line_index);
124 return Ok(Some(change));
127 pub fn handle_document_symbol(
129 params: req::DocumentSymbolParams,
130 ) -> Result<Option<req::DocumentSymbolResponse>> {
131 let file_id = params.text_document.try_conv_with(&world)?;
132 let line_index = world.analysis().file_line_index(file_id);
134 let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
136 for symbol in world.analysis().file_structure(file_id) {
137 let doc_symbol = DocumentSymbol {
139 detail: symbol.detail,
140 kind: symbol.kind.conv(),
142 range: symbol.node_range.conv_with(&line_index),
143 selection_range: symbol.navigation_range.conv_with(&line_index),
146 parents.push((doc_symbol, symbol.parent));
148 let mut res = Vec::new();
149 while let Some((node, parent)) = parents.pop() {
151 None => res.push(node),
153 let children = &mut parents[i].0.children;
154 if children.is_none() {
155 *children = Some(Vec::new());
157 children.as_mut().unwrap().push(node);
162 Ok(Some(req::DocumentSymbolResponse::Nested(res)))
165 pub fn handle_workspace_symbol(
167 params: req::WorkspaceSymbolParams,
168 ) -> Result<Option<Vec<SymbolInformation>>> {
169 let all_symbols = params.query.contains('#');
170 let libs = params.query.contains('*');
172 let query: String = params
175 .filter(|&c| c != '#' && c != '*')
177 let mut q = Query::new(query);
187 let mut res = exec_query(&world, query)?;
188 if res.is_empty() && !all_symbols {
189 let mut query = Query::new(params.query);
191 res = exec_query(&world, query)?;
194 return Ok(Some(res));
196 fn exec_query(world: &ServerWorld, query: Query) -> Result<Vec<SymbolInformation>> {
197 let mut res = Vec::new();
198 for nav in world.analysis().symbol_search(query)? {
199 let info = SymbolInformation {
200 name: nav.name().to_string(),
201 kind: nav.kind().conv(),
202 location: nav.try_conv_with(world)?,
203 container_name: None,
212 pub fn handle_goto_definition(
214 params: req::TextDocumentPositionParams,
215 ) -> Result<Option<req::GotoDefinitionResponse>> {
216 let position = params.try_conv_with(&world)?;
217 let line_index = world.analysis().file_line_index(position.file_id);
218 let nav_info = match world.analysis().goto_definition(position)? {
219 None => return Ok(None),
222 let nav_range = nav_info.range;
226 .map(|nav| RangeInfo::new(nav_range, nav))
227 .map(|nav| to_location_link(&nav, &world, &line_index))
228 .collect::<Result<Vec<_>>>()?;
229 Ok(Some(req::GotoDefinitionResponse::Link(res)))
232 pub fn handle_parent_module(
234 params: req::TextDocumentPositionParams,
235 ) -> Result<Vec<Location>> {
236 let position = params.try_conv_with(&world)?;
239 .parent_module(position)?
241 .map(|nav| nav.try_conv_with(&world))
242 .collect::<Result<Vec<_>>>()
245 pub fn handle_runnables(
247 params: req::RunnablesParams,
248 ) -> Result<Vec<req::Runnable>> {
249 let file_id = params.text_document.try_conv_with(&world)?;
250 let line_index = world.analysis().file_line_index(file_id);
251 let offset = params.position.map(|it| it.conv_with(&line_index));
252 let mut res = Vec::new();
253 for runnable in world.analysis().runnables(file_id)? {
254 if let Some(offset) = offset {
255 if !runnable.range.contains_inclusive(offset) {
260 let args = runnable_args(&world, file_id, &runnable.kind)?;
262 let r = req::Runnable {
263 range: runnable.range.conv_with(&line_index),
264 label: match &runnable.kind {
265 RunnableKind::Test { name } => format!("test {}", name),
266 RunnableKind::TestMod { path } => format!("test-mod {}", path),
267 RunnableKind::Bench { name } => format!("bench {}", name),
268 RunnableKind::Bin => "run binary".to_string(),
270 bin: "cargo".to_string(),
273 let mut m = FxHashMap::default();
274 m.insert("RUST_BACKTRACE".to_string(), "short".to_string());
280 let mut check_args = vec!["check".to_string()];
282 match CargoTargetSpec::for_file(&world, file_id)? {
284 label = format!("cargo check -p {}", spec.package);
285 spec.push_to(&mut check_args);
288 label = "cargo check --all".to_string();
289 check_args.push("--all".to_string())
292 // Always add `cargo check`.
293 res.push(req::Runnable {
294 range: Default::default(),
296 bin: "cargo".to_string(),
298 env: FxHashMap::default(),
303 pub fn handle_decorations(
305 params: TextDocumentIdentifier,
306 ) -> Result<Vec<Decoration>> {
307 let file_id = params.try_conv_with(&world)?;
308 highlight(&world, file_id)
311 pub fn handle_completion(
313 params: req::CompletionParams,
314 ) -> Result<Option<req::CompletionResponse>> {
316 let file_id = params.text_document.try_conv_with(&world)?;
317 let line_index = world.analysis().file_line_index(file_id);
318 let offset = params.position.conv_with(&line_index);
319 FilePosition { file_id, offset }
321 let completion_triggered_after_single_colon = {
323 if let Some(ctx) = params.context {
324 if ctx.trigger_character.unwrap_or_default() == ":" {
325 let source_file = world.analysis().parse(position.file_id);
326 let syntax = source_file.syntax();
327 let text = syntax.text();
328 if let Some(next_char) = text.char_at(position.offset) {
329 let diff = TextUnit::of_char(next_char) + TextUnit::of_char(':');
330 let prev_char = position.offset - diff;
331 if text.char_at(prev_char) != Some(':') {
339 if completion_triggered_after_single_colon {
343 let items = match world.analysis().completions(position)? {
344 None => return Ok(None),
345 Some(items) => items,
347 let line_index = world.analysis().file_line_index(position.file_id);
350 .map(|item| item.conv_with(&line_index))
353 Ok(Some(req::CompletionResponse::Array(items)))
356 pub fn handle_folding_range(
358 params: FoldingRangeParams,
359 ) -> Result<Option<Vec<FoldingRange>>> {
360 let file_id = params.text_document.try_conv_with(&world)?;
361 let line_index = world.analysis().file_line_index(file_id);
366 .folding_ranges(file_id)
369 let kind = match fold.kind {
370 FoldKind::Comment => Some(FoldingRangeKind::Comment),
371 FoldKind::Imports => Some(FoldingRangeKind::Imports),
372 FoldKind::Mods => None,
373 FoldKind::Block => None,
375 let range = fold.range.conv_with(&line_index);
377 start_line: range.start.line,
378 start_character: Some(range.start.character),
379 end_line: range.end.line,
380 end_character: Some(range.start.character),
390 pub fn handle_signature_help(
392 params: req::TextDocumentPositionParams,
393 ) -> Result<Option<req::SignatureHelp>> {
394 let position = params.try_conv_with(&world)?;
395 if let Some(call_info) = world.analysis().call_info(position)? {
396 let parameters: Vec<ParameterInformation> = call_info
399 .map(|param| ParameterInformation {
400 label: ParameterLabel::Simple(param.clone()),
404 let documentation = call_info.doc.map(|value| {
405 Documentation::MarkupContent(MarkupContent {
406 kind: MarkupKind::Markdown,
410 let sig_info = SignatureInformation {
411 label: call_info.label,
413 parameters: Some(parameters),
415 Ok(Some(req::SignatureHelp {
416 signatures: vec![sig_info],
417 active_signature: Some(0),
418 active_parameter: call_info.active_parameter.map(|it| it as u64),
427 params: req::TextDocumentPositionParams,
428 ) -> Result<Option<Hover>> {
429 let position = params.try_conv_with(&world)?;
430 let info = match world.analysis().hover(position)? {
431 None => return Ok(None),
434 let line_index = world.analysis.file_line_index(position.file_id);
435 let range = info.range.conv_with(&line_index);
437 contents: HoverContents::Markup(MarkupContent {
438 kind: MarkupKind::Markdown,
447 pub fn handle_prepare_rename(
449 params: req::TextDocumentPositionParams,
450 ) -> Result<Option<PrepareRenameResponse>> {
451 let position = params.try_conv_with(&world)?;
453 // We support renaming references like handle_rename does.
454 // In the future we may want to reject the renaming of things like keywords here too.
455 let refs = world.analysis().find_all_refs(position)?;
456 let r = match refs.first() {
458 None => return Ok(None),
460 let file_id = params.text_document.try_conv_with(&world)?;
461 let line_index = world.analysis().file_line_index(file_id);
462 let loc = to_location(r.0, r.1, &world, &line_index)?;
464 Ok(Some(PrepareRenameResponse::Range(loc.range)))
467 pub fn handle_rename(world: ServerWorld, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
468 let file_id = params.text_document.try_conv_with(&world)?;
469 let line_index = world.analysis().file_line_index(file_id);
470 let offset = params.position.conv_with(&line_index);
472 if params.new_name.is_empty() {
473 return Err(LspError::new(
474 ErrorCode::InvalidParams as i32,
475 "New Name cannot be empty".into(),
480 let optional_change = world
482 .rename(FilePosition { file_id, offset }, &*params.new_name)?;
483 let change = match optional_change {
484 None => return Ok(None),
488 let source_change_req = change.try_conv_with(&world)?;
490 Ok(Some(source_change_req.workspace_edit))
493 pub fn handle_references(
495 params: req::ReferenceParams,
496 ) -> Result<Option<Vec<Location>>> {
497 let file_id = params.text_document.try_conv_with(&world)?;
498 let line_index = world.analysis().file_line_index(file_id);
499 let offset = params.position.conv_with(&line_index);
503 .find_all_refs(FilePosition { file_id, offset })?;
507 .filter_map(|r| to_location(r.0, r.1, &world, &line_index).ok())
512 pub fn handle_formatting(
514 params: DocumentFormattingParams,
515 ) -> Result<Option<Vec<TextEdit>>> {
516 let file_id = params.text_document.try_conv_with(&world)?;
517 let file = world.analysis().file_text(file_id);
519 let file_line_index = world.analysis().file_line_index(file_id);
520 let end_position = TextUnit::of_str(&file).conv_with(&file_line_index);
523 let mut rustfmt = process::Command::new("rustfmt");
525 .stdin(process::Stdio::piped())
526 .stdout(process::Stdio::piped());
528 if let Ok(path) = params.text_document.uri.to_file_path() {
529 if let Some(parent) = path.parent() {
530 rustfmt.current_dir(parent);
533 let mut rustfmt = rustfmt.spawn()?;
535 rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
537 let output = rustfmt.wait_with_output()?;
538 let captured_stdout = String::from_utf8(output.stdout)?;
539 if !output.status.success() {
540 return Err(LspError::new(
543 r#"rustfmt exited with:
546 output.status, captured_stdout,
552 Ok(Some(vec![TextEdit {
553 range: Range::new(Position::new(0, 0), end_position),
554 new_text: captured_stdout,
558 pub fn handle_code_action(
560 params: req::CodeActionParams,
561 ) -> Result<Option<CodeActionResponse>> {
562 let file_id = params.text_document.try_conv_with(&world)?;
563 let line_index = world.analysis().file_line_index(file_id);
564 let range = params.range.conv_with(&line_index);
568 .assists(FileRange { file_id, range })?
572 .diagnostics(file_id)?
574 .filter_map(|d| Some((d.range, d.fix?)))
575 .filter(|(diag_range, _fix)| diag_range.intersection(&range).is_some())
576 .map(|(_range, fix)| fix);
578 let mut res = Vec::new();
579 for source_edit in assists.chain(fixes) {
580 let title = source_edit.label.clone();
581 let edit = source_edit.try_conv_with(&world)?;
584 command: "rust-analyzer.applySourceChange".to_string(),
585 arguments: Some(vec![to_value(edit).unwrap()]),
590 Ok(Some(CodeActionResponse::Commands(res)))
593 pub fn handle_code_lens(
595 params: req::CodeLensParams,
596 ) -> Result<Option<Vec<CodeLens>>> {
597 let file_id = params.text_document.try_conv_with(&world)?;
598 let line_index = world.analysis().file_line_index(file_id);
600 let mut lenses: Vec<CodeLens> = Default::default();
602 for runnable in world.analysis().runnables(file_id)? {
603 let title = match &runnable.kind {
604 RunnableKind::Test { name: _ } | RunnableKind::TestMod { path: _ } => Some("Run Test"),
605 RunnableKind::Bench { name: _ } => Some("Run Bench"),
609 if let Some(title) = title {
610 let args = runnable_args(&world, file_id, &runnable.kind)?;
611 let range = runnable.range.conv_with(&line_index);
613 // This represents the actual command that will be run.
614 let r: req::Runnable = req::Runnable {
616 label: Default::default(),
619 env: Default::default(),
622 let lens = CodeLens {
624 command: Some(Command {
626 command: "rust-analyzer.runSingle".into(),
627 arguments: Some(vec![to_value(r).unwrap()]),
636 return Ok(Some(lenses));
639 pub fn handle_document_highlight(
641 params: req::TextDocumentPositionParams,
642 ) -> Result<Option<Vec<DocumentHighlight>>> {
643 let file_id = params.text_document.try_conv_with(&world)?;
644 let line_index = world.analysis().file_line_index(file_id);
648 .find_all_refs(params.try_conv_with(&world)?)?;
652 .map(|r| DocumentHighlight {
653 range: r.1.conv_with(&line_index),
660 pub fn publish_diagnostics(
663 ) -> Result<req::PublishDiagnosticsParams> {
664 let uri = world.file_id_to_uri(file_id)?;
665 let line_index = world.analysis().file_line_index(file_id);
666 let diagnostics = world
668 .diagnostics(file_id)?
670 .map(|d| Diagnostic {
671 range: d.range.conv_with(&line_index),
672 severity: Some(to_diagnostic_severity(d.severity)),
674 source: Some("rust-analyzer".to_string()),
676 related_information: None,
679 Ok(req::PublishDiagnosticsParams { uri, diagnostics })
682 pub fn publish_decorations(
685 ) -> Result<req::PublishDecorationsParams> {
686 let uri = world.file_id_to_uri(file_id)?;
687 Ok(req::PublishDecorationsParams {
689 decorations: highlight(&world, file_id)?,
693 fn highlight(world: &ServerWorld, file_id: FileId) -> Result<Vec<Decoration>> {
694 let line_index = world.analysis().file_line_index(file_id);
699 .map(|h| Decoration {
700 range: h.range.conv_with(&line_index),
707 fn to_diagnostic_severity(severity: Severity) -> DiagnosticSeverity {
708 use ra_ide_api::Severity::*;
711 Error => DiagnosticSeverity::Error,
712 WeakWarning => DiagnosticSeverity::Hint,