]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | //! This module is responsible for implementing handlers for Language Server |
2 | //! Protocol. The majority of requests are fulfilled by calling into the | |
3 | //! `ide` crate. | |
4 | ||
5 | use std::{ | |
6 | io::Write as _, | |
7 | process::{self, Stdio}, | |
8 | }; | |
9 | ||
10 | use anyhow::Context; | |
11 | use ide::{ | |
487cf647 FG |
12 | AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, FileId, FilePosition, |
13 | FileRange, HoverAction, HoverGotoTypeData, Query, RangeInfo, ReferenceCategory, Runnable, | |
14 | RunnableKind, SingleResolve, SourceChange, TextEdit, | |
064997fb FG |
15 | }; |
16 | use ide_db::SymbolKind; | |
17 | use lsp_server::ErrorCode; | |
18 | use lsp_types::{ | |
19 | CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, | |
20 | CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, | |
21 | CodeLens, CompletionItem, Diagnostic, DiagnosticTag, DocumentFormattingParams, FoldingRange, | |
22 | FoldingRangeParams, HoverContents, InlayHint, InlayHintParams, Location, LocationLink, | |
23 | NumberOrString, Position, PrepareRenameResponse, Range, RenameParams, | |
24 | SemanticTokensDeltaParams, SemanticTokensFullDeltaResult, SemanticTokensParams, | |
25 | SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, | |
26 | SymbolTag, TextDocumentIdentifier, Url, WorkspaceEdit, | |
27 | }; | |
28 | use project_model::{ManifestPath, ProjectWorkspace, TargetKind}; | |
29 | use serde_json::json; | |
30 | use stdx::{format_to, never}; | |
f25598a0 FG |
31 | use syntax::{algo, ast, AstNode, TextRange, TextSize}; |
32 | use tracing::error; | |
064997fb FG |
33 | use vfs::AbsPathBuf; |
34 | ||
35 | use crate::{ | |
36 | cargo_target_spec::CargoTargetSpec, | |
37 | config::{RustfmtConfig, WorkspaceSymbolConfig}, | |
38 | diff::diff, | |
39 | from_proto, | |
40 | global_state::{GlobalState, GlobalStateSnapshot}, | |
41 | line_index::LineEndings, | |
42 | lsp_ext::{self, PositionOrRange, ViewCrateGraphParams, WorkspaceSymbolParams}, | |
43 | lsp_utils::{all_edits_are_disjoint, invalid_params_error}, | |
44 | to_proto, LspError, Result, | |
45 | }; | |
46 | ||
47 | pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> Result<()> { | |
48 | state.proc_macro_clients.clear(); | |
49 | state.proc_macro_changed = false; | |
50 | state.fetch_workspaces_queue.request_op("reload workspace request".to_string()); | |
51 | state.fetch_build_data_queue.request_op("reload workspace request".to_string()); | |
52 | Ok(()) | |
53 | } | |
54 | ||
f2b60f7d FG |
55 | pub(crate) fn handle_cancel_flycheck(state: &mut GlobalState, _: ()) -> Result<()> { |
56 | let _p = profile::span("handle_stop_flycheck"); | |
57 | state.flycheck.iter().for_each(|flycheck| flycheck.cancel()); | |
58 | Ok(()) | |
59 | } | |
60 | ||
064997fb FG |
61 | pub(crate) fn handle_analyzer_status( |
62 | snap: GlobalStateSnapshot, | |
63 | params: lsp_ext::AnalyzerStatusParams, | |
64 | ) -> Result<String> { | |
65 | let _p = profile::span("handle_analyzer_status"); | |
66 | ||
67 | let mut buf = String::new(); | |
68 | ||
69 | let mut file_id = None; | |
70 | if let Some(tdi) = params.text_document { | |
71 | match from_proto::file_id(&snap, &tdi.uri) { | |
72 | Ok(it) => file_id = Some(it), | |
73 | Err(_) => format_to!(buf, "file {} not found in vfs", tdi.uri), | |
74 | } | |
75 | } | |
76 | ||
77 | if snap.workspaces.is_empty() { | |
78 | buf.push_str("No workspaces\n") | |
79 | } else { | |
80 | buf.push_str("Workspaces:\n"); | |
81 | format_to!( | |
82 | buf, | |
83 | "Loaded {:?} packages across {} workspace{}.\n", | |
84 | snap.workspaces.iter().map(|w| w.n_packages()).sum::<usize>(), | |
85 | snap.workspaces.len(), | |
86 | if snap.workspaces.len() == 1 { "" } else { "s" } | |
87 | ); | |
88 | } | |
89 | buf.push_str("\nAnalysis:\n"); | |
90 | buf.push_str( | |
91 | &snap | |
92 | .analysis | |
93 | .status(file_id) | |
94 | .unwrap_or_else(|_| "Analysis retrieval was cancelled".to_owned()), | |
95 | ); | |
96 | Ok(buf) | |
97 | } | |
98 | ||
99 | pub(crate) fn handle_memory_usage(state: &mut GlobalState, _: ()) -> Result<String> { | |
100 | let _p = profile::span("handle_memory_usage"); | |
101 | let mut mem = state.analysis_host.per_query_memory_usage(); | |
102 | mem.push(("Remaining".into(), profile::memory_usage().allocated)); | |
103 | ||
104 | let mut out = String::new(); | |
105 | for (name, bytes) in mem { | |
106 | format_to!(out, "{:>8} {}\n", bytes, name); | |
107 | } | |
108 | Ok(out) | |
109 | } | |
110 | ||
111 | pub(crate) fn handle_shuffle_crate_graph(state: &mut GlobalState, _: ()) -> Result<()> { | |
112 | state.analysis_host.shuffle_crate_graph(); | |
113 | Ok(()) | |
114 | } | |
115 | ||
116 | pub(crate) fn handle_syntax_tree( | |
117 | snap: GlobalStateSnapshot, | |
118 | params: lsp_ext::SyntaxTreeParams, | |
119 | ) -> Result<String> { | |
120 | let _p = profile::span("handle_syntax_tree"); | |
121 | let id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | |
122 | let line_index = snap.file_line_index(id)?; | |
123 | let text_range = params.range.and_then(|r| from_proto::text_range(&line_index, r).ok()); | |
124 | let res = snap.analysis.syntax_tree(id, text_range)?; | |
125 | Ok(res) | |
126 | } | |
127 | ||
128 | pub(crate) fn handle_view_hir( | |
129 | snap: GlobalStateSnapshot, | |
130 | params: lsp_types::TextDocumentPositionParams, | |
131 | ) -> Result<String> { | |
132 | let _p = profile::span("handle_view_hir"); | |
133 | let position = from_proto::file_position(&snap, params)?; | |
134 | let res = snap.analysis.view_hir(position)?; | |
135 | Ok(res) | |
136 | } | |
137 | ||
138 | pub(crate) fn handle_view_file_text( | |
139 | snap: GlobalStateSnapshot, | |
140 | params: lsp_types::TextDocumentIdentifier, | |
141 | ) -> Result<String> { | |
142 | let file_id = from_proto::file_id(&snap, ¶ms.uri)?; | |
143 | Ok(snap.analysis.file_text(file_id)?.to_string()) | |
144 | } | |
145 | ||
146 | pub(crate) fn handle_view_item_tree( | |
147 | snap: GlobalStateSnapshot, | |
148 | params: lsp_ext::ViewItemTreeParams, | |
149 | ) -> Result<String> { | |
150 | let _p = profile::span("handle_view_item_tree"); | |
151 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | |
152 | let res = snap.analysis.view_item_tree(file_id)?; | |
153 | Ok(res) | |
154 | } | |
155 | ||
156 | pub(crate) fn handle_view_crate_graph( | |
157 | snap: GlobalStateSnapshot, | |
158 | params: ViewCrateGraphParams, | |
159 | ) -> Result<String> { | |
160 | let _p = profile::span("handle_view_crate_graph"); | |
161 | let dot = snap.analysis.view_crate_graph(params.full)??; | |
162 | Ok(dot) | |
163 | } | |
164 | ||
165 | pub(crate) fn handle_expand_macro( | |
166 | snap: GlobalStateSnapshot, | |
167 | params: lsp_ext::ExpandMacroParams, | |
168 | ) -> Result<Option<lsp_ext::ExpandedMacro>> { | |
169 | let _p = profile::span("handle_expand_macro"); | |
170 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | |
171 | let line_index = snap.file_line_index(file_id)?; | |
172 | let offset = from_proto::offset(&line_index, params.position)?; | |
173 | ||
174 | let res = snap.analysis.expand_macro(FilePosition { file_id, offset })?; | |
175 | Ok(res.map(|it| lsp_ext::ExpandedMacro { name: it.name, expansion: it.expansion })) | |
176 | } | |
177 | ||
178 | pub(crate) fn handle_selection_range( | |
179 | snap: GlobalStateSnapshot, | |
180 | params: lsp_types::SelectionRangeParams, | |
181 | ) -> Result<Option<Vec<lsp_types::SelectionRange>>> { | |
182 | let _p = profile::span("handle_selection_range"); | |
183 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | |
184 | let line_index = snap.file_line_index(file_id)?; | |
185 | let res: Result<Vec<lsp_types::SelectionRange>> = params | |
186 | .positions | |
187 | .into_iter() | |
188 | .map(|position| { | |
189 | let offset = from_proto::offset(&line_index, position)?; | |
190 | let mut ranges = Vec::new(); | |
191 | { | |
192 | let mut range = TextRange::new(offset, offset); | |
193 | loop { | |
194 | ranges.push(range); | |
195 | let frange = FileRange { file_id, range }; | |
196 | let next = snap.analysis.extend_selection(frange)?; | |
197 | if next == range { | |
198 | break; | |
199 | } else { | |
200 | range = next | |
201 | } | |
202 | } | |
203 | } | |
204 | let mut range = lsp_types::SelectionRange { | |
205 | range: to_proto::range(&line_index, *ranges.last().unwrap()), | |
206 | parent: None, | |
207 | }; | |
208 | for &r in ranges.iter().rev().skip(1) { | |
209 | range = lsp_types::SelectionRange { | |
210 | range: to_proto::range(&line_index, r), | |
211 | parent: Some(Box::new(range)), | |
212 | } | |
213 | } | |
214 | Ok(range) | |
215 | }) | |
216 | .collect(); | |
217 | ||
218 | Ok(Some(res?)) | |
219 | } | |
220 | ||
221 | pub(crate) fn handle_matching_brace( | |
222 | snap: GlobalStateSnapshot, | |
223 | params: lsp_ext::MatchingBraceParams, | |
224 | ) -> Result<Vec<Position>> { | |
225 | let _p = profile::span("handle_matching_brace"); | |
226 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | |
227 | let line_index = snap.file_line_index(file_id)?; | |
228 | params | |
229 | .positions | |
230 | .into_iter() | |
231 | .map(|position| { | |
232 | let offset = from_proto::offset(&line_index, position); | |
233 | offset.map(|offset| { | |
234 | let offset = match snap.analysis.matching_brace(FilePosition { file_id, offset }) { | |
235 | Ok(Some(matching_brace_offset)) => matching_brace_offset, | |
236 | Err(_) | Ok(None) => offset, | |
237 | }; | |
238 | to_proto::position(&line_index, offset) | |
239 | }) | |
240 | }) | |
241 | .collect() | |
242 | } | |
243 | ||
244 | pub(crate) fn handle_join_lines( | |
245 | snap: GlobalStateSnapshot, | |
246 | params: lsp_ext::JoinLinesParams, | |
247 | ) -> Result<Vec<lsp_types::TextEdit>> { | |
248 | let _p = profile::span("handle_join_lines"); | |
249 | ||
250 | let config = snap.config.join_lines(); | |
251 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | |
252 | let line_index = snap.file_line_index(file_id)?; | |
253 | ||
254 | let mut res = TextEdit::default(); | |
255 | for range in params.ranges { | |
256 | let range = from_proto::text_range(&line_index, range)?; | |
257 | let edit = snap.analysis.join_lines(&config, FileRange { file_id, range })?; | |
258 | match res.union(edit) { | |
259 | Ok(()) => (), | |
260 | Err(_edit) => { | |
261 | // just ignore overlapping edits | |
262 | } | |
263 | } | |
264 | } | |
265 | ||
266 | Ok(to_proto::text_edit_vec(&line_index, res)) | |
267 | } | |
268 | ||
269 | pub(crate) fn handle_on_enter( | |
270 | snap: GlobalStateSnapshot, | |
271 | params: lsp_types::TextDocumentPositionParams, | |
272 | ) -> Result<Option<Vec<lsp_ext::SnippetTextEdit>>> { | |
273 | let _p = profile::span("handle_on_enter"); | |
274 | let position = from_proto::file_position(&snap, params)?; | |
275 | let edit = match snap.analysis.on_enter(position)? { | |
276 | None => return Ok(None), | |
277 | Some(it) => it, | |
278 | }; | |
279 | let line_index = snap.file_line_index(position.file_id)?; | |
280 | let edit = to_proto::snippet_text_edit_vec(&line_index, true, edit); | |
281 | Ok(Some(edit)) | |
282 | } | |
283 | ||
284 | pub(crate) fn handle_on_type_formatting( | |
285 | snap: GlobalStateSnapshot, | |
286 | params: lsp_types::DocumentOnTypeFormattingParams, | |
287 | ) -> Result<Option<Vec<lsp_ext::SnippetTextEdit>>> { | |
288 | let _p = profile::span("handle_on_type_formatting"); | |
289 | let mut position = from_proto::file_position(&snap, params.text_document_position)?; | |
290 | let line_index = snap.file_line_index(position.file_id)?; | |
291 | ||
292 | // in `ide`, the `on_type` invariant is that | |
293 | // `text.char_at(position) == typed_char`. | |
294 | position.offset -= TextSize::of('.'); | |
295 | let char_typed = params.ch.chars().next().unwrap_or('\0'); | |
296 | ||
297 | let text = snap.analysis.file_text(position.file_id)?; | |
298 | if stdx::never!(!text[usize::from(position.offset)..].starts_with(char_typed)) { | |
299 | return Ok(None); | |
300 | } | |
301 | ||
302 | // We have an assist that inserts ` ` after typing `->` in `fn foo() ->{`, | |
303 | // but it requires precise cursor positioning to work, and one can't | |
304 | // position the cursor with on_type formatting. So, let's just toggle this | |
305 | // feature off here, hoping that we'll enable it one day, 😿. | |
306 | if char_typed == '>' { | |
307 | return Ok(None); | |
308 | } | |
309 | ||
310 | let edit = | |
311 | snap.analysis.on_char_typed(position, char_typed, snap.config.typing_autoclose_angle())?; | |
312 | let edit = match edit { | |
313 | Some(it) => it, | |
314 | None => return Ok(None), | |
315 | }; | |
316 | ||
317 | // This should be a single-file edit | |
318 | let (_, text_edit) = edit.source_file_edits.into_iter().next().unwrap(); | |
319 | ||
320 | let change = to_proto::snippet_text_edit_vec(&line_index, edit.is_snippet, text_edit); | |
321 | Ok(Some(change)) | |
322 | } | |
323 | ||
324 | pub(crate) fn handle_document_symbol( | |
325 | snap: GlobalStateSnapshot, | |
326 | params: lsp_types::DocumentSymbolParams, | |
327 | ) -> Result<Option<lsp_types::DocumentSymbolResponse>> { | |
328 | let _p = profile::span("handle_document_symbol"); | |
329 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | |
330 | let line_index = snap.file_line_index(file_id)?; | |
331 | ||
332 | let mut parents: Vec<(lsp_types::DocumentSymbol, Option<usize>)> = Vec::new(); | |
333 | ||
334 | for symbol in snap.analysis.file_structure(file_id)? { | |
335 | let mut tags = Vec::new(); | |
336 | if symbol.deprecated { | |
337 | tags.push(SymbolTag::DEPRECATED) | |
338 | }; | |
339 | ||
340 | #[allow(deprecated)] | |
341 | let doc_symbol = lsp_types::DocumentSymbol { | |
342 | name: symbol.label, | |
343 | detail: symbol.detail, | |
344 | kind: to_proto::structure_node_kind(symbol.kind), | |
345 | tags: Some(tags), | |
346 | deprecated: Some(symbol.deprecated), | |
347 | range: to_proto::range(&line_index, symbol.node_range), | |
348 | selection_range: to_proto::range(&line_index, symbol.navigation_range), | |
349 | children: None, | |
350 | }; | |
351 | parents.push((doc_symbol, symbol.parent)); | |
352 | } | |
353 | ||
354 | // Builds hierarchy from a flat list, in reverse order (so that indices | |
355 | // makes sense) | |
356 | let document_symbols = { | |
357 | let mut acc = Vec::new(); | |
358 | while let Some((mut node, parent_idx)) = parents.pop() { | |
359 | if let Some(children) = &mut node.children { | |
360 | children.reverse(); | |
361 | } | |
362 | let parent = match parent_idx { | |
363 | None => &mut acc, | |
364 | Some(i) => parents[i].0.children.get_or_insert_with(Vec::new), | |
365 | }; | |
366 | parent.push(node); | |
367 | } | |
368 | acc.reverse(); | |
369 | acc | |
370 | }; | |
371 | ||
372 | let res = if snap.config.hierarchical_symbols() { | |
373 | document_symbols.into() | |
374 | } else { | |
375 | let url = to_proto::url(&snap, file_id); | |
376 | let mut symbol_information = Vec::<SymbolInformation>::new(); | |
377 | for symbol in document_symbols { | |
378 | flatten_document_symbol(&symbol, None, &url, &mut symbol_information); | |
379 | } | |
380 | symbol_information.into() | |
381 | }; | |
382 | return Ok(Some(res)); | |
383 | ||
384 | fn flatten_document_symbol( | |
385 | symbol: &lsp_types::DocumentSymbol, | |
386 | container_name: Option<String>, | |
387 | url: &Url, | |
388 | res: &mut Vec<SymbolInformation>, | |
389 | ) { | |
390 | let mut tags = Vec::new(); | |
391 | ||
392 | #[allow(deprecated)] | |
393 | if let Some(true) = symbol.deprecated { | |
394 | tags.push(SymbolTag::DEPRECATED) | |
395 | } | |
396 | ||
397 | #[allow(deprecated)] | |
398 | res.push(SymbolInformation { | |
399 | name: symbol.name.clone(), | |
400 | kind: symbol.kind, | |
401 | tags: Some(tags), | |
402 | deprecated: symbol.deprecated, | |
403 | location: Location::new(url.clone(), symbol.range), | |
404 | container_name, | |
405 | }); | |
406 | ||
407 | for child in symbol.children.iter().flatten() { | |
408 | flatten_document_symbol(child, Some(symbol.name.clone()), url, res); | |
409 | } | |
410 | } | |
411 | } | |
412 | ||
413 | pub(crate) fn handle_workspace_symbol( | |
414 | snap: GlobalStateSnapshot, | |
415 | params: WorkspaceSymbolParams, | |
416 | ) -> Result<Option<Vec<SymbolInformation>>> { | |
417 | let _p = profile::span("handle_workspace_symbol"); | |
418 | ||
419 | let config = snap.config.workspace_symbol(); | |
420 | let (all_symbols, libs) = decide_search_scope_and_kind(¶ms, &config); | |
421 | let limit = config.search_limit; | |
422 | ||
423 | let query = { | |
424 | let query: String = params.query.chars().filter(|&c| c != '#' && c != '*').collect(); | |
425 | let mut q = Query::new(query); | |
426 | if !all_symbols { | |
427 | q.only_types(); | |
428 | } | |
429 | if libs { | |
430 | q.libs(); | |
431 | } | |
432 | q.limit(limit); | |
433 | q | |
434 | }; | |
435 | let mut res = exec_query(&snap, query)?; | |
436 | if res.is_empty() && !all_symbols { | |
437 | let mut query = Query::new(params.query); | |
438 | query.limit(limit); | |
439 | res = exec_query(&snap, query)?; | |
440 | } | |
441 | ||
442 | return Ok(Some(res)); | |
443 | ||
444 | fn decide_search_scope_and_kind( | |
445 | params: &WorkspaceSymbolParams, | |
446 | config: &WorkspaceSymbolConfig, | |
447 | ) -> (bool, bool) { | |
448 | // Support old-style parsing of markers in the query. | |
449 | let mut all_symbols = params.query.contains('#'); | |
450 | let mut libs = params.query.contains('*'); | |
451 | ||
452 | // If no explicit marker was set, check request params. If that's also empty | |
453 | // use global config. | |
454 | if !all_symbols { | |
455 | let search_kind = match params.search_kind { | |
456 | Some(ref search_kind) => search_kind, | |
457 | None => &config.search_kind, | |
458 | }; | |
459 | all_symbols = match search_kind { | |
460 | lsp_ext::WorkspaceSymbolSearchKind::OnlyTypes => false, | |
461 | lsp_ext::WorkspaceSymbolSearchKind::AllSymbols => true, | |
462 | } | |
463 | } | |
464 | ||
465 | if !libs { | |
466 | let search_scope = match params.search_scope { | |
467 | Some(ref search_scope) => search_scope, | |
468 | None => &config.search_scope, | |
469 | }; | |
470 | libs = match search_scope { | |
471 | lsp_ext::WorkspaceSymbolSearchScope::Workspace => false, | |
472 | lsp_ext::WorkspaceSymbolSearchScope::WorkspaceAndDependencies => true, | |
473 | } | |
474 | } | |
475 | ||
476 | (all_symbols, libs) | |
477 | } | |
478 | ||
479 | fn exec_query(snap: &GlobalStateSnapshot, query: Query) -> Result<Vec<SymbolInformation>> { | |
480 | let mut res = Vec::new(); | |
481 | for nav in snap.analysis.symbol_search(query)? { | |
482 | let container_name = nav.container_name.as_ref().map(|v| v.to_string()); | |
483 | ||
484 | #[allow(deprecated)] | |
485 | let info = SymbolInformation { | |
486 | name: nav.name.to_string(), | |
487 | kind: nav | |
488 | .kind | |
489 | .map(to_proto::symbol_kind) | |
490 | .unwrap_or(lsp_types::SymbolKind::VARIABLE), | |
491 | tags: None, | |
492 | location: to_proto::location_from_nav(snap, nav)?, | |
493 | container_name, | |
494 | deprecated: None, | |
495 | }; | |
496 | res.push(info); | |
497 | } | |
498 | Ok(res) | |
499 | } | |
500 | } | |
501 | ||
502 | pub(crate) fn handle_will_rename_files( | |
503 | snap: GlobalStateSnapshot, | |
504 | params: lsp_types::RenameFilesParams, | |
505 | ) -> Result<Option<lsp_types::WorkspaceEdit>> { | |
506 | let _p = profile::span("handle_will_rename_files"); | |
507 | ||
508 | let source_changes: Vec<SourceChange> = params | |
509 | .files | |
510 | .into_iter() | |
511 | .filter_map(|file_rename| { | |
512 | let from = Url::parse(&file_rename.old_uri).ok()?; | |
513 | let to = Url::parse(&file_rename.new_uri).ok()?; | |
514 | ||
515 | let from_path = from.to_file_path().ok()?; | |
516 | let to_path = to.to_file_path().ok()?; | |
517 | ||
518 | // Limit to single-level moves for now. | |
519 | match (from_path.parent(), to_path.parent()) { | |
520 | (Some(p1), Some(p2)) if p1 == p2 => { | |
521 | if from_path.is_dir() { | |
522 | // add '/' to end of url -- from `file://path/to/folder` to `file://path/to/folder/` | |
523 | let mut old_folder_name = from_path.file_stem()?.to_str()?.to_string(); | |
524 | old_folder_name.push('/'); | |
525 | let from_with_trailing_slash = from.join(&old_folder_name).ok()?; | |
526 | ||
527 | let imitate_from_url = from_with_trailing_slash.join("mod.rs").ok()?; | |
528 | let new_file_name = to_path.file_name()?.to_str()?; | |
529 | Some(( | |
530 | snap.url_to_file_id(&imitate_from_url).ok()?, | |
531 | new_file_name.to_string(), | |
532 | )) | |
533 | } else { | |
534 | let old_name = from_path.file_stem()?.to_str()?; | |
535 | let new_name = to_path.file_stem()?.to_str()?; | |
536 | match (old_name, new_name) { | |
537 | ("mod", _) => None, | |
538 | (_, "mod") => None, | |
539 | _ => Some((snap.url_to_file_id(&from).ok()?, new_name.to_string())), | |
540 | } | |
541 | } | |
542 | } | |
543 | _ => None, | |
544 | } | |
545 | }) | |
546 | .filter_map(|(file_id, new_name)| { | |
547 | snap.analysis.will_rename_file(file_id, &new_name).ok()? | |
548 | }) | |
549 | .collect(); | |
550 | ||
551 | // Drop file system edits since we're just renaming things on the same level | |
552 | let mut source_changes = source_changes.into_iter(); | |
553 | let mut source_change = source_changes.next().unwrap_or_default(); | |
554 | source_change.file_system_edits.clear(); | |
555 | // no collect here because we want to merge text edits on same file ids | |
556 | source_change.extend(source_changes.flat_map(|it| it.source_file_edits)); | |
557 | if source_change.source_file_edits.is_empty() { | |
558 | Ok(None) | |
559 | } else { | |
487cf647 | 560 | Ok(Some(to_proto::workspace_edit(&snap, source_change)?)) |
064997fb FG |
561 | } |
562 | } | |
563 | ||
564 | pub(crate) fn handle_goto_definition( | |
565 | snap: GlobalStateSnapshot, | |
566 | params: lsp_types::GotoDefinitionParams, | |
567 | ) -> Result<Option<lsp_types::GotoDefinitionResponse>> { | |
568 | let _p = profile::span("handle_goto_definition"); | |
569 | let position = from_proto::file_position(&snap, params.text_document_position_params)?; | |
570 | let nav_info = match snap.analysis.goto_definition(position)? { | |
571 | None => return Ok(None), | |
572 | Some(it) => it, | |
573 | }; | |
574 | let src = FileRange { file_id: position.file_id, range: nav_info.range }; | |
575 | let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?; | |
576 | Ok(Some(res)) | |
577 | } | |
578 | ||
579 | pub(crate) fn handle_goto_declaration( | |
580 | snap: GlobalStateSnapshot, | |
581 | params: lsp_types::request::GotoDeclarationParams, | |
582 | ) -> Result<Option<lsp_types::request::GotoDeclarationResponse>> { | |
583 | let _p = profile::span("handle_goto_declaration"); | |
584 | let position = from_proto::file_position(&snap, params.text_document_position_params.clone())?; | |
585 | let nav_info = match snap.analysis.goto_declaration(position)? { | |
586 | None => return handle_goto_definition(snap, params), | |
587 | Some(it) => it, | |
588 | }; | |
589 | let src = FileRange { file_id: position.file_id, range: nav_info.range }; | |
590 | let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?; | |
591 | Ok(Some(res)) | |
592 | } | |
593 | ||
594 | pub(crate) fn handle_goto_implementation( | |
595 | snap: GlobalStateSnapshot, | |
596 | params: lsp_types::request::GotoImplementationParams, | |
597 | ) -> Result<Option<lsp_types::request::GotoImplementationResponse>> { | |
598 | let _p = profile::span("handle_goto_implementation"); | |
599 | let position = from_proto::file_position(&snap, params.text_document_position_params)?; | |
600 | let nav_info = match snap.analysis.goto_implementation(position)? { | |
601 | None => return Ok(None), | |
602 | Some(it) => it, | |
603 | }; | |
604 | let src = FileRange { file_id: position.file_id, range: nav_info.range }; | |
605 | let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?; | |
606 | Ok(Some(res)) | |
607 | } | |
608 | ||
609 | pub(crate) fn handle_goto_type_definition( | |
610 | snap: GlobalStateSnapshot, | |
611 | params: lsp_types::request::GotoTypeDefinitionParams, | |
612 | ) -> Result<Option<lsp_types::request::GotoTypeDefinitionResponse>> { | |
613 | let _p = profile::span("handle_goto_type_definition"); | |
614 | let position = from_proto::file_position(&snap, params.text_document_position_params)?; | |
615 | let nav_info = match snap.analysis.goto_type_definition(position)? { | |
616 | None => return Ok(None), | |
617 | Some(it) => it, | |
618 | }; | |
619 | let src = FileRange { file_id: position.file_id, range: nav_info.range }; | |
620 | let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?; | |
621 | Ok(Some(res)) | |
622 | } | |
623 | ||
624 | pub(crate) fn handle_parent_module( | |
625 | snap: GlobalStateSnapshot, | |
626 | params: lsp_types::TextDocumentPositionParams, | |
627 | ) -> Result<Option<lsp_types::GotoDefinitionResponse>> { | |
628 | let _p = profile::span("handle_parent_module"); | |
629 | if let Ok(file_path) = ¶ms.text_document.uri.to_file_path() { | |
630 | if file_path.file_name().unwrap_or_default() == "Cargo.toml" { | |
631 | // search workspaces for parent packages or fallback to workspace root | |
632 | let abs_path_buf = match AbsPathBuf::try_from(file_path.to_path_buf()).ok() { | |
633 | Some(abs_path_buf) => abs_path_buf, | |
634 | None => return Ok(None), | |
635 | }; | |
636 | ||
637 | let manifest_path = match ManifestPath::try_from(abs_path_buf).ok() { | |
638 | Some(manifest_path) => manifest_path, | |
639 | None => return Ok(None), | |
640 | }; | |
641 | ||
642 | let links: Vec<LocationLink> = snap | |
643 | .workspaces | |
644 | .iter() | |
645 | .filter_map(|ws| match ws { | |
646 | ProjectWorkspace::Cargo { cargo, .. } => cargo.parent_manifests(&manifest_path), | |
647 | _ => None, | |
648 | }) | |
649 | .flatten() | |
650 | .map(|parent_manifest_path| LocationLink { | |
651 | origin_selection_range: None, | |
652 | target_uri: to_proto::url_from_abs_path(&parent_manifest_path), | |
653 | target_range: Range::default(), | |
654 | target_selection_range: Range::default(), | |
655 | }) | |
656 | .collect::<_>(); | |
657 | return Ok(Some(links.into())); | |
658 | } | |
659 | ||
660 | // check if invoked at the crate root | |
661 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | |
2b03887a | 662 | let crate_id = match snap.analysis.crates_for(file_id)?.first() { |
064997fb FG |
663 | Some(&crate_id) => crate_id, |
664 | None => return Ok(None), | |
665 | }; | |
666 | let cargo_spec = match CargoTargetSpec::for_file(&snap, file_id)? { | |
667 | Some(it) => it, | |
668 | None => return Ok(None), | |
669 | }; | |
670 | ||
671 | if snap.analysis.crate_root(crate_id)? == file_id { | |
672 | let cargo_toml_url = to_proto::url_from_abs_path(&cargo_spec.cargo_toml); | |
673 | let res = vec![LocationLink { | |
674 | origin_selection_range: None, | |
675 | target_uri: cargo_toml_url, | |
676 | target_range: Range::default(), | |
677 | target_selection_range: Range::default(), | |
678 | }] | |
679 | .into(); | |
680 | return Ok(Some(res)); | |
681 | } | |
682 | } | |
683 | ||
684 | // locate parent module by semantics | |
685 | let position = from_proto::file_position(&snap, params)?; | |
686 | let navs = snap.analysis.parent_module(position)?; | |
687 | let res = to_proto::goto_definition_response(&snap, None, navs)?; | |
688 | Ok(Some(res)) | |
689 | } | |
690 | ||
691 | pub(crate) fn handle_runnables( | |
692 | snap: GlobalStateSnapshot, | |
693 | params: lsp_ext::RunnablesParams, | |
694 | ) -> Result<Vec<lsp_ext::Runnable>> { | |
695 | let _p = profile::span("handle_runnables"); | |
696 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | |
697 | let line_index = snap.file_line_index(file_id)?; | |
698 | let offset = params.position.and_then(|it| from_proto::offset(&line_index, it).ok()); | |
699 | let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?; | |
700 | ||
701 | let expect_test = match offset { | |
702 | Some(offset) => { | |
703 | let source_file = snap.analysis.parse(file_id)?; | |
704 | algo::find_node_at_offset::<ast::MacroCall>(source_file.syntax(), offset) | |
705 | .and_then(|it| it.path()?.segment()?.name_ref()) | |
706 | .map_or(false, |it| it.text() == "expect" || it.text() == "expect_file") | |
707 | } | |
708 | None => false, | |
709 | }; | |
710 | ||
711 | let mut res = Vec::new(); | |
712 | for runnable in snap.analysis.runnables(file_id)? { | |
f2b60f7d FG |
713 | if should_skip_for_offset(&runnable, offset) { |
714 | continue; | |
064997fb FG |
715 | } |
716 | if should_skip_target(&runnable, cargo_spec.as_ref()) { | |
717 | continue; | |
718 | } | |
719 | let mut runnable = to_proto::runnable(&snap, runnable)?; | |
720 | if expect_test { | |
721 | runnable.label = format!("{} + expect", runnable.label); | |
722 | runnable.args.expect_test = Some(true); | |
723 | } | |
724 | res.push(runnable); | |
725 | } | |
726 | ||
727 | // Add `cargo check` and `cargo test` for all targets of the whole package | |
728 | let config = snap.config.runnables(); | |
729 | match cargo_spec { | |
730 | Some(spec) => { | |
731 | for cmd in ["check", "test"] { | |
732 | res.push(lsp_ext::Runnable { | |
f25598a0 | 733 | label: format!("cargo {cmd} -p {} --all-targets", spec.package), |
064997fb FG |
734 | location: None, |
735 | kind: lsp_ext::RunnableKind::Cargo, | |
736 | args: lsp_ext::CargoRunnable { | |
737 | workspace_root: Some(spec.workspace_root.clone().into()), | |
738 | override_cargo: config.override_cargo.clone(), | |
739 | cargo_args: vec![ | |
740 | cmd.to_string(), | |
741 | "--package".to_string(), | |
742 | spec.package.clone(), | |
743 | "--all-targets".to_string(), | |
744 | ], | |
745 | cargo_extra_args: config.cargo_extra_args.clone(), | |
746 | executable_args: Vec::new(), | |
747 | expect_test: None, | |
748 | }, | |
749 | }) | |
750 | } | |
751 | } | |
752 | None => { | |
753 | if !snap.config.linked_projects().is_empty() | |
754 | || !snap | |
755 | .config | |
756 | .discovered_projects | |
757 | .as_ref() | |
758 | .map(|projects| projects.is_empty()) | |
759 | .unwrap_or(true) | |
760 | { | |
761 | res.push(lsp_ext::Runnable { | |
762 | label: "cargo check --workspace".to_string(), | |
763 | location: None, | |
764 | kind: lsp_ext::RunnableKind::Cargo, | |
765 | args: lsp_ext::CargoRunnable { | |
766 | workspace_root: None, | |
767 | override_cargo: config.override_cargo, | |
768 | cargo_args: vec!["check".to_string(), "--workspace".to_string()], | |
769 | cargo_extra_args: config.cargo_extra_args, | |
770 | executable_args: Vec::new(), | |
771 | expect_test: None, | |
772 | }, | |
773 | }); | |
774 | } | |
775 | } | |
776 | } | |
777 | Ok(res) | |
778 | } | |
779 | ||
f2b60f7d FG |
780 | fn should_skip_for_offset(runnable: &Runnable, offset: Option<TextSize>) -> bool { |
781 | match offset { | |
782 | None => false, | |
783 | _ if matches!(&runnable.kind, RunnableKind::TestMod { .. }) => false, | |
784 | Some(offset) => !runnable.nav.full_range.contains_inclusive(offset), | |
785 | } | |
786 | } | |
787 | ||
064997fb FG |
788 | pub(crate) fn handle_related_tests( |
789 | snap: GlobalStateSnapshot, | |
790 | params: lsp_types::TextDocumentPositionParams, | |
791 | ) -> Result<Vec<lsp_ext::TestInfo>> { | |
792 | let _p = profile::span("handle_related_tests"); | |
793 | let position = from_proto::file_position(&snap, params)?; | |
794 | ||
795 | let tests = snap.analysis.related_tests(position, None)?; | |
796 | let mut res = Vec::new(); | |
797 | for it in tests { | |
798 | if let Ok(runnable) = to_proto::runnable(&snap, it) { | |
799 | res.push(lsp_ext::TestInfo { runnable }) | |
800 | } | |
801 | } | |
802 | ||
803 | Ok(res) | |
804 | } | |
805 | ||
806 | pub(crate) fn handle_completion( | |
807 | snap: GlobalStateSnapshot, | |
808 | params: lsp_types::CompletionParams, | |
809 | ) -> Result<Option<lsp_types::CompletionResponse>> { | |
810 | let _p = profile::span("handle_completion"); | |
811 | let text_document_position = params.text_document_position.clone(); | |
812 | let position = from_proto::file_position(&snap, params.text_document_position)?; | |
813 | let completion_trigger_character = | |
814 | params.context.and_then(|ctx| ctx.trigger_character).and_then(|s| s.chars().next()); | |
815 | ||
064997fb FG |
816 | let completion_config = &snap.config.completion(); |
817 | let items = match snap.analysis.completions( | |
818 | completion_config, | |
819 | position, | |
820 | completion_trigger_character, | |
821 | )? { | |
822 | None => return Ok(None), | |
823 | Some(items) => items, | |
824 | }; | |
825 | let line_index = snap.file_line_index(position.file_id)?; | |
826 | ||
827 | let items = | |
828 | to_proto::completion_items(&snap.config, &line_index, text_document_position, items); | |
829 | ||
830 | let completion_list = lsp_types::CompletionList { is_incomplete: true, items }; | |
831 | Ok(Some(completion_list.into())) | |
832 | } | |
833 | ||
834 | pub(crate) fn handle_completion_resolve( | |
835 | snap: GlobalStateSnapshot, | |
836 | mut original_completion: CompletionItem, | |
837 | ) -> Result<CompletionItem> { | |
838 | let _p = profile::span("handle_completion_resolve"); | |
839 | ||
840 | if !all_edits_are_disjoint(&original_completion, &[]) { | |
841 | return Err(invalid_params_error( | |
842 | "Received a completion with overlapping edits, this is not LSP-compliant".to_string(), | |
843 | ) | |
844 | .into()); | |
845 | } | |
846 | ||
847 | let data = match original_completion.data.take() { | |
848 | Some(it) => it, | |
849 | None => return Ok(original_completion), | |
850 | }; | |
851 | ||
852 | let resolve_data: lsp_ext::CompletionResolveData = serde_json::from_value(data)?; | |
853 | ||
854 | let file_id = from_proto::file_id(&snap, &resolve_data.position.text_document.uri)?; | |
855 | let line_index = snap.file_line_index(file_id)?; | |
856 | let offset = from_proto::offset(&line_index, resolve_data.position.position)?; | |
857 | ||
858 | let additional_edits = snap | |
859 | .analysis | |
860 | .resolve_completion_edits( | |
861 | &snap.config.completion(), | |
862 | FilePosition { file_id, offset }, | |
863 | resolve_data | |
864 | .imports | |
865 | .into_iter() | |
866 | .map(|import| (import.full_import_path, import.imported_name)), | |
867 | )? | |
868 | .into_iter() | |
869 | .flat_map(|edit| edit.into_iter().map(|indel| to_proto::text_edit(&line_index, indel))) | |
870 | .collect::<Vec<_>>(); | |
871 | ||
872 | if !all_edits_are_disjoint(&original_completion, &additional_edits) { | |
873 | return Err(LspError::new( | |
874 | ErrorCode::InternalError as i32, | |
875 | "Import edit overlaps with the original completion edits, this is not LSP-compliant" | |
876 | .into(), | |
877 | ) | |
878 | .into()); | |
879 | } | |
880 | ||
881 | if let Some(original_additional_edits) = original_completion.additional_text_edits.as_mut() { | |
882 | original_additional_edits.extend(additional_edits.into_iter()) | |
883 | } else { | |
884 | original_completion.additional_text_edits = Some(additional_edits); | |
885 | } | |
886 | ||
887 | Ok(original_completion) | |
888 | } | |
889 | ||
890 | pub(crate) fn handle_folding_range( | |
891 | snap: GlobalStateSnapshot, | |
892 | params: FoldingRangeParams, | |
893 | ) -> Result<Option<Vec<FoldingRange>>> { | |
894 | let _p = profile::span("handle_folding_range"); | |
895 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | |
896 | let folds = snap.analysis.folding_ranges(file_id)?; | |
897 | let text = snap.analysis.file_text(file_id)?; | |
898 | let line_index = snap.file_line_index(file_id)?; | |
899 | let line_folding_only = snap.config.line_folding_only(); | |
900 | let res = folds | |
901 | .into_iter() | |
f25598a0 | 902 | .map(|it| to_proto::folding_range(&text, &line_index, line_folding_only, it)) |
064997fb FG |
903 | .collect(); |
904 | Ok(Some(res)) | |
905 | } | |
906 | ||
907 | pub(crate) fn handle_signature_help( | |
908 | snap: GlobalStateSnapshot, | |
909 | params: lsp_types::SignatureHelpParams, | |
910 | ) -> Result<Option<lsp_types::SignatureHelp>> { | |
911 | let _p = profile::span("handle_signature_help"); | |
912 | let position = from_proto::file_position(&snap, params.text_document_position_params)?; | |
913 | let help = match snap.analysis.signature_help(position)? { | |
914 | Some(it) => it, | |
915 | None => return Ok(None), | |
916 | }; | |
917 | let config = snap.config.call_info(); | |
918 | let res = to_proto::signature_help(help, config, snap.config.signature_help_label_offsets()); | |
919 | Ok(Some(res)) | |
920 | } | |
921 | ||
922 | pub(crate) fn handle_hover( | |
923 | snap: GlobalStateSnapshot, | |
924 | params: lsp_ext::HoverParams, | |
925 | ) -> Result<Option<lsp_ext::Hover>> { | |
926 | let _p = profile::span("handle_hover"); | |
927 | let range = match params.position { | |
928 | PositionOrRange::Position(position) => Range::new(position, position), | |
929 | PositionOrRange::Range(range) => range, | |
930 | }; | |
931 | ||
932 | let file_range = from_proto::file_range(&snap, params.text_document, range)?; | |
933 | let info = match snap.analysis.hover(&snap.config.hover(), file_range)? { | |
934 | None => return Ok(None), | |
935 | Some(info) => info, | |
936 | }; | |
937 | ||
938 | let line_index = snap.file_line_index(file_range.file_id)?; | |
939 | let range = to_proto::range(&line_index, info.range); | |
940 | let markup_kind = | |
941 | snap.config.hover().documentation.map_or(ide::HoverDocFormat::Markdown, |kind| kind); | |
942 | let hover = lsp_ext::Hover { | |
943 | hover: lsp_types::Hover { | |
944 | contents: HoverContents::Markup(to_proto::markup_content( | |
945 | info.info.markup, | |
946 | markup_kind, | |
947 | )), | |
948 | range: Some(range), | |
949 | }, | |
950 | actions: if snap.config.hover_actions().none() { | |
951 | Vec::new() | |
952 | } else { | |
953 | prepare_hover_actions(&snap, &info.info.actions) | |
954 | }, | |
955 | }; | |
956 | ||
957 | Ok(Some(hover)) | |
958 | } | |
959 | ||
960 | pub(crate) fn handle_prepare_rename( | |
961 | snap: GlobalStateSnapshot, | |
962 | params: lsp_types::TextDocumentPositionParams, | |
963 | ) -> Result<Option<PrepareRenameResponse>> { | |
964 | let _p = profile::span("handle_prepare_rename"); | |
965 | let position = from_proto::file_position(&snap, params)?; | |
966 | ||
967 | let change = snap.analysis.prepare_rename(position)?.map_err(to_proto::rename_error)?; | |
968 | ||
969 | let line_index = snap.file_line_index(position.file_id)?; | |
970 | let range = to_proto::range(&line_index, change.range); | |
971 | Ok(Some(PrepareRenameResponse::Range(range))) | |
972 | } | |
973 | ||
974 | pub(crate) fn handle_rename( | |
975 | snap: GlobalStateSnapshot, | |
976 | params: RenameParams, | |
977 | ) -> Result<Option<WorkspaceEdit>> { | |
978 | let _p = profile::span("handle_rename"); | |
979 | let position = from_proto::file_position(&snap, params.text_document_position)?; | |
980 | ||
981 | let mut change = | |
f25598a0 | 982 | snap.analysis.rename(position, ¶ms.new_name)?.map_err(to_proto::rename_error)?; |
064997fb FG |
983 | |
984 | // this is kind of a hack to prevent double edits from happening when moving files | |
985 | // When a module gets renamed by renaming the mod declaration this causes the file to move | |
986 | // which in turn will trigger a WillRenameFiles request to the server for which we reply with a | |
987 | // a second identical set of renames, the client will then apply both edits causing incorrect edits | |
988 | // with this we only emit source_file_edits in the WillRenameFiles response which will do the rename instead | |
989 | // See https://github.com/microsoft/vscode-languageserver-node/issues/752 for more info | |
990 | if !change.file_system_edits.is_empty() && snap.config.will_rename() { | |
991 | change.source_file_edits.clear(); | |
992 | } | |
993 | let workspace_edit = to_proto::workspace_edit(&snap, change)?; | |
994 | Ok(Some(workspace_edit)) | |
995 | } | |
996 | ||
997 | pub(crate) fn handle_references( | |
998 | snap: GlobalStateSnapshot, | |
999 | params: lsp_types::ReferenceParams, | |
1000 | ) -> Result<Option<Vec<Location>>> { | |
1001 | let _p = profile::span("handle_references"); | |
1002 | let position = from_proto::file_position(&snap, params.text_document_position)?; | |
1003 | ||
2b03887a FG |
1004 | let exclude_imports = snap.config.find_all_refs_exclude_imports(); |
1005 | ||
064997fb FG |
1006 | let refs = match snap.analysis.find_all_refs(position, None)? { |
1007 | None => return Ok(None), | |
1008 | Some(refs) => refs, | |
1009 | }; | |
1010 | ||
1011 | let include_declaration = params.context.include_declaration; | |
1012 | let locations = refs | |
1013 | .into_iter() | |
1014 | .flat_map(|refs| { | |
1015 | let decl = if include_declaration { | |
1016 | refs.declaration.map(|decl| FileRange { | |
1017 | file_id: decl.nav.file_id, | |
1018 | range: decl.nav.focus_or_full_range(), | |
1019 | }) | |
1020 | } else { | |
1021 | None | |
1022 | }; | |
1023 | refs.references | |
1024 | .into_iter() | |
1025 | .flat_map(|(file_id, refs)| { | |
2b03887a FG |
1026 | refs.into_iter() |
1027 | .filter(|&(_, category)| { | |
1028 | !exclude_imports || category != Some(ReferenceCategory::Import) | |
1029 | }) | |
1030 | .map(move |(range, _)| FileRange { file_id, range }) | |
064997fb FG |
1031 | }) |
1032 | .chain(decl) | |
1033 | }) | |
1034 | .filter_map(|frange| to_proto::location(&snap, frange).ok()) | |
1035 | .collect(); | |
1036 | ||
1037 | Ok(Some(locations)) | |
1038 | } | |
1039 | ||
1040 | pub(crate) fn handle_formatting( | |
1041 | snap: GlobalStateSnapshot, | |
1042 | params: DocumentFormattingParams, | |
1043 | ) -> Result<Option<Vec<lsp_types::TextEdit>>> { | |
1044 | let _p = profile::span("handle_formatting"); | |
1045 | ||
1046 | run_rustfmt(&snap, params.text_document, None) | |
1047 | } | |
1048 | ||
1049 | pub(crate) fn handle_range_formatting( | |
1050 | snap: GlobalStateSnapshot, | |
1051 | params: lsp_types::DocumentRangeFormattingParams, | |
1052 | ) -> Result<Option<Vec<lsp_types::TextEdit>>> { | |
1053 | let _p = profile::span("handle_range_formatting"); | |
1054 | ||
1055 | run_rustfmt(&snap, params.text_document, Some(params.range)) | |
1056 | } | |
1057 | ||
1058 | pub(crate) fn handle_code_action( | |
1059 | snap: GlobalStateSnapshot, | |
1060 | params: lsp_types::CodeActionParams, | |
1061 | ) -> Result<Option<Vec<lsp_ext::CodeAction>>> { | |
1062 | let _p = profile::span("handle_code_action"); | |
1063 | ||
1064 | if !snap.config.code_action_literals() { | |
1065 | // We intentionally don't support command-based actions, as those either | |
1066 | // require either custom client-code or server-initiated edits. Server | |
1067 | // initiated edits break causality, so we avoid those. | |
1068 | return Ok(None); | |
1069 | } | |
1070 | ||
1071 | let line_index = | |
1072 | snap.file_line_index(from_proto::file_id(&snap, ¶ms.text_document.uri)?)?; | |
1073 | let frange = from_proto::file_range(&snap, params.text_document.clone(), params.range)?; | |
1074 | ||
1075 | let mut assists_config = snap.config.assist(); | |
1076 | assists_config.allowed = params | |
1077 | .context | |
1078 | .only | |
1079 | .clone() | |
1080 | .map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect()); | |
1081 | ||
1082 | let mut res: Vec<lsp_ext::CodeAction> = Vec::new(); | |
1083 | ||
1084 | let code_action_resolve_cap = snap.config.code_action_resolve(); | |
1085 | let resolve = if code_action_resolve_cap { | |
1086 | AssistResolveStrategy::None | |
1087 | } else { | |
1088 | AssistResolveStrategy::All | |
1089 | }; | |
1090 | let assists = snap.analysis.assists_with_fixes( | |
1091 | &assists_config, | |
1092 | &snap.config.diagnostics(), | |
1093 | resolve, | |
1094 | frange, | |
1095 | )?; | |
1096 | for (index, assist) in assists.into_iter().enumerate() { | |
1097 | let resolve_data = | |
1098 | if code_action_resolve_cap { Some((index, params.clone())) } else { None }; | |
1099 | let code_action = to_proto::code_action(&snap, assist, resolve_data)?; | |
1100 | res.push(code_action) | |
1101 | } | |
1102 | ||
1103 | // Fixes from `cargo check`. | |
f25598a0 | 1104 | for fix in snap.check_fixes.values().filter_map(|it| it.get(&frange.file_id)).flatten() { |
064997fb FG |
1105 | // FIXME: this mapping is awkward and shouldn't exist. Refactor |
1106 | // `snap.check_fixes` to not convert to LSP prematurely. | |
1107 | let intersect_fix_range = fix | |
1108 | .ranges | |
1109 | .iter() | |
1110 | .copied() | |
1111 | .filter_map(|range| from_proto::text_range(&line_index, range).ok()) | |
1112 | .any(|fix_range| fix_range.intersect(frange.range).is_some()); | |
1113 | if intersect_fix_range { | |
1114 | res.push(fix.action.clone()); | |
1115 | } | |
1116 | } | |
1117 | ||
1118 | Ok(Some(res)) | |
1119 | } | |
1120 | ||
1121 | pub(crate) fn handle_code_action_resolve( | |
1122 | snap: GlobalStateSnapshot, | |
1123 | mut code_action: lsp_ext::CodeAction, | |
1124 | ) -> Result<lsp_ext::CodeAction> { | |
1125 | let _p = profile::span("handle_code_action_resolve"); | |
1126 | let params = match code_action.data.take() { | |
1127 | Some(it) => it, | |
1128 | None => return Err(invalid_params_error("code action without data".to_string()).into()), | |
1129 | }; | |
1130 | ||
1131 | let file_id = from_proto::file_id(&snap, ¶ms.code_action_params.text_document.uri)?; | |
1132 | let line_index = snap.file_line_index(file_id)?; | |
1133 | let range = from_proto::text_range(&line_index, params.code_action_params.range)?; | |
1134 | let frange = FileRange { file_id, range }; | |
1135 | ||
1136 | let mut assists_config = snap.config.assist(); | |
1137 | assists_config.allowed = params | |
1138 | .code_action_params | |
1139 | .context | |
1140 | .only | |
1141 | .map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect()); | |
1142 | ||
1143 | let (assist_index, assist_resolve) = match parse_action_id(¶ms.id) { | |
1144 | Ok(parsed_data) => parsed_data, | |
1145 | Err(e) => { | |
1146 | return Err(invalid_params_error(format!( | |
f25598a0 FG |
1147 | "Failed to parse action id string '{}': {e}", |
1148 | params.id | |
064997fb FG |
1149 | )) |
1150 | .into()) | |
1151 | } | |
1152 | }; | |
1153 | ||
1154 | let expected_assist_id = assist_resolve.assist_id.clone(); | |
1155 | let expected_kind = assist_resolve.assist_kind; | |
1156 | ||
1157 | let assists = snap.analysis.assists_with_fixes( | |
1158 | &assists_config, | |
1159 | &snap.config.diagnostics(), | |
1160 | AssistResolveStrategy::Single(assist_resolve), | |
1161 | frange, | |
1162 | )?; | |
1163 | ||
1164 | let assist = match assists.get(assist_index) { | |
1165 | Some(assist) => assist, | |
1166 | None => return Err(invalid_params_error(format!( | |
1167 | "Failed to find the assist for index {} provided by the resolve request. Resolve request assist id: {}", | |
1168 | assist_index, params.id, | |
1169 | )) | |
1170 | .into()) | |
1171 | }; | |
1172 | if assist.id.0 != expected_assist_id || assist.id.1 != expected_kind { | |
1173 | return Err(invalid_params_error(format!( | |
1174 | "Mismatching assist at index {} for the resolve parameters given. Resolve request assist id: {}, actual id: {:?}.", | |
1175 | assist_index, params.id, assist.id | |
1176 | )) | |
1177 | .into()); | |
1178 | } | |
1179 | let ca = to_proto::code_action(&snap, assist.clone(), None)?; | |
1180 | code_action.edit = ca.edit; | |
1181 | code_action.command = ca.command; | |
1182 | Ok(code_action) | |
1183 | } | |
1184 | ||
1185 | fn parse_action_id(action_id: &str) -> Result<(usize, SingleResolve), String> { | |
1186 | let id_parts = action_id.split(':').collect::<Vec<_>>(); | |
1187 | match id_parts.as_slice() { | |
1188 | [assist_id_string, assist_kind_string, index_string] => { | |
1189 | let assist_kind: AssistKind = assist_kind_string.parse()?; | |
1190 | let index: usize = match index_string.parse() { | |
1191 | Ok(index) => index, | |
f25598a0 | 1192 | Err(e) => return Err(format!("Incorrect index string: {e}")), |
064997fb FG |
1193 | }; |
1194 | Ok((index, SingleResolve { assist_id: assist_id_string.to_string(), assist_kind })) | |
1195 | } | |
1196 | _ => Err("Action id contains incorrect number of segments".to_string()), | |
1197 | } | |
1198 | } | |
1199 | ||
1200 | pub(crate) fn handle_code_lens( | |
1201 | snap: GlobalStateSnapshot, | |
1202 | params: lsp_types::CodeLensParams, | |
1203 | ) -> Result<Option<Vec<CodeLens>>> { | |
1204 | let _p = profile::span("handle_code_lens"); | |
1205 | ||
1206 | let lens_config = snap.config.lens(); | |
1207 | if lens_config.none() { | |
1208 | // early return before any db query! | |
1209 | return Ok(Some(Vec::default())); | |
1210 | } | |
1211 | ||
1212 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | |
1213 | let cargo_target_spec = CargoTargetSpec::for_file(&snap, file_id)?; | |
1214 | ||
1215 | let annotations = snap.analysis.annotations( | |
1216 | &AnnotationConfig { | |
1217 | binary_target: cargo_target_spec | |
1218 | .map(|spec| { | |
1219 | matches!( | |
1220 | spec.target_kind, | |
1221 | TargetKind::Bin | TargetKind::Example | TargetKind::Test | |
1222 | ) | |
1223 | }) | |
1224 | .unwrap_or(false), | |
1225 | annotate_runnables: lens_config.runnable(), | |
1226 | annotate_impls: lens_config.implementations, | |
1227 | annotate_references: lens_config.refs_adt, | |
1228 | annotate_method_references: lens_config.method_refs, | |
1229 | annotate_enum_variant_references: lens_config.enum_variant_refs, | |
2b03887a | 1230 | location: lens_config.location.into(), |
064997fb FG |
1231 | }, |
1232 | file_id, | |
1233 | )?; | |
1234 | ||
1235 | let mut res = Vec::new(); | |
1236 | for a in annotations { | |
1237 | to_proto::code_lens(&mut res, &snap, a)?; | |
1238 | } | |
1239 | ||
1240 | Ok(Some(res)) | |
1241 | } | |
1242 | ||
1243 | pub(crate) fn handle_code_lens_resolve( | |
1244 | snap: GlobalStateSnapshot, | |
1245 | code_lens: CodeLens, | |
1246 | ) -> Result<CodeLens> { | |
1247 | let annotation = from_proto::annotation(&snap, code_lens.clone())?; | |
1248 | let annotation = snap.analysis.resolve_annotation(annotation)?; | |
1249 | ||
1250 | let mut acc = Vec::new(); | |
1251 | to_proto::code_lens(&mut acc, &snap, annotation)?; | |
1252 | ||
1253 | let res = match acc.pop() { | |
1254 | Some(it) if acc.is_empty() => it, | |
1255 | _ => { | |
1256 | never!(); | |
1257 | code_lens | |
1258 | } | |
1259 | }; | |
1260 | ||
1261 | Ok(res) | |
1262 | } | |
1263 | ||
1264 | pub(crate) fn handle_document_highlight( | |
1265 | snap: GlobalStateSnapshot, | |
1266 | params: lsp_types::DocumentHighlightParams, | |
1267 | ) -> Result<Option<Vec<lsp_types::DocumentHighlight>>> { | |
1268 | let _p = profile::span("handle_document_highlight"); | |
1269 | let position = from_proto::file_position(&snap, params.text_document_position_params)?; | |
1270 | let line_index = snap.file_line_index(position.file_id)?; | |
1271 | ||
1272 | let refs = match snap.analysis.highlight_related(snap.config.highlight_related(), position)? { | |
1273 | None => return Ok(None), | |
1274 | Some(refs) => refs, | |
1275 | }; | |
1276 | let res = refs | |
1277 | .into_iter() | |
1278 | .map(|ide::HighlightedRange { range, category }| lsp_types::DocumentHighlight { | |
1279 | range: to_proto::range(&line_index, range), | |
2b03887a | 1280 | kind: category.and_then(to_proto::document_highlight_kind), |
064997fb FG |
1281 | }) |
1282 | .collect(); | |
1283 | Ok(Some(res)) | |
1284 | } | |
1285 | ||
1286 | pub(crate) fn handle_ssr( | |
1287 | snap: GlobalStateSnapshot, | |
1288 | params: lsp_ext::SsrParams, | |
1289 | ) -> Result<lsp_types::WorkspaceEdit> { | |
1290 | let _p = profile::span("handle_ssr"); | |
1291 | let selections = params | |
1292 | .selections | |
1293 | .iter() | |
1294 | .map(|range| from_proto::file_range(&snap, params.position.text_document.clone(), *range)) | |
1295 | .collect::<Result<Vec<_>, _>>()?; | |
1296 | let position = from_proto::file_position(&snap, params.position)?; | |
1297 | let source_change = snap.analysis.structural_search_replace( | |
1298 | ¶ms.query, | |
1299 | params.parse_only, | |
1300 | position, | |
1301 | selections, | |
1302 | )??; | |
487cf647 | 1303 | to_proto::workspace_edit(&snap, source_change).map_err(Into::into) |
064997fb FG |
1304 | } |
1305 | ||
1306 | pub(crate) fn publish_diagnostics( | |
1307 | snap: &GlobalStateSnapshot, | |
1308 | file_id: FileId, | |
1309 | ) -> Result<Vec<Diagnostic>> { | |
1310 | let _p = profile::span("publish_diagnostics"); | |
1311 | let line_index = snap.file_line_index(file_id)?; | |
1312 | ||
1313 | let diagnostics: Vec<Diagnostic> = snap | |
1314 | .analysis | |
1315 | .diagnostics(&snap.config.diagnostics(), AssistResolveStrategy::None, file_id)? | |
1316 | .into_iter() | |
1317 | .map(|d| Diagnostic { | |
1318 | range: to_proto::range(&line_index, d.range), | |
1319 | severity: Some(to_proto::diagnostic_severity(d.severity)), | |
1320 | code: Some(NumberOrString::String(d.code.as_str().to_string())), | |
1321 | code_description: Some(lsp_types::CodeDescription { | |
1322 | href: lsp_types::Url::parse(&format!( | |
1323 | "https://rust-analyzer.github.io/manual.html#{}", | |
1324 | d.code.as_str() | |
1325 | )) | |
1326 | .unwrap(), | |
1327 | }), | |
1328 | source: Some("rust-analyzer".to_string()), | |
f2b60f7d | 1329 | message: d.message, |
064997fb FG |
1330 | related_information: None, |
1331 | tags: if d.unused { Some(vec![DiagnosticTag::UNNECESSARY]) } else { None }, | |
1332 | data: None, | |
1333 | }) | |
1334 | .collect(); | |
1335 | Ok(diagnostics) | |
1336 | } | |
1337 | ||
1338 | pub(crate) fn handle_inlay_hints( | |
1339 | snap: GlobalStateSnapshot, | |
1340 | params: InlayHintParams, | |
1341 | ) -> Result<Option<Vec<InlayHint>>> { | |
1342 | let _p = profile::span("handle_inlay_hints"); | |
1343 | let document_uri = ¶ms.text_document.uri; | |
487cf647 | 1344 | let FileRange { file_id, range } = from_proto::file_range( |
064997fb FG |
1345 | &snap, |
1346 | TextDocumentIdentifier::new(document_uri.to_owned()), | |
1347 | params.range, | |
1348 | )?; | |
487cf647 | 1349 | let line_index = snap.file_line_index(file_id)?; |
064997fb FG |
1350 | let inlay_hints_config = snap.config.inlay_hints(); |
1351 | Ok(Some( | |
1352 | snap.analysis | |
1353 | .inlay_hints(&inlay_hints_config, file_id, Some(range))? | |
1354 | .into_iter() | |
1355 | .map(|it| { | |
1356 | to_proto::inlay_hint(&snap, &line_index, inlay_hints_config.render_colons, it) | |
1357 | }) | |
487cf647 | 1358 | .collect::<Cancellable<Vec<_>>>()?, |
064997fb FG |
1359 | )) |
1360 | } | |
1361 | ||
1362 | pub(crate) fn handle_inlay_hints_resolve( | |
1363 | snap: GlobalStateSnapshot, | |
1364 | mut hint: InlayHint, | |
1365 | ) -> Result<InlayHint> { | |
1366 | let _p = profile::span("handle_inlay_hints_resolve"); | |
1367 | let data = match hint.data.take() { | |
1368 | Some(it) => it, | |
1369 | None => return Ok(hint), | |
1370 | }; | |
1371 | ||
1372 | let resolve_data: lsp_ext::InlayHintResolveData = serde_json::from_value(data)?; | |
1373 | ||
f25598a0 FG |
1374 | match snap.url_file_version(&resolve_data.text_document.uri) { |
1375 | Some(version) if version == resolve_data.text_document.version => {} | |
1376 | Some(version) => { | |
1377 | error!( | |
1378 | "attempted inlayHints/resolve of '{}' at version {} while server version is {}", | |
1379 | resolve_data.text_document.uri, resolve_data.text_document.version, version, | |
1380 | ); | |
1381 | return Ok(hint); | |
1382 | } | |
1383 | None => { | |
1384 | error!( | |
1385 | "attempted inlayHints/resolve of unknown file '{}' at version {}", | |
1386 | resolve_data.text_document.uri, resolve_data.text_document.version, | |
1387 | ); | |
1388 | return Ok(hint); | |
1389 | } | |
1390 | } | |
1391 | let file_range = from_proto::file_range_uri( | |
064997fb | 1392 | &snap, |
f25598a0 | 1393 | &resolve_data.text_document.uri, |
064997fb FG |
1394 | match resolve_data.position { |
1395 | PositionOrRange::Position(pos) => Range::new(pos, pos), | |
1396 | PositionOrRange::Range(range) => range, | |
1397 | }, | |
1398 | )?; | |
1399 | let info = match snap.analysis.hover(&snap.config.hover(), file_range)? { | |
1400 | None => return Ok(hint), | |
1401 | Some(info) => info, | |
1402 | }; | |
1403 | ||
1404 | let markup_kind = | |
1405 | snap.config.hover().documentation.map_or(ide::HoverDocFormat::Markdown, |kind| kind); | |
1406 | ||
1407 | // FIXME: hover actions? | |
1408 | hint.tooltip = Some(lsp_types::InlayHintTooltip::MarkupContent(to_proto::markup_content( | |
1409 | info.info.markup, | |
1410 | markup_kind, | |
1411 | ))); | |
1412 | Ok(hint) | |
1413 | } | |
1414 | ||
1415 | pub(crate) fn handle_call_hierarchy_prepare( | |
1416 | snap: GlobalStateSnapshot, | |
1417 | params: CallHierarchyPrepareParams, | |
1418 | ) -> Result<Option<Vec<CallHierarchyItem>>> { | |
1419 | let _p = profile::span("handle_call_hierarchy_prepare"); | |
1420 | let position = from_proto::file_position(&snap, params.text_document_position_params)?; | |
1421 | ||
1422 | let nav_info = match snap.analysis.call_hierarchy(position)? { | |
1423 | None => return Ok(None), | |
1424 | Some(it) => it, | |
1425 | }; | |
1426 | ||
1427 | let RangeInfo { range: _, info: navs } = nav_info; | |
1428 | let res = navs | |
1429 | .into_iter() | |
1430 | .filter(|it| it.kind == Some(SymbolKind::Function)) | |
1431 | .map(|it| to_proto::call_hierarchy_item(&snap, it)) | |
487cf647 | 1432 | .collect::<Cancellable<Vec<_>>>()?; |
064997fb FG |
1433 | |
1434 | Ok(Some(res)) | |
1435 | } | |
1436 | ||
1437 | pub(crate) fn handle_call_hierarchy_incoming( | |
1438 | snap: GlobalStateSnapshot, | |
1439 | params: CallHierarchyIncomingCallsParams, | |
1440 | ) -> Result<Option<Vec<CallHierarchyIncomingCall>>> { | |
1441 | let _p = profile::span("handle_call_hierarchy_incoming"); | |
1442 | let item = params.item; | |
1443 | ||
1444 | let doc = TextDocumentIdentifier::new(item.uri); | |
1445 | let frange = from_proto::file_range(&snap, doc, item.selection_range)?; | |
1446 | let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; | |
1447 | ||
1448 | let call_items = match snap.analysis.incoming_calls(fpos)? { | |
1449 | None => return Ok(None), | |
1450 | Some(it) => it, | |
1451 | }; | |
1452 | ||
1453 | let mut res = vec![]; | |
1454 | ||
1455 | for call_item in call_items.into_iter() { | |
1456 | let file_id = call_item.target.file_id; | |
1457 | let line_index = snap.file_line_index(file_id)?; | |
1458 | let item = to_proto::call_hierarchy_item(&snap, call_item.target)?; | |
1459 | res.push(CallHierarchyIncomingCall { | |
1460 | from: item, | |
1461 | from_ranges: call_item | |
1462 | .ranges | |
1463 | .into_iter() | |
1464 | .map(|it| to_proto::range(&line_index, it)) | |
1465 | .collect(), | |
1466 | }); | |
1467 | } | |
1468 | ||
1469 | Ok(Some(res)) | |
1470 | } | |
1471 | ||
1472 | pub(crate) fn handle_call_hierarchy_outgoing( | |
1473 | snap: GlobalStateSnapshot, | |
1474 | params: CallHierarchyOutgoingCallsParams, | |
1475 | ) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> { | |
1476 | let _p = profile::span("handle_call_hierarchy_outgoing"); | |
1477 | let item = params.item; | |
1478 | ||
1479 | let doc = TextDocumentIdentifier::new(item.uri); | |
1480 | let frange = from_proto::file_range(&snap, doc, item.selection_range)?; | |
1481 | let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; | |
1482 | ||
1483 | let call_items = match snap.analysis.outgoing_calls(fpos)? { | |
1484 | None => return Ok(None), | |
1485 | Some(it) => it, | |
1486 | }; | |
1487 | ||
1488 | let mut res = vec![]; | |
1489 | ||
1490 | for call_item in call_items.into_iter() { | |
1491 | let file_id = call_item.target.file_id; | |
1492 | let line_index = snap.file_line_index(file_id)?; | |
1493 | let item = to_proto::call_hierarchy_item(&snap, call_item.target)?; | |
1494 | res.push(CallHierarchyOutgoingCall { | |
1495 | to: item, | |
1496 | from_ranges: call_item | |
1497 | .ranges | |
1498 | .into_iter() | |
1499 | .map(|it| to_proto::range(&line_index, it)) | |
1500 | .collect(), | |
1501 | }); | |
1502 | } | |
1503 | ||
1504 | Ok(Some(res)) | |
1505 | } | |
1506 | ||
1507 | pub(crate) fn handle_semantic_tokens_full( | |
1508 | snap: GlobalStateSnapshot, | |
1509 | params: SemanticTokensParams, | |
1510 | ) -> Result<Option<SemanticTokensResult>> { | |
1511 | let _p = profile::span("handle_semantic_tokens_full"); | |
1512 | ||
1513 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | |
1514 | let text = snap.analysis.file_text(file_id)?; | |
1515 | let line_index = snap.file_line_index(file_id)?; | |
1516 | ||
f2b60f7d FG |
1517 | let mut highlight_config = snap.config.highlighting_config(); |
1518 | // Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet. | |
1519 | highlight_config.syntactic_name_ref_highlighting = !snap.proc_macros_loaded; | |
1520 | ||
1521 | let highlights = snap.analysis.highlight(highlight_config, file_id)?; | |
1522 | let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); | |
064997fb FG |
1523 | |
1524 | // Unconditionally cache the tokens | |
1525 | snap.semantic_tokens_cache.lock().insert(params.text_document.uri, semantic_tokens.clone()); | |
1526 | ||
1527 | Ok(Some(semantic_tokens.into())) | |
1528 | } | |
1529 | ||
1530 | pub(crate) fn handle_semantic_tokens_full_delta( | |
1531 | snap: GlobalStateSnapshot, | |
1532 | params: SemanticTokensDeltaParams, | |
1533 | ) -> Result<Option<SemanticTokensFullDeltaResult>> { | |
1534 | let _p = profile::span("handle_semantic_tokens_full_delta"); | |
1535 | ||
1536 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | |
1537 | let text = snap.analysis.file_text(file_id)?; | |
1538 | let line_index = snap.file_line_index(file_id)?; | |
1539 | ||
f2b60f7d FG |
1540 | let mut highlight_config = snap.config.highlighting_config(); |
1541 | // Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet. | |
1542 | highlight_config.syntactic_name_ref_highlighting = !snap.proc_macros_loaded; | |
1543 | ||
1544 | let highlights = snap.analysis.highlight(highlight_config, file_id)?; | |
1545 | let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); | |
064997fb FG |
1546 | |
1547 | let mut cache = snap.semantic_tokens_cache.lock(); | |
1548 | let cached_tokens = cache.entry(params.text_document.uri).or_default(); | |
1549 | ||
1550 | if let Some(prev_id) = &cached_tokens.result_id { | |
1551 | if *prev_id == params.previous_result_id { | |
1552 | let delta = to_proto::semantic_token_delta(cached_tokens, &semantic_tokens); | |
1553 | *cached_tokens = semantic_tokens; | |
1554 | return Ok(Some(delta.into())); | |
1555 | } | |
1556 | } | |
1557 | ||
1558 | *cached_tokens = semantic_tokens.clone(); | |
1559 | ||
1560 | Ok(Some(semantic_tokens.into())) | |
1561 | } | |
1562 | ||
1563 | pub(crate) fn handle_semantic_tokens_range( | |
1564 | snap: GlobalStateSnapshot, | |
1565 | params: SemanticTokensRangeParams, | |
1566 | ) -> Result<Option<SemanticTokensRangeResult>> { | |
1567 | let _p = profile::span("handle_semantic_tokens_range"); | |
1568 | ||
1569 | let frange = from_proto::file_range(&snap, params.text_document, params.range)?; | |
1570 | let text = snap.analysis.file_text(frange.file_id)?; | |
1571 | let line_index = snap.file_line_index(frange.file_id)?; | |
1572 | ||
f2b60f7d FG |
1573 | let highlights = snap.analysis.highlight_range(snap.config.highlighting_config(), frange)?; |
1574 | let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); | |
064997fb FG |
1575 | Ok(Some(semantic_tokens.into())) |
1576 | } | |
1577 | ||
1578 | pub(crate) fn handle_open_docs( | |
1579 | snap: GlobalStateSnapshot, | |
1580 | params: lsp_types::TextDocumentPositionParams, | |
1581 | ) -> Result<Option<lsp_types::Url>> { | |
1582 | let _p = profile::span("handle_open_docs"); | |
1583 | let position = from_proto::file_position(&snap, params)?; | |
1584 | ||
1585 | let remote = snap.analysis.external_docs(position)?; | |
1586 | ||
1587 | Ok(remote.and_then(|remote| Url::parse(&remote).ok())) | |
1588 | } | |
1589 | ||
1590 | pub(crate) fn handle_open_cargo_toml( | |
1591 | snap: GlobalStateSnapshot, | |
1592 | params: lsp_ext::OpenCargoTomlParams, | |
1593 | ) -> Result<Option<lsp_types::GotoDefinitionResponse>> { | |
1594 | let _p = profile::span("handle_open_cargo_toml"); | |
1595 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | |
1596 | ||
1597 | let cargo_spec = match CargoTargetSpec::for_file(&snap, file_id)? { | |
1598 | Some(it) => it, | |
1599 | None => return Ok(None), | |
1600 | }; | |
1601 | ||
1602 | let cargo_toml_url = to_proto::url_from_abs_path(&cargo_spec.cargo_toml); | |
1603 | let res: lsp_types::GotoDefinitionResponse = | |
1604 | Location::new(cargo_toml_url, Range::default()).into(); | |
1605 | Ok(Some(res)) | |
1606 | } | |
1607 | ||
1608 | pub(crate) fn handle_move_item( | |
1609 | snap: GlobalStateSnapshot, | |
1610 | params: lsp_ext::MoveItemParams, | |
1611 | ) -> Result<Vec<lsp_ext::SnippetTextEdit>> { | |
1612 | let _p = profile::span("handle_move_item"); | |
1613 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | |
1614 | let range = from_proto::file_range(&snap, params.text_document, params.range)?; | |
1615 | ||
1616 | let direction = match params.direction { | |
1617 | lsp_ext::MoveItemDirection::Up => ide::Direction::Up, | |
1618 | lsp_ext::MoveItemDirection::Down => ide::Direction::Down, | |
1619 | }; | |
1620 | ||
1621 | match snap.analysis.move_item(range, direction)? { | |
1622 | Some(text_edit) => { | |
1623 | let line_index = snap.file_line_index(file_id)?; | |
1624 | Ok(to_proto::snippet_text_edit_vec(&line_index, true, text_edit)) | |
1625 | } | |
1626 | None => Ok(vec![]), | |
1627 | } | |
1628 | } | |
1629 | ||
1630 | fn to_command_link(command: lsp_types::Command, tooltip: String) -> lsp_ext::CommandLink { | |
1631 | lsp_ext::CommandLink { tooltip: Some(tooltip), command } | |
1632 | } | |
1633 | ||
1634 | fn show_impl_command_link( | |
1635 | snap: &GlobalStateSnapshot, | |
1636 | position: &FilePosition, | |
1637 | ) -> Option<lsp_ext::CommandLinkGroup> { | |
1638 | if snap.config.hover_actions().implementations && snap.config.client_commands().show_reference { | |
1639 | if let Some(nav_data) = snap.analysis.goto_implementation(*position).unwrap_or(None) { | |
1640 | let uri = to_proto::url(snap, position.file_id); | |
1641 | let line_index = snap.file_line_index(position.file_id).ok()?; | |
1642 | let position = to_proto::position(&line_index, position.offset); | |
1643 | let locations: Vec<_> = nav_data | |
1644 | .info | |
1645 | .into_iter() | |
1646 | .filter_map(|nav| to_proto::location_from_nav(snap, nav).ok()) | |
1647 | .collect(); | |
1648 | let title = to_proto::implementation_title(locations.len()); | |
1649 | let command = to_proto::command::show_references(title, &uri, position, locations); | |
1650 | ||
1651 | return Some(lsp_ext::CommandLinkGroup { | |
1652 | commands: vec![to_command_link(command, "Go to implementations".into())], | |
1653 | ..Default::default() | |
1654 | }); | |
1655 | } | |
1656 | } | |
1657 | None | |
1658 | } | |
1659 | ||
1660 | fn show_ref_command_link( | |
1661 | snap: &GlobalStateSnapshot, | |
1662 | position: &FilePosition, | |
1663 | ) -> Option<lsp_ext::CommandLinkGroup> { | |
1664 | if snap.config.hover_actions().references && snap.config.client_commands().show_reference { | |
1665 | if let Some(ref_search_res) = snap.analysis.find_all_refs(*position, None).unwrap_or(None) { | |
1666 | let uri = to_proto::url(snap, position.file_id); | |
1667 | let line_index = snap.file_line_index(position.file_id).ok()?; | |
1668 | let position = to_proto::position(&line_index, position.offset); | |
1669 | let locations: Vec<_> = ref_search_res | |
1670 | .into_iter() | |
1671 | .flat_map(|res| res.references) | |
1672 | .flat_map(|(file_id, ranges)| { | |
1673 | ranges.into_iter().filter_map(move |(range, _)| { | |
1674 | to_proto::location(snap, FileRange { file_id, range }).ok() | |
1675 | }) | |
1676 | }) | |
1677 | .collect(); | |
1678 | let title = to_proto::reference_title(locations.len()); | |
1679 | let command = to_proto::command::show_references(title, &uri, position, locations); | |
1680 | ||
1681 | return Some(lsp_ext::CommandLinkGroup { | |
1682 | commands: vec![to_command_link(command, "Go to references".into())], | |
1683 | ..Default::default() | |
1684 | }); | |
1685 | } | |
1686 | } | |
1687 | None | |
1688 | } | |
1689 | ||
1690 | fn runnable_action_links( | |
1691 | snap: &GlobalStateSnapshot, | |
1692 | runnable: Runnable, | |
1693 | ) -> Option<lsp_ext::CommandLinkGroup> { | |
1694 | let hover_actions_config = snap.config.hover_actions(); | |
1695 | if !hover_actions_config.runnable() { | |
1696 | return None; | |
1697 | } | |
1698 | ||
1699 | let cargo_spec = CargoTargetSpec::for_file(snap, runnable.nav.file_id).ok()?; | |
1700 | if should_skip_target(&runnable, cargo_spec.as_ref()) { | |
1701 | return None; | |
1702 | } | |
1703 | ||
1704 | let client_commands_config = snap.config.client_commands(); | |
1705 | if !(client_commands_config.run_single || client_commands_config.debug_single) { | |
1706 | return None; | |
1707 | } | |
1708 | ||
1709 | let title = runnable.title(); | |
1710 | let r = to_proto::runnable(snap, runnable).ok()?; | |
1711 | ||
1712 | let mut group = lsp_ext::CommandLinkGroup::default(); | |
1713 | ||
1714 | if hover_actions_config.run && client_commands_config.run_single { | |
1715 | let run_command = to_proto::command::run_single(&r, &title); | |
1716 | group.commands.push(to_command_link(run_command, r.label.clone())); | |
1717 | } | |
1718 | ||
1719 | if hover_actions_config.debug && client_commands_config.debug_single { | |
1720 | let dbg_command = to_proto::command::debug_single(&r); | |
1721 | group.commands.push(to_command_link(dbg_command, r.label)); | |
1722 | } | |
1723 | ||
1724 | Some(group) | |
1725 | } | |
1726 | ||
1727 | fn goto_type_action_links( | |
1728 | snap: &GlobalStateSnapshot, | |
1729 | nav_targets: &[HoverGotoTypeData], | |
1730 | ) -> Option<lsp_ext::CommandLinkGroup> { | |
1731 | if !snap.config.hover_actions().goto_type_def | |
1732 | || nav_targets.is_empty() | |
1733 | || !snap.config.client_commands().goto_location | |
1734 | { | |
1735 | return None; | |
1736 | } | |
1737 | ||
1738 | Some(lsp_ext::CommandLinkGroup { | |
1739 | title: Some("Go to ".into()), | |
1740 | commands: nav_targets | |
1741 | .iter() | |
1742 | .filter_map(|it| { | |
1743 | to_proto::command::goto_location(snap, &it.nav) | |
1744 | .map(|cmd| to_command_link(cmd, it.mod_path.clone())) | |
1745 | }) | |
1746 | .collect(), | |
1747 | }) | |
1748 | } | |
1749 | ||
1750 | fn prepare_hover_actions( | |
1751 | snap: &GlobalStateSnapshot, | |
1752 | actions: &[HoverAction], | |
1753 | ) -> Vec<lsp_ext::CommandLinkGroup> { | |
1754 | actions | |
1755 | .iter() | |
1756 | .filter_map(|it| match it { | |
1757 | HoverAction::Implementation(position) => show_impl_command_link(snap, position), | |
1758 | HoverAction::Reference(position) => show_ref_command_link(snap, position), | |
1759 | HoverAction::Runnable(r) => runnable_action_links(snap, r.clone()), | |
1760 | HoverAction::GoToType(targets) => goto_type_action_links(snap, targets), | |
1761 | }) | |
1762 | .collect() | |
1763 | } | |
1764 | ||
1765 | fn should_skip_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>) -> bool { | |
1766 | match runnable.kind { | |
1767 | RunnableKind::Bin => { | |
1768 | // Do not suggest binary run on other target than binary | |
1769 | match &cargo_spec { | |
1770 | Some(spec) => !matches!( | |
1771 | spec.target_kind, | |
1772 | TargetKind::Bin | TargetKind::Example | TargetKind::Test | |
1773 | ), | |
1774 | None => true, | |
1775 | } | |
1776 | } | |
1777 | _ => false, | |
1778 | } | |
1779 | } | |
1780 | ||
1781 | fn run_rustfmt( | |
1782 | snap: &GlobalStateSnapshot, | |
1783 | text_document: TextDocumentIdentifier, | |
1784 | range: Option<lsp_types::Range>, | |
1785 | ) -> Result<Option<Vec<lsp_types::TextEdit>>> { | |
1786 | let file_id = from_proto::file_id(snap, &text_document.uri)?; | |
1787 | let file = snap.analysis.file_text(file_id)?; | |
2b03887a | 1788 | |
f25598a0 FG |
1789 | // Determine the edition of the crate the file belongs to (if there's multiple, we pick the |
1790 | // highest edition). | |
1791 | let editions = snap | |
2b03887a FG |
1792 | .analysis |
1793 | .relevant_crates_for(file_id)? | |
1794 | .into_iter() | |
f25598a0 FG |
1795 | .map(|crate_id| snap.analysis.crate_edition(crate_id)) |
1796 | .collect::<Result<Vec<_>, _>>()?; | |
1797 | let edition = editions.iter().copied().max(); | |
064997fb FG |
1798 | |
1799 | let line_index = snap.file_line_index(file_id)?; | |
1800 | ||
f2b60f7d | 1801 | let mut command = match snap.config.rustfmt() { |
064997fb FG |
1802 | RustfmtConfig::Rustfmt { extra_args, enable_range_formatting } => { |
1803 | let mut cmd = process::Command::new(toolchain::rustfmt()); | |
2b03887a | 1804 | cmd.envs(snap.config.extra_env()); |
064997fb FG |
1805 | cmd.args(extra_args); |
1806 | // try to chdir to the file so we can respect `rustfmt.toml` | |
1807 | // FIXME: use `rustfmt --config-path` once | |
1808 | // https://github.com/rust-lang/rustfmt/issues/4660 gets fixed | |
1809 | match text_document.uri.to_file_path() { | |
1810 | Ok(mut path) => { | |
1811 | // pop off file name | |
1812 | if path.pop() && path.is_dir() { | |
1813 | cmd.current_dir(path); | |
1814 | } | |
1815 | } | |
1816 | Err(_) => { | |
1817 | tracing::error!( | |
1818 | "Unable to get file path for {}, rustfmt.toml might be ignored", | |
1819 | text_document.uri | |
1820 | ); | |
1821 | } | |
1822 | } | |
2b03887a | 1823 | if let Some(edition) = edition { |
064997fb FG |
1824 | cmd.arg("--edition"); |
1825 | cmd.arg(edition.to_string()); | |
1826 | } | |
1827 | ||
1828 | if let Some(range) = range { | |
1829 | if !enable_range_formatting { | |
1830 | return Err(LspError::new( | |
1831 | ErrorCode::InvalidRequest as i32, | |
1832 | String::from( | |
1833 | "rustfmt range formatting is unstable. \ | |
1834 | Opt-in by using a nightly build of rustfmt and setting \ | |
1835 | `rustfmt.rangeFormatting.enable` to true in your LSP configuration", | |
1836 | ), | |
1837 | ) | |
1838 | .into()); | |
1839 | } | |
1840 | ||
1841 | let frange = from_proto::file_range(snap, text_document, range)?; | |
1842 | let start_line = line_index.index.line_col(frange.range.start()).line; | |
1843 | let end_line = line_index.index.line_col(frange.range.end()).line; | |
1844 | ||
1845 | cmd.arg("--unstable-features"); | |
1846 | cmd.arg("--file-lines"); | |
1847 | cmd.arg( | |
1848 | json!([{ | |
1849 | "file": "stdin", | |
1850 | "range": [start_line, end_line] | |
1851 | }]) | |
1852 | .to_string(), | |
1853 | ); | |
1854 | } | |
1855 | ||
1856 | cmd | |
1857 | } | |
1858 | RustfmtConfig::CustomCommand { command, args } => { | |
1859 | let mut cmd = process::Command::new(command); | |
2b03887a | 1860 | cmd.envs(snap.config.extra_env()); |
064997fb FG |
1861 | cmd.args(args); |
1862 | cmd | |
1863 | } | |
1864 | }; | |
1865 | ||
f2b60f7d | 1866 | let mut rustfmt = command |
064997fb FG |
1867 | .stdin(Stdio::piped()) |
1868 | .stdout(Stdio::piped()) | |
1869 | .stderr(Stdio::piped()) | |
1870 | .spawn() | |
f25598a0 | 1871 | .context(format!("Failed to spawn {command:?}"))?; |
064997fb FG |
1872 | |
1873 | rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?; | |
1874 | ||
1875 | let output = rustfmt.wait_with_output()?; | |
1876 | let captured_stdout = String::from_utf8(output.stdout)?; | |
1877 | let captured_stderr = String::from_utf8(output.stderr).unwrap_or_default(); | |
1878 | ||
1879 | if !output.status.success() { | |
1880 | let rustfmt_not_installed = | |
1881 | captured_stderr.contains("not installed") || captured_stderr.contains("not available"); | |
1882 | ||
1883 | return match output.status.code() { | |
1884 | Some(1) if !rustfmt_not_installed => { | |
1885 | // While `rustfmt` doesn't have a specific exit code for parse errors this is the | |
1886 | // likely cause exiting with 1. Most Language Servers swallow parse errors on | |
1887 | // formatting because otherwise an error is surfaced to the user on top of the | |
1888 | // syntax error diagnostics they're already receiving. This is especially jarring | |
1889 | // if they have format on save enabled. | |
f2b60f7d FG |
1890 | tracing::warn!( |
1891 | ?command, | |
1892 | %captured_stderr, | |
1893 | "rustfmt exited with status 1" | |
1894 | ); | |
064997fb FG |
1895 | Ok(None) |
1896 | } | |
1897 | _ => { | |
1898 | // Something else happened - e.g. `rustfmt` is missing or caught a signal | |
1899 | Err(LspError::new( | |
1900 | -32900, | |
1901 | format!( | |
1902 | r#"rustfmt exited with: | |
1903 | Status: {} | |
f25598a0 FG |
1904 | stdout: {captured_stdout} |
1905 | stderr: {captured_stderr}"#, | |
1906 | output.status, | |
064997fb FG |
1907 | ), |
1908 | ) | |
1909 | .into()) | |
1910 | } | |
1911 | }; | |
1912 | } | |
1913 | ||
1914 | let (new_text, new_line_endings) = LineEndings::normalize(captured_stdout); | |
1915 | ||
1916 | if line_index.endings != new_line_endings { | |
1917 | // If line endings are different, send the entire file. | |
1918 | // Diffing would not work here, as the line endings might be the only | |
1919 | // difference. | |
1920 | Ok(Some(to_proto::text_edit_vec( | |
1921 | &line_index, | |
1922 | TextEdit::replace(TextRange::up_to(TextSize::of(&*file)), new_text), | |
1923 | ))) | |
1924 | } else if *file == new_text { | |
1925 | // The document is already formatted correctly -- no edits needed. | |
1926 | Ok(None) | |
1927 | } else { | |
1928 | Ok(Some(to_proto::text_edit_vec(&line_index, diff(&file, &new_text)))) | |
1929 | } | |
1930 | } |