1 //! This module is responsible for implementing handlers for Language Server
2 //! Protocol. The majority of requests are fulfilled by calling into the
7 process
::{self, Stdio}
,
12 AnnotationConfig
, AssistKind
, AssistResolveStrategy
, Cancellable
, FileId
, FilePosition
,
13 FileRange
, HoverAction
, HoverGotoTypeData
, Query
, RangeInfo
, ReferenceCategory
, Runnable
,
14 RunnableKind
, SingleResolve
, SourceChange
, TextEdit
,
16 use ide_db
::SymbolKind
;
17 use lsp_server
::ErrorCode
;
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
,
28 use project_model
::{ManifestPath, ProjectWorkspace, TargetKind}
;
30 use stdx
::{format_to, never}
;
31 use syntax
::{algo, ast, AstNode, TextRange, TextSize}
;
36 cargo_target_spec
::CargoTargetSpec
,
37 config
::{RustfmtConfig, WorkspaceSymbolConfig}
,
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
,
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());
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());
61 pub(crate) fn handle_analyzer_status(
62 snap
: GlobalStateSnapshot
,
63 params
: lsp_ext
::AnalyzerStatusParams
,
65 let _p
= profile
::span("handle_analyzer_status");
67 let mut buf
= String
::new();
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
),
77 if snap
.workspaces
.is_empty() {
78 buf
.push_str("No workspaces\n")
80 buf
.push_str("Workspaces:\n");
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" }
89 buf
.push_str("\nAnalysis:\n");
94 .unwrap_or_else(|_
| "Analysis retrieval was cancelled".to_owned()),
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
));
104 let mut out
= String
::new();
105 for (name
, bytes
) in mem
{
106 format_to
!(out
, "{:>8} {}\n", bytes
, name
);
111 pub(crate) fn handle_shuffle_crate_graph(state
: &mut GlobalState
, _
: ()) -> Result
<()> {
112 state
.analysis_host
.shuffle_crate_graph();
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
)?
;
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
)?
;
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())
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
)?
;
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
)??
;
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
)?
;
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 }
))
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
189 let offset
= from_proto
::offset(&line_index
, position
)?
;
190 let mut ranges
= Vec
::new();
192 let mut range
= TextRange
::new(offset
, offset
);
195 let frange
= FileRange { file_id, range }
;
196 let next
= snap
.analysis
.extend_selection(frange
)?
;
204 let mut range
= lsp_types
::SelectionRange
{
205 range
: to_proto
::range(&line_index
, *ranges
.last().unwrap()),
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
)),
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
)?
;
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
,
238 to_proto
::position(&line_index
, offset
)
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");
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
)?
;
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
) {
261 // just ignore overlapping edits
266 Ok(to_proto
::text_edit_vec(&line_index
, res
))
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
),
279 let line_index
= snap
.file_line_index(position
.file_id
)?
;
280 let edit
= to_proto
::snippet_text_edit_vec(&line_index
, true, edit
);
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
)?
;
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'
);
297 let text
= snap
.analysis
.file_text(position
.file_id
)?
;
298 if stdx
::never
!(!text
[usize::from(position
.offset
)..].starts_with(char_typed
)) {
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
== '
>'
{
311 snap
.analysis
.on_char_typed(position
, char_typed
, snap
.config
.typing_autoclose_angle())?
;
312 let edit
= match edit
{
314 None
=> return Ok(None
),
317 // This should be a single-file edit
318 let (_
, text_edit
) = edit
.source_file_edits
.into_iter().next().unwrap();
320 let change
= to_proto
::snippet_text_edit_vec(&line_index
, edit
.is_snippet
, text_edit
);
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
)?
;
332 let mut parents
: Vec
<(lsp_types
::DocumentSymbol
, Option
<usize>)> = Vec
::new();
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
)
341 let doc_symbol
= lsp_types
::DocumentSymbol
{
343 detail
: symbol
.detail
,
344 kind
: to_proto
::structure_node_kind(symbol
.kind
),
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
),
351 parents
.push((doc_symbol
, symbol
.parent
));
354 // Builds hierarchy from a flat list, in reverse order (so that indices
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
{
362 let parent
= match parent_idx
{
364 Some(i
) => parents
[i
].0.children
.get_or_insert_with(Vec
::new
),
372 let res
= if snap
.config
.hierarchical_symbols() {
373 document_symbols
.into()
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
);
380 symbol_information
.into()
382 return Ok(Some(res
));
384 fn flatten_document_symbol(
385 symbol
: &lsp_types
::DocumentSymbol
,
386 container_name
: Option
<String
>,
388 res
: &mut Vec
<SymbolInformation
>,
390 let mut tags
= Vec
::new();
393 if let Some(true) = symbol
.deprecated
{
394 tags
.push(SymbolTag
::DEPRECATED
)
398 res
.push(SymbolInformation
{
399 name
: symbol
.name
.clone(),
402 deprecated
: symbol
.deprecated
,
403 location
: Location
::new(url
.clone(), symbol
.range
),
407 for child
in symbol
.children
.iter().flatten() {
408 flatten_document_symbol(child
, Some(symbol
.name
.clone()), url
, res
);
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");
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
;
424 let query
: String
= params
.query
.chars().filter(|&c
| c
!= '
#' && c != '*').collect();
425 let mut q
= Query
::new(query
);
435 let mut res
= exec_query(&snap
, query
)?
;
436 if res
.is_empty() && !all_symbols
{
437 let mut query
= Query
::new(params
.query
);
439 res
= exec_query(&snap
, query
)?
;
442 return Ok(Some(res
));
444 fn decide_search_scope_and_kind(
445 params
: &WorkspaceSymbolParams
,
446 config
: &WorkspaceSymbolConfig
,
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('
*'
);
452 // If no explicit marker was set, check request params. If that's also empty
453 // use global config.
455 let search_kind
= match params
.search_kind
{
456 Some(ref search_kind
) => search_kind
,
457 None
=> &config
.search_kind
,
459 all_symbols
= match search_kind
{
460 lsp_ext
::WorkspaceSymbolSearchKind
::OnlyTypes
=> false,
461 lsp_ext
::WorkspaceSymbolSearchKind
::AllSymbols
=> true,
466 let search_scope
= match params
.search_scope
{
467 Some(ref search_scope
) => search_scope
,
468 None
=> &config
.search_scope
,
470 libs
= match search_scope
{
471 lsp_ext
::WorkspaceSymbolSearchScope
::Workspace
=> false,
472 lsp_ext
::WorkspaceSymbolSearchScope
::WorkspaceAndDependencies
=> true,
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());
485 let info
= SymbolInformation
{
486 name
: nav
.name
.to_string(),
489 .map(to_proto
::symbol_kind
)
490 .unwrap_or(lsp_types
::SymbolKind
::VARIABLE
),
492 location
: to_proto
::location_from_nav(snap
, nav
)?
,
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");
508 let source_changes
: Vec
<SourceChange
> = params
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()?
;
515 let from_path
= from
.to_file_path().ok()?
;
516 let to_path
= to
.to_file_path().ok()?
;
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()?
;
527 let imitate_from_url
= from_with_trailing_slash
.join("mod.rs").ok()?
;
528 let new_file_name
= to_path
.file_name()?
.to_str()?
;
530 snap
.url_to_file_id(&imitate_from_url
).ok()?
,
531 new_file_name
.to_string(),
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
) {
539 _
=> Some((snap
.url_to_file_id(&from
).ok()?
, new_name
.to_string())),
546 .filter_map(|(file_id
, new_name
)| {
547 snap
.analysis
.will_rename_file(file_id
, &new_name
).ok()?
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() {
560 Ok(Some(to_proto
::workspace_edit(&snap
, source_change
)?
))
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
),
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
)?
;
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
),
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
)?
;
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
),
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
)?
;
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
),
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
)?
;
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
),
637 let manifest_path
= match ManifestPath
::try_from(abs_path_buf
).ok() {
638 Some(manifest_path
) => manifest_path
,
639 None
=> return Ok(None
),
642 let links
: Vec
<LocationLink
> = snap
645 .filter_map(|ws
| match ws
{
646 ProjectWorkspace
::Cargo { cargo, .. }
=> cargo
.parent_manifests(&manifest_path
),
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(),
657 return Ok(Some(links
.into()));
660 // check if invoked at the crate root
661 let file_id
= from_proto
::file_id(&snap
, ¶ms
.text_document
.uri
)?
;
662 let crate_id
= match snap
.analysis
.crates_for(file_id
)?
.first() {
663 Some(&crate_id
) => crate_id
,
664 None
=> return Ok(None
),
666 let cargo_spec
= match CargoTargetSpec
::for_file(&snap
, file_id
)?
{
668 None
=> return Ok(None
),
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(),
680 return Ok(Some(res
));
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
)?
;
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
)?
;
701 let expect_test
= match 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")
711 let mut res
= Vec
::new();
712 for runnable
in snap
.analysis
.runnables(file_id
)?
{
713 if should_skip_for_offset(&runnable
, offset
) {
716 if should_skip_target(&runnable
, cargo_spec
.as_ref()) {
719 let mut runnable
= to_proto
::runnable(&snap
, runnable
)?
;
721 runnable
.label
= format
!("{} + expect", runnable
.label
);
722 runnable
.args
.expect_test
= Some(true);
727 // Add `cargo check` and `cargo test` for all targets of the whole package
728 let config
= snap
.config
.runnables();
731 for cmd
in ["check", "test"] {
732 res
.push(lsp_ext
::Runnable
{
733 label
: format
!("cargo {cmd} -p {} --all-targets", spec
.package
),
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(),
741 "--package".to_string(),
742 spec
.package
.clone(),
743 "--all-targets".to_string(),
745 cargo_extra_args
: config
.cargo_extra_args
.clone(),
746 executable_args
: Vec
::new(),
753 if !snap
.config
.linked_projects().is_empty()
758 .map(|projects
| projects
.is_empty())
761 res
.push(lsp_ext
::Runnable
{
762 label
: "cargo check --workspace".to_string(),
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(),
780 fn should_skip_for_offset(runnable
: &Runnable
, offset
: Option
<TextSize
>) -> bool
{
783 _
if matches
!(&runnable
.kind
, RunnableKind
::TestMod { .. }
) => false,
784 Some(offset
) => !runnable
.nav
.full_range
.contains_inclusive(offset
),
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
)?
;
795 let tests
= snap
.analysis
.related_tests(position
, None
)?
;
796 let mut res
= Vec
::new();
798 if let Ok(runnable
) = to_proto
::runnable(&snap
, it
) {
799 res
.push(lsp_ext
::TestInfo { runnable }
)
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());
816 let completion_config
= &snap
.config
.completion();
817 let items
= match snap
.analysis
.completions(
820 completion_trigger_character
,
822 None
=> return Ok(None
),
823 Some(items
) => items
,
825 let line_index
= snap
.file_line_index(position
.file_id
)?
;
828 to_proto
::completion_items(&snap
.config
, &line_index
, text_document_position
, items
);
830 let completion_list
= lsp_types
::CompletionList { is_incomplete: true, items }
;
831 Ok(Some(completion_list
.into()))
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");
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(),
847 let data
= match original_completion
.data
.take() {
849 None
=> return Ok(original_completion
),
852 let resolve_data
: lsp_ext
::CompletionResolveData
= serde_json
::from_value(data
)?
;
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
)?
;
858 let additional_edits
= snap
860 .resolve_completion_edits(
861 &snap
.config
.completion(),
862 FilePosition { file_id, offset }
,
866 .map(|import
| (import
.full_import_path
, import
.imported_name
)),
869 .flat_map(|edit
| edit
.into_iter().map(|indel
| to_proto
::text_edit(&line_index
, indel
)))
870 .collect
::<Vec
<_
>>();
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"
881 if let Some(original_additional_edits
) = original_completion
.additional_text_edits
.as_mut() {
882 original_additional_edits
.extend(additional_edits
.into_iter())
884 original_completion
.additional_text_edits
= Some(additional_edits
);
887 Ok(original_completion
)
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();
902 .map(|it
| to_proto
::folding_range(&text
, &line_index
, line_folding_only
, it
))
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
)?
{
915 None
=> return Ok(None
),
917 let config
= snap
.config
.call_info();
918 let res
= to_proto
::signature_help(help
, config
, snap
.config
.signature_help_label_offsets());
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
,
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
),
938 let line_index
= snap
.file_line_index(file_range
.file_id
)?
;
939 let range
= to_proto
::range(&line_index
, info
.range
);
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(
950 actions
: if snap
.config
.hover_actions().none() {
953 prepare_hover_actions(&snap
, &info
.info
.actions
)
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
)?
;
967 let change
= snap
.analysis
.prepare_rename(position
)?
.map_err(to_proto
::rename_error
)?
;
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
)))
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
)?
;
982 snap
.analysis
.rename(position
, ¶ms
.new_name
)?
.map_err(to_proto
::rename_error
)?
;
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();
993 let workspace_edit
= to_proto
::workspace_edit(&snap
, change
)?
;
994 Ok(Some(workspace_edit
))
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
)?
;
1004 let exclude_imports
= snap
.config
.find_all_refs_exclude_imports();
1006 let refs
= match snap
.analysis
.find_all_refs(position
, None
)?
{
1007 None
=> return Ok(None
),
1011 let include_declaration
= params
.context
.include_declaration
;
1012 let locations
= 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(),
1025 .flat_map(|(file_id
, refs
)| {
1027 .filter(|&(_
, category
)| {
1028 !exclude_imports
|| category
!= Some(ReferenceCategory
::Import
)
1030 .map(move |(range
, _
)| FileRange { file_id, range }
)
1034 .filter_map(|frange
| to_proto
::location(&snap
, frange
).ok())
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");
1046 run_rustfmt(&snap
, params
.text_document
, None
)
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");
1055 run_rustfmt(&snap
, params
.text_document
, Some(params
.range
))
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");
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.
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
)?
;
1075 let mut assists_config
= snap
.config
.assist();
1076 assists_config
.allowed
= params
1080 .map(|it
| it
.into_iter().filter_map(from_proto
::assist_kind
).collect());
1082 let mut res
: Vec
<lsp_ext
::CodeAction
> = Vec
::new();
1084 let code_action_resolve_cap
= snap
.config
.code_action_resolve();
1085 let resolve
= if code_action_resolve_cap
{
1086 AssistResolveStrategy
::None
1088 AssistResolveStrategy
::All
1090 let assists
= snap
.analysis
.assists_with_fixes(
1092 &snap
.config
.diagnostics(),
1096 for (index
, assist
) in assists
.into_iter().enumerate() {
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
)
1103 // Fixes from `cargo check`.
1104 for fix
in snap
.check_fixes
.values().filter_map(|it
| it
.get(&frange
.file_id
)).flatten() {
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
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());
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() {
1128 None
=> return Err(invalid_params_error("code action without data".to_string()).into()),
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 }
;
1136 let mut assists_config
= snap
.config
.assist();
1137 assists_config
.allowed
= params
1141 .map(|it
| it
.into_iter().filter_map(from_proto
::assist_kind
).collect());
1143 let (assist_index
, assist_resolve
) = match parse_action_id(¶ms
.id
) {
1144 Ok(parsed_data
) => parsed_data
,
1146 return Err(invalid_params_error(format
!(
1147 "Failed to parse action id string '{}': {e}",
1154 let expected_assist_id
= assist_resolve
.assist_id
.clone();
1155 let expected_kind
= assist_resolve
.assist_kind
;
1157 let assists
= snap
.analysis
.assists_with_fixes(
1159 &snap
.config
.diagnostics(),
1160 AssistResolveStrategy
::Single(assist_resolve
),
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
,
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
1179 let ca
= to_proto
::code_action(&snap
, assist
.clone(), None
)?
;
1180 code_action
.edit
= ca
.edit
;
1181 code_action
.command
= ca
.command
;
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() {
1192 Err(e
) => return Err(format
!("Incorrect index string: {e}")),
1194 Ok((index
, SingleResolve { assist_id: assist_id_string.to_string(), assist_kind }
))
1196 _
=> Err("Action id contains incorrect number of segments".to_string()),
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");
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()));
1212 let file_id
= from_proto
::file_id(&snap
, ¶ms
.text_document
.uri
)?
;
1213 let cargo_target_spec
= CargoTargetSpec
::for_file(&snap
, file_id
)?
;
1215 let annotations
= snap
.analysis
.annotations(
1217 binary_target
: cargo_target_spec
1221 TargetKind
::Bin
| TargetKind
::Example
| TargetKind
::Test
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
,
1230 location
: lens_config
.location
.into(),
1235 let mut res
= Vec
::new();
1236 for a
in annotations
{
1237 to_proto
::code_lens(&mut res
, &snap
, a
)?
;
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
)?
;
1250 let mut acc
= Vec
::new();
1251 to_proto
::code_lens(&mut acc
, &snap
, annotation
)?
;
1253 let res
= match acc
.pop() {
1254 Some(it
) if acc
.is_empty() => it
,
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
)?
;
1272 let refs
= match snap
.analysis
.highlight_related(snap
.config
.highlight_related(), position
)?
{
1273 None
=> return Ok(None
),
1278 .map(|ide
::HighlightedRange { range, category }
| lsp_types
::DocumentHighlight
{
1279 range
: to_proto
::range(&line_index
, range
),
1280 kind
: category
.and_then(to_proto
::document_highlight_kind
),
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
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(
1303 to_proto
::workspace_edit(&snap
, source_change
).map_err(Into
::into
)
1306 pub(crate) fn publish_diagnostics(
1307 snap
: &GlobalStateSnapshot
,
1309 ) -> Result
<Vec
<Diagnostic
>> {
1310 let _p
= profile
::span("publish_diagnostics");
1311 let line_index
= snap
.file_line_index(file_id
)?
;
1313 let diagnostics
: Vec
<Diagnostic
> = snap
1315 .diagnostics(&snap
.config
.diagnostics(), AssistResolveStrategy
::None
, file_id
)?
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#{}",
1328 source
: Some("rust-analyzer".to_string()),
1330 related_information
: None
,
1331 tags
: if d
.unused { Some(vec![DiagnosticTag::UNNECESSARY]) }
else { None }
,
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
;
1344 let FileRange { file_id, range }
= from_proto
::file_range(
1346 TextDocumentIdentifier
::new(document_uri
.to_owned()),
1349 let line_index
= snap
.file_line_index(file_id
)?
;
1350 let inlay_hints_config
= snap
.config
.inlay_hints();
1353 .inlay_hints(&inlay_hints_config
, file_id
, Some(range
))?
1356 to_proto
::inlay_hint(&snap
, &line_index
, inlay_hints_config
.render_colons
, it
)
1358 .collect
::<Cancellable
<Vec
<_
>>>()?
,
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() {
1369 None
=> return Ok(hint
),
1372 let resolve_data
: lsp_ext
::InlayHintResolveData
= serde_json
::from_value(data
)?
;
1374 match snap
.url_file_version(&resolve_data
.text_document
.uri
) {
1375 Some(version
) if version
== resolve_data
.text_document
.version
=> {}
1378 "attempted inlayHints/resolve of '{}' at version {} while server version is {}",
1379 resolve_data
.text_document
.uri
, resolve_data
.text_document
.version
, version
,
1385 "attempted inlayHints/resolve of unknown file '{}' at version {}",
1386 resolve_data
.text_document
.uri
, resolve_data
.text_document
.version
,
1391 let file_range
= from_proto
::file_range_uri(
1393 &resolve_data
.text_document
.uri
,
1394 match resolve_data
.position
{
1395 PositionOrRange
::Position(pos
) => Range
::new(pos
, pos
),
1396 PositionOrRange
::Range(range
) => range
,
1399 let info
= match snap
.analysis
.hover(&snap
.config
.hover(), file_range
)?
{
1400 None
=> return Ok(hint
),
1405 snap
.config
.hover().documentation
.map_or(ide
::HoverDocFormat
::Markdown
, |kind
| kind
);
1407 // FIXME: hover actions?
1408 hint
.tooltip
= Some(lsp_types
::InlayHintTooltip
::MarkupContent(to_proto
::markup_content(
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
)?
;
1422 let nav_info
= match snap
.analysis
.call_hierarchy(position
)?
{
1423 None
=> return Ok(None
),
1427 let RangeInfo { range: _, info: navs }
= nav_info
;
1430 .filter(|it
| it
.kind
== Some(SymbolKind
::Function
))
1431 .map(|it
| to_proto
::call_hierarchy_item(&snap
, it
))
1432 .collect
::<Cancellable
<Vec
<_
>>>()?
;
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
;
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() }
;
1448 let call_items
= match snap
.analysis
.incoming_calls(fpos
)?
{
1449 None
=> return Ok(None
),
1453 let mut res
= vec
![];
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
{
1461 from_ranges
: call_item
1464 .map(|it
| to_proto
::range(&line_index
, it
))
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
;
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() }
;
1483 let call_items
= match snap
.analysis
.outgoing_calls(fpos
)?
{
1484 None
=> return Ok(None
),
1488 let mut res
= vec
![];
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
{
1496 from_ranges
: call_item
1499 .map(|it
| to_proto
::range(&line_index
, it
))
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");
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
)?
;
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
;
1521 let highlights
= snap
.analysis
.highlight(highlight_config
, file_id
)?
;
1522 let semantic_tokens
= to_proto
::semantic_tokens(&text
, &line_index
, highlights
);
1524 // Unconditionally cache the tokens
1525 snap
.semantic_tokens_cache
.lock().insert(params
.text_document
.uri
, semantic_tokens
.clone());
1527 Ok(Some(semantic_tokens
.into()))
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");
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
)?
;
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
;
1544 let highlights
= snap
.analysis
.highlight(highlight_config
, file_id
)?
;
1545 let semantic_tokens
= to_proto
::semantic_tokens(&text
, &line_index
, highlights
);
1547 let mut cache
= snap
.semantic_tokens_cache
.lock();
1548 let cached_tokens
= cache
.entry(params
.text_document
.uri
).or_default();
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()));
1558 *cached_tokens
= semantic_tokens
.clone();
1560 Ok(Some(semantic_tokens
.into()))
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");
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
)?
;
1573 let highlights
= snap
.analysis
.highlight_range(snap
.config
.highlighting_config(), frange
)?
;
1574 let semantic_tokens
= to_proto
::semantic_tokens(&text
, &line_index
, highlights
);
1575 Ok(Some(semantic_tokens
.into()))
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
)?
;
1585 let remote
= snap
.analysis
.external_docs(position
)?
;
1587 Ok(remote
.and_then(|remote
| Url
::parse(&remote
).ok()))
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
)?
;
1597 let cargo_spec
= match CargoTargetSpec
::for_file(&snap
, file_id
)?
{
1599 None
=> return Ok(None
),
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();
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
)?
;
1616 let direction
= match params
.direction
{
1617 lsp_ext
::MoveItemDirection
::Up
=> ide
::Direction
::Up
,
1618 lsp_ext
::MoveItemDirection
::Down
=> ide
::Direction
::Down
,
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
))
1630 fn to_command_link(command
: lsp_types
::Command
, tooltip
: String
) -> lsp_ext
::CommandLink
{
1631 lsp_ext
::CommandLink { tooltip: Some(tooltip), command }
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
1646 .filter_map(|nav
| to_proto
::location_from_nav(snap
, nav
).ok())
1648 let title
= to_proto
::implementation_title(locations
.len());
1649 let command
= to_proto
::command
::show_references(title
, &uri
, position
, locations
);
1651 return Some(lsp_ext
::CommandLinkGroup
{
1652 commands
: vec
![to_command_link(command
, "Go to implementations".into())],
1653 ..Default
::default()
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
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()
1678 let title
= to_proto
::reference_title(locations
.len());
1679 let command
= to_proto
::command
::show_references(title
, &uri
, position
, locations
);
1681 return Some(lsp_ext
::CommandLinkGroup
{
1682 commands
: vec
![to_command_link(command
, "Go to references".into())],
1683 ..Default
::default()
1690 fn runnable_action_links(
1691 snap
: &GlobalStateSnapshot
,
1693 ) -> Option
<lsp_ext
::CommandLinkGroup
> {
1694 let hover_actions_config
= snap
.config
.hover_actions();
1695 if !hover_actions_config
.runnable() {
1699 let cargo_spec
= CargoTargetSpec
::for_file(snap
, runnable
.nav
.file_id
).ok()?
;
1700 if should_skip_target(&runnable
, cargo_spec
.as_ref()) {
1704 let client_commands_config
= snap
.config
.client_commands();
1705 if !(client_commands_config
.run_single
|| client_commands_config
.debug_single
) {
1709 let title
= runnable
.title();
1710 let r
= to_proto
::runnable(snap
, runnable
).ok()?
;
1712 let mut group
= lsp_ext
::CommandLinkGroup
::default();
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()));
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
));
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
1738 Some(lsp_ext
::CommandLinkGroup
{
1739 title
: Some("Go to ".into()),
1740 commands
: nav_targets
1743 to_proto
::command
::goto_location(snap
, &it
.nav
)
1744 .map(|cmd
| to_command_link(cmd
, it
.mod_path
.clone()))
1750 fn prepare_hover_actions(
1751 snap
: &GlobalStateSnapshot
,
1752 actions
: &[HoverAction
],
1753 ) -> Vec
<lsp_ext
::CommandLinkGroup
> {
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
),
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
1770 Some(spec
) => !matches
!(
1772 TargetKind
::Bin
| TargetKind
::Example
| TargetKind
::Test
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
)?
;
1789 // Determine the edition of the crate the file belongs to (if there's multiple, we pick the
1790 // highest edition).
1793 .relevant_crates_for(file_id
)?
1795 .map(|crate_id
| snap
.analysis
.crate_edition(crate_id
))
1796 .collect
::<Result
<Vec
<_
>, _
>>()?
;
1797 let edition
= editions
.iter().copied().max();
1799 let line_index
= snap
.file_line_index(file_id
)?
;
1801 let mut command
= match snap
.config
.rustfmt() {
1802 RustfmtConfig
::Rustfmt { extra_args, enable_range_formatting }
=> {
1803 let mut cmd
= process
::Command
::new(toolchain
::rustfmt());
1804 cmd
.envs(snap
.config
.extra_env());
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() {
1811 // pop off file name
1812 if path
.pop() && path
.is_dir() {
1813 cmd
.current_dir(path
);
1818 "Unable to get file path for {}, rustfmt.toml might be ignored",
1823 if let Some(edition
) = edition
{
1824 cmd
.arg("--edition");
1825 cmd
.arg(edition
.to_string());
1828 if let Some(range
) = range
{
1829 if !enable_range_formatting
{
1830 return Err(LspError
::new(
1831 ErrorCode
::InvalidRequest
as i32,
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",
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
;
1845 cmd
.arg("--unstable-features");
1846 cmd
.arg("--file-lines");
1850 "range": [start_line
, end_line
]
1858 RustfmtConfig
::CustomCommand { command, args }
=> {
1859 let mut cmd
= process
::Command
::new(command
);
1860 cmd
.envs(snap
.config
.extra_env());
1866 let mut rustfmt
= command
1867 .stdin(Stdio
::piped())
1868 .stdout(Stdio
::piped())
1869 .stderr(Stdio
::piped())
1871 .context(format
!("Failed to spawn {command:?}"))?
;
1873 rustfmt
.stdin
.as_mut().unwrap().write_all(file
.as_bytes())?
;
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();
1879 if !output
.status
.success() {
1880 let rustfmt_not_installed
=
1881 captured_stderr
.contains("not installed") || captured_stderr
.contains("not available");
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.
1893 "rustfmt exited with status 1"
1898 // Something else happened - e.g. `rustfmt` is missing or caught a signal
1902 r
#"rustfmt exited with:
1904 stdout: {captured_stdout}
1905 stderr: {captured_stderr}"#,
1914 let (new_text
, new_line_endings
) = LineEndings
::normalize(captured_stdout
);
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
1920 Ok(Some(to_proto
::text_edit_vec(
1922 TextEdit
::replace(TextRange
::up_to(TextSize
::of(&*file
)), new_text
),
1924 } else if *file
== new_text
{
1925 // The document is already formatted correctly -- no edits needed.
1928 Ok(Some(to_proto
::text_edit_vec(&line_index
, diff(&file
, &new_text
))))