rust_analyzer/
lsp.rs

1//! Custom LSP definitions and protocol conversions.
2
3use core::fmt;
4
5use hir::Mutability;
6use ide::{CompletionItem, CompletionItemRefMode, CompletionRelevance};
7use tenthash::TentHash;
8
9pub mod ext;
10
11pub(crate) mod capabilities;
12pub(crate) mod from_proto;
13pub(crate) mod semantic_tokens;
14pub(crate) mod to_proto;
15pub(crate) mod utils;
16
17#[derive(Debug)]
18pub(crate) struct LspError {
19    pub(crate) code: i32,
20    pub(crate) message: String,
21}
22
23impl LspError {
24    pub(crate) fn new(code: i32, message: String) -> LspError {
25        LspError { code, message }
26    }
27}
28
29impl fmt::Display for LspError {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        write!(f, "Language Server request failed with {}. ({})", self.code, self.message)
32    }
33}
34
35impl std::error::Error for LspError {}
36
37pub(crate) fn completion_item_hash(item: &CompletionItem, is_ref_completion: bool) -> [u8; 20] {
38    fn hash_completion_relevance(hasher: &mut TentHash, relevance: &CompletionRelevance) {
39        use ide_completion::{
40            CompletionRelevancePostfixMatch, CompletionRelevanceReturnType,
41            CompletionRelevanceTypeMatch,
42        };
43
44        hasher.update([
45            u8::from(relevance.exact_name_match),
46            u8::from(relevance.is_local),
47            u8::from(relevance.is_name_already_imported),
48            u8::from(relevance.requires_import),
49            u8::from(relevance.is_private_editable),
50        ]);
51
52        match relevance.type_match {
53            None => hasher.update([0u8]),
54            Some(CompletionRelevanceTypeMatch::CouldUnify) => hasher.update([1u8]),
55            Some(CompletionRelevanceTypeMatch::Exact) => hasher.update([2u8]),
56        }
57
58        hasher.update([u8::from(relevance.trait_.is_some())]);
59        if let Some(trait_) = &relevance.trait_ {
60            hasher.update([u8::from(trait_.is_op_method), u8::from(trait_.notable_trait)]);
61        }
62
63        match relevance.postfix_match {
64            None => hasher.update([0u8]),
65            Some(CompletionRelevancePostfixMatch::NonExact) => hasher.update([1u8]),
66            Some(CompletionRelevancePostfixMatch::Exact) => hasher.update([2u8]),
67        }
68
69        hasher.update([u8::from(relevance.function.is_some())]);
70        if let Some(function) = &relevance.function {
71            hasher.update([u8::from(function.has_params), u8::from(function.has_self_param)]);
72            let discriminant: u8 = match function.return_type {
73                CompletionRelevanceReturnType::Other => 0,
74                CompletionRelevanceReturnType::DirectConstructor => 1,
75                CompletionRelevanceReturnType::Constructor => 2,
76                CompletionRelevanceReturnType::Builder => 3,
77            };
78            hasher.update([discriminant]);
79        }
80    }
81
82    let mut hasher = TentHash::new();
83    hasher.update([
84        u8::from(is_ref_completion),
85        u8::from(item.is_snippet),
86        u8::from(item.deprecated),
87        u8::from(item.trigger_call_info),
88    ]);
89
90    hasher.update(item.label.primary.len().to_ne_bytes());
91    hasher.update(&item.label.primary);
92
93    hasher.update([u8::from(item.label.detail_left.is_some())]);
94    if let Some(label_detail) = &item.label.detail_left {
95        hasher.update(label_detail.len().to_ne_bytes());
96        hasher.update(label_detail);
97    }
98
99    hasher.update([u8::from(item.label.detail_right.is_some())]);
100    if let Some(label_detail) = &item.label.detail_right {
101        hasher.update(label_detail.len().to_ne_bytes());
102        hasher.update(label_detail);
103    }
104
105    // NB: do not hash edits or source range, as those may change between the time the client sends the resolve request
106    // and the time it receives it: some editors do allow changing the buffer between that, leading to ranges being different.
107    //
108    // Documentation hashing is skipped too, as it's a large blob to process,
109    // while not really making completion properties more unique as they are already.
110
111    let kind_tag = item.kind.tag();
112    hasher.update(kind_tag.len().to_ne_bytes());
113    hasher.update(kind_tag);
114
115    hasher.update(item.lookup.len().to_ne_bytes());
116    hasher.update(&item.lookup);
117
118    hasher.update([u8::from(item.detail.is_some())]);
119    if let Some(detail) = &item.detail {
120        hasher.update(detail.len().to_ne_bytes());
121        hasher.update(detail);
122    }
123
124    hash_completion_relevance(&mut hasher, &item.relevance);
125
126    hasher.update([u8::from(item.ref_match.is_some())]);
127    if let Some((ref_mode, text_size)) = &item.ref_match {
128        let discriminant = match ref_mode {
129            CompletionItemRefMode::Reference(Mutability::Shared) => 0u8,
130            CompletionItemRefMode::Reference(Mutability::Mut) => 1u8,
131            CompletionItemRefMode::Dereference => 2u8,
132        };
133        hasher.update([discriminant]);
134        hasher.update(u32::from(*text_size).to_ne_bytes());
135    }
136
137    hasher.update(item.import_to_add.len().to_ne_bytes());
138    for import_path in &item.import_to_add {
139        hasher.update(import_path.len().to_ne_bytes());
140        hasher.update(import_path);
141    }
142
143    hasher.finalize()
144}