]> git.lizzy.rs Git - rust.git/blobdiff - crates/rust-analyzer/src/to_proto.rs
Implement rust-analyzer feature configuration to tests.
[rust.git] / crates / rust-analyzer / src / to_proto.rs
index 66144fe2411438de3feba12ec32093b5ebd1b967..652a4469468aaa903a79cf568f7b00c40211529d 100644 (file)
@@ -1,17 +1,19 @@
 //! Conversion of rust-analyzer specific types to lsp_types equivalents.
+use std::path::{self, Path};
+
+use itertools::Itertools;
 use ra_db::{FileId, FileRange};
 use ra_ide::{
     Assist, CompletionItem, CompletionItemKind, Documentation, FileSystemEdit, Fold, FoldKind,
     FunctionSignature, Highlight, HighlightModifier, HighlightTag, HighlightedRange, Indel,
-    InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, ReferenceAccess, Runnable,
-    RunnableKind, Severity, SourceChange, SourceFileEdit, TextEdit,
+    InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, ReferenceAccess,
+    ResolvedAssist, Runnable, Severity, SourceChange, SourceFileEdit, TextEdit,
 };
 use ra_syntax::{SyntaxKind, TextRange, TextSize};
-use ra_vfs::LineEndings;
-use rustc_hash::FxHashMap;
 
 use crate::{
-    cargo_target_spec::CargoTargetSpec, lsp_ext, semantic_tokens, world::WorldSnapshot, Result,
+    cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot,
+    line_endings::LineEndings, lsp_ext, semantic_tokens, Result,
 };
 
 pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position {
@@ -293,6 +295,7 @@ fn semantic_token_type_and_modifiers(
         HighlightTag::SelfType => lsp_types::SemanticTokenType::TYPE,
         HighlightTag::Field => lsp_types::SemanticTokenType::PROPERTY,
         HighlightTag::Function => lsp_types::SemanticTokenType::FUNCTION,
+        HighlightTag::Generic => semantic_tokens::GENERIC,
         HighlightTag::Module => lsp_types::SemanticTokenType::NAMESPACE,
         HighlightTag::Constant => {
             mods |= semantic_tokens::CONSTANT;
@@ -321,12 +324,15 @@ fn semantic_token_type_and_modifiers(
         HighlightTag::UnresolvedReference => semantic_tokens::UNRESOLVED_REFERENCE,
         HighlightTag::FormatSpecifier => semantic_tokens::FORMAT_SPECIFIER,
         HighlightTag::Operator => lsp_types::SemanticTokenType::OPERATOR,
+        HighlightTag::EscapeSequence => semantic_tokens::ESCAPE_SEQUENCE,
     };
 
     for modifier in highlight.modifiers.iter() {
         let modifier = match modifier {
             HighlightModifier::Attribute => semantic_tokens::ATTRIBUTE_MODIFIER,
             HighlightModifier::Definition => lsp_types::SemanticTokenModifier::DECLARATION,
+            HighlightModifier::Documentation => lsp_types::SemanticTokenModifier::DOCUMENTATION,
+            HighlightModifier::Injected => semantic_tokens::INJECTED,
             HighlightModifier::ControlFlow => semantic_tokens::CONTROL_FLOW,
             HighlightModifier::Mutable => semantic_tokens::MUTABLE,
             HighlightModifier::Unsafe => semantic_tokens::UNSAFE,
@@ -346,7 +352,7 @@ pub(crate) fn folding_range(
     let kind = match fold.kind {
         FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment),
         FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports),
-        FoldKind::Mods | FoldKind::Block => None,
+        FoldKind::Mods | FoldKind::Block | FoldKind::ArgList => None,
     };
 
     let range = range(line_index, fold.range);
@@ -385,41 +391,87 @@ pub(crate) fn folding_range(
     }
 }
 
-pub(crate) fn url(world: &WorldSnapshot, file_id: FileId) -> Result<lsp_types::Url> {
-    world.file_id_to_uri(file_id)
+pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> lsp_types::Url {
+    snap.file_id_to_url(file_id)
+}
+
+/// Returns a `Url` object from a given path, will lowercase drive letters if present.
+/// This will only happen when processing windows paths.
+///
+/// When processing non-windows path, this is essentially the same as `Url::from_file_path`.
+pub(crate) fn url_from_abs_path(path: &Path) -> lsp_types::Url {
+    assert!(path.is_absolute());
+    let url = lsp_types::Url::from_file_path(path).unwrap();
+    match path.components().next() {
+        Some(path::Component::Prefix(prefix)) if matches!(prefix.kind(), path::Prefix::Disk(_) | path::Prefix::VerbatimDisk(_)) =>
+        {
+            // Need to lowercase driver letter
+        }
+        _ => return url,
+    }
+
+    let driver_letter_range = {
+        let (scheme, drive_letter, _rest) = match url.as_str().splitn(3, ':').collect_tuple() {
+            Some(it) => it,
+            None => return url,
+        };
+        let start = scheme.len() + ':'.len_utf8();
+        start..(start + drive_letter.len())
+    };
+
+    // Note: lowercasing the `path` itself doesn't help, the `Url::parse`
+    // machinery *also* canonicalizes the drive letter. So, just massage the
+    // string in place.
+    let mut url = url.into_string();
+    url[driver_letter_range].make_ascii_lowercase();
+    lsp_types::Url::parse(&url).unwrap()
 }
 
 pub(crate) fn versioned_text_document_identifier(
-    world: &WorldSnapshot,
+    snap: &GlobalStateSnapshot,
     file_id: FileId,
     version: Option<i64>,
-) -> Result<lsp_types::VersionedTextDocumentIdentifier> {
-    let res = lsp_types::VersionedTextDocumentIdentifier { uri: url(world, file_id)?, version };
-    Ok(res)
+) -> lsp_types::VersionedTextDocumentIdentifier {
+    lsp_types::VersionedTextDocumentIdentifier { uri: url(snap, file_id), version }
 }
 
-pub(crate) fn location(world: &WorldSnapshot, frange: FileRange) -> Result<lsp_types::Location> {
-    let url = url(world, frange.file_id)?;
-    let line_index = world.analysis().file_line_index(frange.file_id)?;
+pub(crate) fn location(
+    snap: &GlobalStateSnapshot,
+    frange: FileRange,
+) -> Result<lsp_types::Location> {
+    let url = url(snap, frange.file_id);
+    let line_index = snap.analysis.file_line_index(frange.file_id)?;
     let range = range(&line_index, frange.range);
     let loc = lsp_types::Location::new(url, range);
     Ok(loc)
 }
 
+/// Perefer using `location_link`, if the client has the cap.
+pub(crate) fn location_from_nav(
+    snap: &GlobalStateSnapshot,
+    nav: NavigationTarget,
+) -> Result<lsp_types::Location> {
+    let url = url(snap, nav.file_id());
+    let line_index = snap.analysis.file_line_index(nav.file_id())?;
+    let range = range(&line_index, nav.full_range());
+    let loc = lsp_types::Location::new(url, range);
+    Ok(loc)
+}
+
 pub(crate) fn location_link(
-    world: &WorldSnapshot,
+    snap: &GlobalStateSnapshot,
     src: Option<FileRange>,
     target: NavigationTarget,
 ) -> Result<lsp_types::LocationLink> {
     let origin_selection_range = match src {
         Some(src) => {
-            let line_index = world.analysis().file_line_index(src.file_id)?;
+            let line_index = snap.analysis.file_line_index(src.file_id)?;
             let range = range(&line_index, src.range);
             Some(range)
         }
         None => None,
     };
-    let (target_uri, target_range, target_selection_range) = location_info(world, target)?;
+    let (target_uri, target_range, target_selection_range) = location_info(snap, target)?;
     let res = lsp_types::LocationLink {
         origin_selection_range,
         target_uri,
@@ -430,12 +482,12 @@ pub(crate) fn location_link(
 }
 
 fn location_info(
-    world: &WorldSnapshot,
+    snap: &GlobalStateSnapshot,
     target: NavigationTarget,
 ) -> Result<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> {
-    let line_index = world.analysis().file_line_index(target.file_id())?;
+    let line_index = snap.analysis.file_line_index(target.file_id())?;
 
-    let target_uri = url(world, target.file_id())?;
+    let target_uri = url(snap, target.file_id());
     let target_range = range(&line_index, target.full_range());
     let target_selection_range =
         target.focus_range().map(|it| range(&line_index, it)).unwrap_or(target_range);
@@ -443,14 +495,14 @@ fn location_info(
 }
 
 pub(crate) fn goto_definition_response(
-    world: &WorldSnapshot,
+    snap: &GlobalStateSnapshot,
     src: Option<FileRange>,
     targets: Vec<NavigationTarget>,
 ) -> Result<lsp_types::GotoDefinitionResponse> {
-    if world.config.client_caps.location_link {
+    if snap.config.client_caps.location_link {
         let links = targets
             .into_iter()
-            .map(|nav| location_link(world, src, nav))
+            .map(|nav| location_link(snap, src, nav))
             .collect::<Result<Vec<_>>>()?;
         Ok(links.into())
     } else {
@@ -458,7 +510,7 @@ pub(crate) fn goto_definition_response(
             .into_iter()
             .map(|nav| {
                 location(
-                    world,
+                    snap,
                     FileRange {
                         file_id: nav.file_id(),
                         range: nav.focus_range().unwrap_or(nav.range()),
@@ -471,13 +523,13 @@ pub(crate) fn goto_definition_response(
 }
 
 pub(crate) fn snippet_text_document_edit(
-    world: &WorldSnapshot,
+    snap: &GlobalStateSnapshot,
     is_snippet: bool,
     source_file_edit: SourceFileEdit,
 ) -> Result<lsp_ext::SnippetTextDocumentEdit> {
-    let text_document = versioned_text_document_identifier(world, source_file_edit.file_id, None)?;
-    let line_index = world.analysis().file_line_index(source_file_edit.file_id)?;
-    let line_endings = world.file_line_endings(source_file_edit.file_id);
+    let text_document = versioned_text_document_identifier(snap, source_file_edit.file_id, None);
+    let line_index = snap.analysis.file_line_index(source_file_edit.file_id)?;
+    let line_endings = snap.file_line_endings(source_file_edit.file_id);
     let edits = source_file_edit
         .edit
         .into_iter()
@@ -487,34 +539,33 @@ pub(crate) fn snippet_text_document_edit(
 }
 
 pub(crate) fn resource_op(
-    world: &WorldSnapshot,
+    snap: &GlobalStateSnapshot,
     file_system_edit: FileSystemEdit,
-) -> Result<lsp_types::ResourceOp> {
-    let res = match file_system_edit {
-        FileSystemEdit::CreateFile { source_root, path } => {
-            let uri = world.path_to_uri(source_root, &path)?;
+) -> lsp_types::ResourceOp {
+    match file_system_edit {
+        FileSystemEdit::CreateFile { anchor, dst } => {
+            let uri = snap.anchored_path(anchor, &dst);
             lsp_types::ResourceOp::Create(lsp_types::CreateFile { uri, options: None })
         }
-        FileSystemEdit::MoveFile { src, dst_source_root, dst_path } => {
-            let old_uri = world.file_id_to_uri(src)?;
-            let new_uri = world.path_to_uri(dst_source_root, &dst_path)?;
+        FileSystemEdit::MoveFile { src, anchor, dst } => {
+            let old_uri = snap.file_id_to_url(src);
+            let new_uri = snap.anchored_path(anchor, &dst);
             lsp_types::ResourceOp::Rename(lsp_types::RenameFile { old_uri, new_uri, options: None })
         }
-    };
-    Ok(res)
+    }
 }
 
 pub(crate) fn snippet_workspace_edit(
-    world: &WorldSnapshot,
+    snap: &GlobalStateSnapshot,
     source_change: SourceChange,
 ) -> Result<lsp_ext::SnippetWorkspaceEdit> {
     let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new();
     for op in source_change.file_system_edits {
-        let op = resource_op(&world, op)?;
+        let op = resource_op(&snap, op);
         document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Op(op));
     }
     for edit in source_change.source_file_edits {
-        let edit = snippet_text_document_edit(&world, source_change.is_snippet, edit)?;
+        let edit = snippet_text_document_edit(&snap, source_change.is_snippet, edit)?;
         document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit));
     }
     let workspace_edit =
@@ -523,11 +574,11 @@ pub(crate) fn snippet_workspace_edit(
 }
 
 pub(crate) fn workspace_edit(
-    world: &WorldSnapshot,
+    snap: &GlobalStateSnapshot,
     source_change: SourceChange,
 ) -> Result<lsp_types::WorkspaceEdit> {
     assert!(!source_change.is_snippet);
-    snippet_workspace_edit(world, source_change).map(|it| it.into())
+    snippet_workspace_edit(snap, source_change).map(|it| it.into())
 }
 
 impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit {
@@ -565,45 +616,96 @@ fn from(snippet_workspace_edit: lsp_ext::SnippetWorkspaceEdit) -> lsp_types::Wor
     }
 }
 
-pub fn call_hierarchy_item(
-    world: &WorldSnapshot,
+pub(crate) fn call_hierarchy_item(
+    snap: &GlobalStateSnapshot,
     target: NavigationTarget,
 ) -> Result<lsp_types::CallHierarchyItem> {
     let name = target.name().to_string();
     let detail = target.description().map(|it| it.to_string());
     let kind = symbol_kind(target.kind());
-    let (uri, range, selection_range) = location_info(world, target)?;
+    let (uri, range, selection_range) = location_info(snap, target)?;
     Ok(lsp_types::CallHierarchyItem { name, kind, tags: None, detail, uri, range, selection_range })
 }
 
+pub(crate) fn unresolved_code_action(
+    snap: &GlobalStateSnapshot,
+    assist: Assist,
+    index: usize,
+) -> Result<lsp_ext::CodeAction> {
+    let res = lsp_ext::CodeAction {
+        title: assist.label,
+        id: Some(format!("{}:{}", assist.id.0.to_owned(), index.to_string())),
+        group: assist.group.filter(|_| snap.config.client_caps.code_action_group).map(|gr| gr.0),
+        kind: Some(String::new()),
+        edit: None,
+        command: None,
+    };
+    Ok(res)
+}
+
+pub(crate) fn resolved_code_action(
+    snap: &GlobalStateSnapshot,
+    assist: ResolvedAssist,
+) -> Result<lsp_ext::CodeAction> {
+    let change = assist.source_change;
+    unresolved_code_action(snap, assist.assist, 0).and_then(|it| {
+        Ok(lsp_ext::CodeAction {
+            id: None,
+            edit: Some(snippet_workspace_edit(snap, change)?),
+            ..it
+        })
+    })
+}
+
+pub(crate) fn runnable(
+    snap: &GlobalStateSnapshot,
+    file_id: FileId,
+    runnable: Runnable,
+) -> Result<lsp_ext::Runnable> {
+    let spec = CargoTargetSpec::for_file(snap, file_id)?;
+    let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone());
+    let target = spec.as_ref().map(|s| s.target.clone());
+    let (cargo_args, executable_args) =
+        CargoTargetSpec::runnable_args(snap, spec, &runnable.kind, &runnable.cfg_exprs)?;
+    let label = runnable.label(target);
+    let location = location_link(snap, None, runnable.nav)?;
+
+    Ok(lsp_ext::Runnable {
+        label,
+        location: Some(location),
+        kind: lsp_ext::RunnableKind::Cargo,
+        args: lsp_ext::CargoRunnable {
+            workspace_root: workspace_root.map(|it| it.into()),
+            cargo_args,
+            executable_args,
+            expect_test: None,
+        },
+    })
+}
+
 #[cfg(test)]
 mod tests {
-    use test_utils::extract_ranges;
+    use ra_ide::Analysis;
 
     use super::*;
 
     #[test]
     fn conv_fold_line_folding_only_fixup() {
-        let text = r#"<fold>mod a;
+        let text = r#"mod a;
 mod b;
-mod c;</fold>
+mod c;
 
-fn main() <fold>{
-    if cond <fold>{
+fn main() {
+    if cond {
         a::do_a();
-    }</fold> else <fold>{
+    } else {
         b::do_b();
-    }</fold>
-}</fold>"#;
-
-        let (ranges, text) = extract_ranges(text, "fold");
-        assert_eq!(ranges.len(), 4);
-        let folds = vec![
-            Fold { range: ranges[0], kind: FoldKind::Mods },
-            Fold { range: ranges[1], kind: FoldKind::Block },
-            Fold { range: ranges[2], kind: FoldKind::Block },
-            Fold { range: ranges[3], kind: FoldKind::Block },
-        ];
+    }
+}"#;
+
+        let (analysis, file_id) = Analysis::from_single_file(text.to_string());
+        let folds = analysis.folding_ranges(file_id).unwrap();
+        assert_eq!(folds.len(), 4);
 
         let line_index = LineIndex::new(&text);
         let converted: Vec<lsp_types::FoldingRange> =
@@ -618,50 +720,19 @@ fn main() <fold>{
             assert_eq!(folding_range.end_character, None);
         }
     }
-}
 
-pub(crate) fn code_action(world: &WorldSnapshot, assist: Assist) -> Result<lsp_ext::CodeAction> {
-    let res = lsp_ext::CodeAction {
-        title: assist.label,
-        group: if world.config.client_caps.code_action_group { assist.group_label } else { None },
-        kind: Some(String::new()),
-        edit: Some(snippet_workspace_edit(world, assist.source_change)?),
-        command: None,
-    };
-    Ok(res)
-}
-
-pub(crate) fn runnable(
-    world: &WorldSnapshot,
-    file_id: FileId,
-    runnable: Runnable,
-) -> Result<lsp_ext::Runnable> {
-    let spec = CargoTargetSpec::for_file(world, file_id)?;
-    let target = spec.as_ref().map(|s| s.target.clone());
-    let (args, extra_args) =
-        CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.cfg_exprs)?;
-    let line_index = world.analysis().file_line_index(file_id)?;
-    let label = match &runnable.kind {
-        RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
-        RunnableKind::TestMod { path } => format!("test-mod {}", path),
-        RunnableKind::Bench { test_id } => format!("bench {}", test_id),
-        RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id),
-        RunnableKind::Bin => {
-            target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t))
-        }
-    };
+    // `Url` is not able to parse windows paths on unix machines.
+    #[test]
+    #[cfg(target_os = "windows")]
+    fn test_lowercase_drive_letter_with_drive() {
+        let url = url_from_abs_path(Path::new("C:\\Test"));
+        assert_eq!(url.to_string(), "file:///c:/Test");
+    }
 
-    Ok(lsp_ext::Runnable {
-        range: range(&line_index, runnable.range),
-        label,
-        kind: lsp_ext::RunnableKind::Cargo,
-        args,
-        extra_args,
-        env: {
-            let mut m = FxHashMap::default();
-            m.insert("RUST_BACKTRACE".to_string(), "short".to_string());
-            m
-        },
-        cwd: world.workspace_root_for(file_id).map(|root| root.to_owned()),
-    })
+    #[test]
+    #[cfg(target_os = "windows")]
+    fn test_drive_without_colon_passthrough() {
+        let url = url_from_abs_path(Path::new(r#"\\localhost\C$\my_dir"#));
+        assert_eq!(url.to_string(), "file://localhost/C$/my_dir");
+    }
 }