rust_analyzer/lsp/
from_proto.rs

1//! Conversion lsp_types types to rust-analyzer specific ones.
2use anyhow::format_err;
3use ide::{Annotation, AnnotationKind, AssistKind, LineCol};
4use ide_db::{FileId, FilePosition, FileRange, line_index::WideLineCol};
5use paths::Utf8PathBuf;
6use syntax::{TextRange, TextSize};
7use vfs::AbsPathBuf;
8
9use crate::{
10    global_state::GlobalStateSnapshot,
11    line_index::{LineIndex, PositionEncoding},
12    lsp_ext, try_default,
13};
14
15pub(crate) fn abs_path(url: &lsp_types::Url) -> anyhow::Result<AbsPathBuf> {
16    let path = url.to_file_path().map_err(|()| anyhow::format_err!("url is not a file"))?;
17    Ok(AbsPathBuf::try_from(Utf8PathBuf::from_path_buf(path).unwrap()).unwrap())
18}
19
20pub(crate) fn vfs_path(url: &lsp_types::Url) -> anyhow::Result<vfs::VfsPath> {
21    abs_path(url).map(vfs::VfsPath::from)
22}
23
24pub(crate) fn offset(
25    line_index: &LineIndex,
26    position: lsp_types::Position,
27) -> anyhow::Result<TextSize> {
28    let line_col = match line_index.encoding {
29        PositionEncoding::Utf8 => LineCol { line: position.line, col: position.character },
30        PositionEncoding::Wide(enc) => {
31            let line_col = WideLineCol { line: position.line, col: position.character };
32            line_index
33                .index
34                .to_utf8(enc, line_col)
35                .ok_or_else(|| format_err!("Invalid wide col offset"))?
36        }
37    };
38    let line_range = line_index.index.line(line_col.line).ok_or_else(|| {
39        format_err!("Invalid offset {line_col:?} (line index length: {:?})", line_index.index.len())
40    })?;
41    let col = TextSize::from(line_col.col);
42    let clamped_len = col.min(line_range.len());
43    // FIXME: The cause for this is likely our request retrying. Commented out as this log is just too chatty and very easy to trigger.
44    // if clamped_len < col {
45    //     tracing::error!(
46    //         "Position {line_col:?} column exceeds line length {}, clamping it",
47    //         u32::from(line_range.len()),
48    //     );
49    // }
50    Ok(line_range.start() + clamped_len)
51}
52
53pub(crate) fn text_range(
54    line_index: &LineIndex,
55    range: lsp_types::Range,
56) -> anyhow::Result<TextRange> {
57    let start = offset(line_index, range.start)?;
58    let end = offset(line_index, range.end)?;
59    match end < start {
60        true => Err(format_err!("Invalid Range")),
61        false => Ok(TextRange::new(start, end)),
62    }
63}
64
65/// Returns `None` if the file was excluded.
66pub(crate) fn file_id(
67    snap: &GlobalStateSnapshot,
68    url: &lsp_types::Url,
69) -> anyhow::Result<Option<FileId>> {
70    snap.url_to_file_id(url)
71}
72
73/// Returns `None` if the file was excluded.
74pub(crate) fn file_position(
75    snap: &GlobalStateSnapshot,
76    tdpp: lsp_types::TextDocumentPositionParams,
77) -> anyhow::Result<Option<FilePosition>> {
78    let file_id = try_default!(file_id(snap, &tdpp.text_document.uri)?);
79    let line_index = snap.file_line_index(file_id)?;
80    let offset = offset(&line_index, tdpp.position)?;
81    Ok(Some(FilePosition { file_id, offset }))
82}
83
84/// Returns `None` if the file was excluded.
85pub(crate) fn file_range(
86    snap: &GlobalStateSnapshot,
87    text_document_identifier: &lsp_types::TextDocumentIdentifier,
88    range: lsp_types::Range,
89) -> anyhow::Result<Option<FileRange>> {
90    file_range_uri(snap, &text_document_identifier.uri, range)
91}
92
93/// Returns `None` if the file was excluded.
94pub(crate) fn file_range_uri(
95    snap: &GlobalStateSnapshot,
96    document: &lsp_types::Url,
97    range: lsp_types::Range,
98) -> anyhow::Result<Option<FileRange>> {
99    let file_id = try_default!(file_id(snap, document)?);
100    let line_index = snap.file_line_index(file_id)?;
101    let range = text_range(&line_index, range)?;
102    Ok(Some(FileRange { file_id, range }))
103}
104
105pub(crate) fn assist_kind(kind: lsp_types::CodeActionKind) -> Option<AssistKind> {
106    let assist_kind = match &kind {
107        k if k == &lsp_types::CodeActionKind::EMPTY => AssistKind::Generate,
108        k if k == &lsp_types::CodeActionKind::QUICKFIX => AssistKind::QuickFix,
109        k if k == &lsp_types::CodeActionKind::REFACTOR => AssistKind::Refactor,
110        k if k == &lsp_types::CodeActionKind::REFACTOR_EXTRACT => AssistKind::RefactorExtract,
111        k if k == &lsp_types::CodeActionKind::REFACTOR_INLINE => AssistKind::RefactorInline,
112        k if k == &lsp_types::CodeActionKind::REFACTOR_REWRITE => AssistKind::RefactorRewrite,
113        _ => return None,
114    };
115
116    Some(assist_kind)
117}
118
119/// Returns `None` if the file was excluded.
120pub(crate) fn annotation(
121    snap: &GlobalStateSnapshot,
122    range: lsp_types::Range,
123    data: lsp_ext::CodeLensResolveData,
124) -> anyhow::Result<Option<Annotation>> {
125    match data.kind {
126        lsp_ext::CodeLensResolveDataKind::Impls(params) => {
127            if snap.url_file_version(&params.text_document_position_params.text_document.uri)
128                != Some(data.version)
129            {
130                return Ok(None);
131            }
132            let pos @ FilePosition { file_id, .. } =
133                try_default!(file_position(snap, params.text_document_position_params)?);
134            let line_index = snap.file_line_index(file_id)?;
135
136            Ok(Annotation {
137                range: text_range(&line_index, range)?,
138                kind: AnnotationKind::HasImpls { pos, data: None },
139            })
140        }
141        lsp_ext::CodeLensResolveDataKind::References(params) => {
142            if snap.url_file_version(&params.text_document.uri) != Some(data.version) {
143                return Ok(None);
144            }
145            let pos @ FilePosition { file_id, .. } = try_default!(file_position(snap, params)?);
146            let line_index = snap.file_line_index(file_id)?;
147
148            Ok(Annotation {
149                range: text_range(&line_index, range)?,
150                kind: AnnotationKind::HasReferences { pos, data: None },
151            })
152        }
153    }
154    .map(Some)
155}