tinymist_query/code_action/
proto.rs

1use std::collections::HashMap;
2
3use ecow::EcoString;
4use lsp_types::{
5    ChangeAnnotation, ChangeAnnotationIdentifier, CodeActionDisabled, CodeActionKind, Command,
6    Diagnostic, InsertTextFormat, OneOf, OptionalVersionedTextDocumentIdentifier, ResourceOp, Url,
7};
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10
11use super::LspRange;
12use crate::completion::EcoTextEdit;
13
14/// A textual edit applicable to a text document.
15#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
16#[serde(rename_all = "camelCase")]
17pub struct EcoSnippetTextEdit {
18    /// The text edit
19    #[serde(flatten)]
20    edit: EcoTextEdit,
21    /// The format of the insert text. The format applies to both the
22    insert_text_format: Option<InsertTextFormat>,
23}
24
25impl EcoSnippetTextEdit {
26    /// Creates a new plain text edit.
27    pub fn new_plain(range: LspRange, new_text: EcoString) -> EcoSnippetTextEdit {
28        EcoSnippetTextEdit {
29            edit: EcoTextEdit::new(range, new_text),
30            insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
31        }
32    }
33
34    /// Creates a new snippet text edit.
35    pub fn new(range: LspRange, new_text: EcoString) -> EcoSnippetTextEdit {
36        EcoSnippetTextEdit {
37            edit: EcoTextEdit::new(range, new_text),
38            insert_text_format: Some(InsertTextFormat::SNIPPET),
39        }
40    }
41}
42
43/// A special text edit with an additional change annotation.
44///
45/// @since 3.16.0
46#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
47#[serde(rename_all = "camelCase")]
48pub struct EcoAnnotatedTextEdit {
49    /// The base text edit.
50    #[serde(flatten)]
51    pub text_edit: EcoSnippetTextEdit,
52
53    /// The actual annotation
54    pub annotation_id: ChangeAnnotationIdentifier,
55}
56
57/// Describes textual changes on a single text document. The text document is
58/// referred to as a `OptionalVersionedTextDocumentIdentifier` to allow clients
59/// to check the text document version before an edit is applied. A
60/// `TextDocumentEdit` describes all changes on a version Si and after they are
61/// applied move the document to version Si+1. So the creator of a
62/// `TextDocumentEdit` doesn't need to sort the array or do any kind of
63/// ordering. However the edits must be non overlapping.
64#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
65#[serde(rename_all = "camelCase")]
66pub struct EcoTextDocumentEdit {
67    /// The text document to change.
68    pub text_document: OptionalVersionedTextDocumentIdentifier,
69
70    /// The edits to be applied.
71    ///
72    /// @since 3.16.0 - support for AnnotatedTextEdit. This is guarded by the
73    /// client capability `workspace.workspaceEdit.changeAnnotationSupport`
74    pub edits: Vec<OneOf<EcoSnippetTextEdit, EcoAnnotatedTextEdit>>,
75}
76
77/// A code action represents to a single or multiple editor behaviors that can
78/// be triggered in a text document.
79#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
80#[serde(rename_all = "camelCase")]
81pub struct CodeAction {
82    /// A short, human-readable, title for this code action.
83    pub title: String,
84
85    /// The kind of the code action.
86    /// Used to filter code actions.
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub kind: Option<CodeActionKind>,
89
90    /// The diagnostics that this code action resolves.
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub diagnostics: Option<Vec<Diagnostic>>,
93
94    /// The workspace edit this code action performs.
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub edit: Option<EcoWorkspaceEdit>,
97
98    /// A command this code action executes. If a code action
99    /// provides an edit and a command, first the edit is
100    /// executed and then the command.
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub command: Option<Command>,
103
104    /// Marks this as a preferred action. Preferred actions are used by the
105    /// `auto fix` command and can be targeted by keybindings.
106    /// A quick fix should be marked preferred if it properly addresses the
107    /// underlying error. A refactoring should be marked preferred if it is
108    /// the most reasonable choice of actions to take.
109    ///
110    /// @since 3.15.0
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub is_preferred: Option<bool>,
113
114    /// Marks that the code action cannot currently be applied.
115    ///
116    /// Clients should follow the following guidelines regarding disabled code
117    /// actions:
118    ///
119    /// - Disabled code actions are not shown in automatic [lightbulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action)
120    ///   code action menu.
121    ///
122    /// - Disabled actions are shown as faded out in the code action menu when
123    ///   the user request a more specific type of code action, such as
124    ///   refactorings.
125    ///
126    /// - If the user has a keybinding that auto applies a code action and only
127    ///   a disabled code actions are returned, the client should show the user
128    ///   an error message with `reason` in the editor.
129    ///
130    /// @since 3.16.0
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub disabled: Option<CodeActionDisabled>,
133
134    /// A data entry field that is preserved on a code action between
135    /// a `textDocument/codeAction` and a `codeAction/resolve` request.
136    ///
137    /// @since 3.16.0
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub data: Option<Value>,
140}
141
142/// A workspace edit represents changes to many resources managed in the
143/// workspace. The edit should either provide `changes` or `documentChanges`.
144/// If the client can handle versioned document edits and if `documentChanges`
145/// are present, the latter are preferred over `changes`.
146#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
147#[serde(rename_all = "camelCase")]
148pub struct EcoWorkspaceEdit {
149    /// Holds changes to existing resources.
150    #[serde(with = "url_map")]
151    #[serde(skip_serializing_if = "Option::is_none")]
152    #[serde(default)]
153    pub changes: Option<HashMap<Url, Vec<EcoSnippetTextEdit>>>,
154
155    /// Depending on the client capability
156    /// `workspace.workspaceEdit.resourceOperations` document changes
157    /// are either an array of `TextDocumentEdit`s to express changes to n
158    /// different text documents where each text document edit addresses a
159    /// specific version of a text document. Or it can contain
160    /// above `TextDocumentEdit`s mixed with create, rename and delete file /
161    /// folder operations.
162    ///
163    /// Whether a client supports versioned document edits is expressed via
164    /// `workspace.workspaceEdit.documentChanges` client capability.
165    ///
166    /// If a client neither supports `documentChanges` nor
167    /// `workspace.workspaceEdit.resourceOperations` then only plain
168    /// `TextEdit`s using the `changes` property are supported.
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub document_changes: Option<EcoDocumentChanges>,
171
172    /// A map of change annotations that can be referenced in
173    /// `AnnotatedTextEdit`s or create, rename and delete file / folder
174    /// operations.
175    ///
176    /// Whether clients honor this property depends on the client capability
177    /// `workspace.changeAnnotationSupport`.
178    ///
179    /// @since 3.16.0
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub change_annotations: Option<HashMap<ChangeAnnotationIdentifier, ChangeAnnotation>>,
182}
183
184/// The `documentChanges` property of a `WorkspaceEdit` can contain
185/// `TextDocumentEdit`s to express changes to n different text documents
186/// where each text document edit addresses a specific version of a text
187/// document. Or it can contain create, rename and delete file / folder
188/// operations.
189#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
190#[serde(untagged)]
191pub enum EcoDocumentChanges {
192    /// Text document edits
193    Edits(Vec<EcoTextDocumentEdit>),
194    /// Resource operations
195    Operations(Vec<EcoDocumentChangeOperation>),
196}
197
198/// A resource operation represents changes to existing resources or
199/// creation of new resources. The operation can be a create, rename or
200/// delete operation.
201#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
202#[serde(untagged, rename_all = "lowercase")]
203pub enum EcoDocumentChangeOperation {
204    /// A resource operation.
205    Op(ResourceOp),
206    /// A text document edit.
207    Edit(EcoTextDocumentEdit),
208}
209
210mod url_map {
211    use std::marker::PhantomData;
212    use std::{collections::HashMap, fmt};
213
214    use lsp_types::Url;
215    use serde::de;
216
217    pub fn deserialize<'de, D, V>(deserializer: D) -> Result<Option<HashMap<Url, V>>, D::Error>
218    where
219        D: serde::Deserializer<'de>,
220        V: de::DeserializeOwned,
221    {
222        struct UrlMapVisitor<V> {
223            _marker: PhantomData<V>,
224        }
225
226        impl<V: de::DeserializeOwned> Default for UrlMapVisitor<V> {
227            fn default() -> Self {
228                UrlMapVisitor {
229                    _marker: PhantomData,
230                }
231            }
232        }
233        impl<'de, V: de::DeserializeOwned> de::Visitor<'de> for UrlMapVisitor<V> {
234            type Value = HashMap<Url, V>;
235
236            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
237                formatter.write_str("map")
238            }
239
240            fn visit_map<M>(self, mut visitor: M) -> Result<Self::Value, M::Error>
241            where
242                M: de::MapAccess<'de>,
243            {
244                let mut values = HashMap::with_capacity(visitor.size_hint().unwrap_or(0));
245
246                // While there are entries remaining in the input, add them
247                // into our map.
248                while let Some((key, value)) = visitor.next_entry::<Url, _>()? {
249                    values.insert(key, value);
250                }
251
252                Ok(values)
253            }
254        }
255
256        struct OptionUrlMapVisitor<V> {
257            _marker: PhantomData<V>,
258        }
259        impl<V: de::DeserializeOwned> Default for OptionUrlMapVisitor<V> {
260            fn default() -> Self {
261                OptionUrlMapVisitor {
262                    _marker: PhantomData,
263                }
264            }
265        }
266        impl<'de, V: de::DeserializeOwned> de::Visitor<'de> for OptionUrlMapVisitor<V> {
267            type Value = Option<HashMap<Url, V>>;
268
269            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
270                formatter.write_str("option")
271            }
272
273            #[inline]
274            fn visit_unit<E>(self) -> Result<Self::Value, E>
275            where
276                E: serde::de::Error,
277            {
278                Ok(None)
279            }
280
281            #[inline]
282            fn visit_none<E>(self) -> Result<Self::Value, E>
283            where
284                E: serde::de::Error,
285            {
286                Ok(None)
287            }
288
289            #[inline]
290            fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
291            where
292                D: serde::Deserializer<'de>,
293            {
294                deserializer
295                    .deserialize_map(UrlMapVisitor::<V>::default())
296                    .map(Some)
297            }
298        }
299
300        // Instantiate our Visitor and ask the Deserializer to drive
301        // it over the input data, resulting in an instance of MyMap.
302        deserializer.deserialize_option(OptionUrlMapVisitor::default())
303    }
304
305    pub fn serialize<S, V>(
306        changes: &Option<HashMap<Url, V>>,
307        serializer: S,
308    ) -> Result<S::Ok, S::Error>
309    where
310        S: serde::Serializer,
311        V: serde::Serialize,
312    {
313        use serde::ser::SerializeMap;
314
315        match *changes {
316            Some(ref changes) => {
317                let mut map = serializer.serialize_map(Some(changes.len()))?;
318                for (k, v) in changes {
319                    map.serialize_entry(k.as_str(), v)?;
320                }
321                map.end()
322            }
323            None => serializer.serialize_none(),
324        }
325    }
326}