rust_analyzer/
lsp.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//! Custom LSP definitions and protocol conversions.

use core::fmt;

use hir::Mutability;
use ide::{CompletionItem, CompletionItemRefMode, CompletionRelevance};
use tenthash::TentHash;

pub mod ext;

pub(crate) mod capabilities;
pub(crate) mod from_proto;
pub(crate) mod semantic_tokens;
pub(crate) mod to_proto;
pub(crate) mod utils;

#[derive(Debug)]
pub(crate) struct LspError {
    pub(crate) code: i32,
    pub(crate) message: String,
}

impl LspError {
    pub(crate) fn new(code: i32, message: String) -> LspError {
        LspError { code, message }
    }
}

impl fmt::Display for LspError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Language Server request failed with {}. ({})", self.code, self.message)
    }
}

impl std::error::Error for LspError {}

pub(crate) fn completion_item_hash(item: &CompletionItem, is_ref_completion: bool) -> [u8; 20] {
    fn hash_completion_relevance(hasher: &mut TentHash, relevance: &CompletionRelevance) {
        use ide_completion::{
            CompletionRelevancePostfixMatch, CompletionRelevanceReturnType,
            CompletionRelevanceTypeMatch,
        };

        hasher.update([
            u8::from(relevance.exact_name_match),
            u8::from(relevance.is_local),
            u8::from(relevance.is_name_already_imported),
            u8::from(relevance.requires_import),
            u8::from(relevance.is_private_editable),
        ]);

        match relevance.type_match {
            None => hasher.update([0u8]),
            Some(CompletionRelevanceTypeMatch::CouldUnify) => hasher.update([1u8]),
            Some(CompletionRelevanceTypeMatch::Exact) => hasher.update([2u8]),
        }

        hasher.update([u8::from(relevance.trait_.is_some())]);
        if let Some(trait_) = &relevance.trait_ {
            hasher.update([u8::from(trait_.is_op_method), u8::from(trait_.notable_trait)]);
        }

        match relevance.postfix_match {
            None => hasher.update([0u8]),
            Some(CompletionRelevancePostfixMatch::NonExact) => hasher.update([1u8]),
            Some(CompletionRelevancePostfixMatch::Exact) => hasher.update([2u8]),
        }

        hasher.update([u8::from(relevance.function.is_some())]);
        if let Some(function) = &relevance.function {
            hasher.update([u8::from(function.has_params), u8::from(function.has_self_param)]);
            let discriminant: u8 = match function.return_type {
                CompletionRelevanceReturnType::Other => 0,
                CompletionRelevanceReturnType::DirectConstructor => 1,
                CompletionRelevanceReturnType::Constructor => 2,
                CompletionRelevanceReturnType::Builder => 3,
            };
            hasher.update([discriminant]);
        }
    }

    let mut hasher = TentHash::new();
    hasher.update([
        u8::from(is_ref_completion),
        u8::from(item.is_snippet),
        u8::from(item.deprecated),
        u8::from(item.trigger_call_info),
    ]);

    hasher.update(item.label.primary.len().to_ne_bytes());
    hasher.update(&item.label.primary);

    hasher.update([u8::from(item.label.detail_left.is_some())]);
    if let Some(label_detail) = &item.label.detail_left {
        hasher.update(label_detail.len().to_ne_bytes());
        hasher.update(label_detail);
    }

    hasher.update([u8::from(item.label.detail_right.is_some())]);
    if let Some(label_detail) = &item.label.detail_right {
        hasher.update(label_detail.len().to_ne_bytes());
        hasher.update(label_detail);
    }

    // NB: do not hash edits or source range, as those may change between the time the client sends the resolve request
    // and the time it receives it: some editors do allow changing the buffer between that, leading to ranges being different.
    //
    // Documentation hashing is skipped too, as it's a large blob to process,
    // while not really making completion properties more unique as they are already.

    let kind_tag = item.kind.tag();
    hasher.update(kind_tag.len().to_ne_bytes());
    hasher.update(kind_tag);

    hasher.update(item.lookup.len().to_ne_bytes());
    hasher.update(&item.lookup);

    hasher.update([u8::from(item.detail.is_some())]);
    if let Some(detail) = &item.detail {
        hasher.update(detail.len().to_ne_bytes());
        hasher.update(detail);
    }

    hash_completion_relevance(&mut hasher, &item.relevance);

    hasher.update([u8::from(item.ref_match.is_some())]);
    if let Some((ref_mode, text_size)) = &item.ref_match {
        let discriminant = match ref_mode {
            CompletionItemRefMode::Reference(Mutability::Shared) => 0u8,
            CompletionItemRefMode::Reference(Mutability::Mut) => 1u8,
            CompletionItemRefMode::Dereference => 2u8,
        };
        hasher.update([discriminant]);
        hasher.update(u32::from(*text_size).to_ne_bytes());
    }

    hasher.update(item.import_to_add.len().to_ne_bytes());
    for import_path in &item.import_to_add {
        hasher.update(import_path.len().to_ne_bytes());
        hasher.update(import_path);
    }

    hasher.finalize()
}