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}