//! 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, 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 crate::{lsp_ext, semantic_tokens, world::WorldSnapshot, Result};
+use crate::{
+ 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 {
let line_col = line_index.line_col(offset);
text_edit.into_iter().map(|indel| self::text_edit(line_index, line_endings, indel)).collect()
}
+pub(crate) fn snippet_text_edit_vec(
+ line_index: &LineIndex,
+ line_endings: LineEndings,
+ is_snippet: bool,
+ text_edit: TextEdit,
+) -> Vec<lsp_ext::SnippetTextEdit> {
+ text_edit
+ .into_iter()
+ .map(|indel| self::snippet_text_edit(line_index, line_endings, is_snippet, indel))
+ .collect()
+}
+
pub(crate) fn completion_item(
line_index: &LineIndex,
line_endings: LineEndings,
HighlightTag::TypeAlias => semantic_tokens::TYPE_ALIAS,
HighlightTag::Trait => lsp_types::SemanticTokenType::INTERFACE,
HighlightTag::BuiltinType => semantic_tokens::BUILTIN_TYPE,
+ HighlightTag::SelfKeyword => semantic_tokens::SELF_KEYWORD,
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;
HighlightTag::ByteLiteral | HighlightTag::NumericLiteral => {
lsp_types::SemanticTokenType::NUMBER
}
+ HighlightTag::BoolLiteral => semantic_tokens::BOOLEAN,
HighlightTag::CharLiteral | HighlightTag::StringLiteral => {
lsp_types::SemanticTokenType::STRING
}
HighlightTag::Keyword => lsp_types::SemanticTokenType::KEYWORD,
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,
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);
}
}
-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,
- src: FileRange,
+ snap: &GlobalStateSnapshot,
+ src: Option<FileRange>,
target: NavigationTarget,
) -> Result<lsp_types::LocationLink> {
- let src_location = location(world, src)?;
- let (target_uri, target_range, target_selection_range) = location_info(world, target)?;
+ let origin_selection_range = match src {
+ Some(src) => {
+ 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(snap, target)?;
let res = lsp_types::LocationLink {
- origin_selection_range: Some(src_location.range),
+ origin_selection_range,
target_uri,
target_range,
target_selection_range,
}
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);
}
pub(crate) fn goto_definition_response(
- world: &WorldSnapshot,
- src: FileRange,
+ 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 {
.into_iter()
.map(|nav| {
location(
- world,
+ snap,
FileRange {
file_id: nav.file_id(),
range: nav.focus_range().unwrap_or(nav.range()),
}
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()
}
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 =
}
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 {
}
}
-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> =
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)
+ // `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");
+ }
+
+ #[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");
+ }
}