1use crate::{lsp_command::LspCommand, lsp_store::LspStore, make_text_document_identifier};
2use anyhow::{Context as _, Result};
3use async_trait::async_trait;
4use gpui::{App, AsyncApp, Entity};
5use language::{Buffer, point_to_lsp, proto::deserialize_anchor};
6use lsp::{LanguageServer, LanguageServerId};
7use rpc::proto::{self, PeerId};
8use serde::{Deserialize, Serialize};
9use std::{path::Path, sync::Arc};
10use text::{BufferId, PointUtf16, ToPointUtf16};
11
12pub enum LspExpandMacro {}
13
14impl lsp::request::Request for LspExpandMacro {
15 type Params = ExpandMacroParams;
16 type Result = Option<ExpandedMacro>;
17 const METHOD: &'static str = "rust-analyzer/expandMacro";
18}
19
20#[derive(Deserialize, Serialize, Debug)]
21#[serde(rename_all = "camelCase")]
22pub struct ExpandMacroParams {
23 pub text_document: lsp::TextDocumentIdentifier,
24 pub position: lsp::Position,
25}
26
27#[derive(Default, Deserialize, Serialize, Debug)]
28#[serde(rename_all = "camelCase")]
29pub struct ExpandedMacro {
30 pub name: String,
31 pub expansion: String,
32}
33
34impl ExpandedMacro {
35 pub fn is_empty(&self) -> bool {
36 self.name.is_empty() && self.expansion.is_empty()
37 }
38}
39#[derive(Debug)]
40pub struct ExpandMacro {
41 pub position: PointUtf16,
42}
43
44#[async_trait(?Send)]
45impl LspCommand for ExpandMacro {
46 type Response = ExpandedMacro;
47 type LspRequest = LspExpandMacro;
48 type ProtoRequest = proto::LspExtExpandMacro;
49
50 fn display_name(&self) -> &str {
51 "Expand macro"
52 }
53
54 fn to_lsp(
55 &self,
56 path: &Path,
57 _: &Buffer,
58 _: &Arc<LanguageServer>,
59 _: &App,
60 ) -> Result<ExpandMacroParams> {
61 Ok(ExpandMacroParams {
62 text_document: make_text_document_identifier(path)?,
63 position: point_to_lsp(self.position),
64 })
65 }
66
67 async fn response_from_lsp(
68 self,
69 message: Option<ExpandedMacro>,
70 _: Entity<LspStore>,
71 _: Entity<Buffer>,
72 _: LanguageServerId,
73 _: AsyncApp,
74 ) -> anyhow::Result<ExpandedMacro> {
75 Ok(message
76 .map(|message| ExpandedMacro {
77 name: message.name,
78 expansion: message.expansion,
79 })
80 .unwrap_or_default())
81 }
82
83 fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtExpandMacro {
84 proto::LspExtExpandMacro {
85 project_id,
86 buffer_id: buffer.remote_id().into(),
87 position: Some(language::proto::serialize_anchor(
88 &buffer.anchor_before(self.position),
89 )),
90 }
91 }
92
93 async fn from_proto(
94 message: Self::ProtoRequest,
95 _: Entity<LspStore>,
96 buffer: Entity<Buffer>,
97 mut cx: AsyncApp,
98 ) -> anyhow::Result<Self> {
99 let position = message
100 .position
101 .and_then(deserialize_anchor)
102 .context("invalid position")?;
103 Ok(Self {
104 position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
105 })
106 }
107
108 fn response_to_proto(
109 response: ExpandedMacro,
110 _: &mut LspStore,
111 _: PeerId,
112 _: &clock::Global,
113 _: &mut App,
114 ) -> proto::LspExtExpandMacroResponse {
115 proto::LspExtExpandMacroResponse {
116 name: response.name,
117 expansion: response.expansion,
118 }
119 }
120
121 async fn response_from_proto(
122 self,
123 message: proto::LspExtExpandMacroResponse,
124 _: Entity<LspStore>,
125 _: Entity<Buffer>,
126 _: AsyncApp,
127 ) -> anyhow::Result<ExpandedMacro> {
128 Ok(ExpandedMacro {
129 name: message.name,
130 expansion: message.expansion,
131 })
132 }
133
134 fn buffer_id_from_proto(message: &proto::LspExtExpandMacro) -> Result<BufferId> {
135 BufferId::new(message.buffer_id)
136 }
137}
138
139pub enum LspOpenDocs {}
140
141impl lsp::request::Request for LspOpenDocs {
142 type Params = OpenDocsParams;
143 type Result = Option<DocsUrls>;
144 const METHOD: &'static str = "experimental/externalDocs";
145}
146
147#[derive(Serialize, Deserialize, Debug)]
148#[serde(rename_all = "camelCase")]
149pub struct OpenDocsParams {
150 pub text_document: lsp::TextDocumentIdentifier,
151 pub position: lsp::Position,
152}
153
154#[derive(Serialize, Deserialize, Debug, Default)]
155#[serde(rename_all = "camelCase")]
156pub struct DocsUrls {
157 pub web: Option<String>,
158 pub local: Option<String>,
159}
160
161impl DocsUrls {
162 pub fn is_empty(&self) -> bool {
163 self.web.is_none() && self.local.is_none()
164 }
165}
166
167#[derive(Debug)]
168pub struct OpenDocs {
169 pub position: PointUtf16,
170}
171
172#[async_trait(?Send)]
173impl LspCommand for OpenDocs {
174 type Response = DocsUrls;
175 type LspRequest = LspOpenDocs;
176 type ProtoRequest = proto::LspExtOpenDocs;
177
178 fn display_name(&self) -> &str {
179 "Open docs"
180 }
181
182 fn to_lsp(
183 &self,
184 path: &Path,
185 _: &Buffer,
186 _: &Arc<LanguageServer>,
187 _: &App,
188 ) -> Result<OpenDocsParams> {
189 Ok(OpenDocsParams {
190 text_document: lsp::TextDocumentIdentifier {
191 uri: lsp::Url::from_file_path(path).unwrap(),
192 },
193 position: point_to_lsp(self.position),
194 })
195 }
196
197 async fn response_from_lsp(
198 self,
199 message: Option<DocsUrls>,
200 _: Entity<LspStore>,
201 _: Entity<Buffer>,
202 _: LanguageServerId,
203 _: AsyncApp,
204 ) -> anyhow::Result<DocsUrls> {
205 Ok(message
206 .map(|message| DocsUrls {
207 web: message.web,
208 local: message.local,
209 })
210 .unwrap_or_default())
211 }
212
213 fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtOpenDocs {
214 proto::LspExtOpenDocs {
215 project_id,
216 buffer_id: buffer.remote_id().into(),
217 position: Some(language::proto::serialize_anchor(
218 &buffer.anchor_before(self.position),
219 )),
220 }
221 }
222
223 async fn from_proto(
224 message: Self::ProtoRequest,
225 _: Entity<LspStore>,
226 buffer: Entity<Buffer>,
227 mut cx: AsyncApp,
228 ) -> anyhow::Result<Self> {
229 let position = message
230 .position
231 .and_then(deserialize_anchor)
232 .context("invalid position")?;
233 Ok(Self {
234 position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
235 })
236 }
237
238 fn response_to_proto(
239 response: DocsUrls,
240 _: &mut LspStore,
241 _: PeerId,
242 _: &clock::Global,
243 _: &mut App,
244 ) -> proto::LspExtOpenDocsResponse {
245 proto::LspExtOpenDocsResponse {
246 web: response.web,
247 local: response.local,
248 }
249 }
250
251 async fn response_from_proto(
252 self,
253 message: proto::LspExtOpenDocsResponse,
254 _: Entity<LspStore>,
255 _: Entity<Buffer>,
256 _: AsyncApp,
257 ) -> anyhow::Result<DocsUrls> {
258 Ok(DocsUrls {
259 web: message.web,
260 local: message.local,
261 })
262 }
263
264 fn buffer_id_from_proto(message: &proto::LspExtOpenDocs) -> Result<BufferId> {
265 BufferId::new(message.buffer_id)
266 }
267}
268
269pub enum LspSwitchSourceHeader {}
270
271impl lsp::request::Request for LspSwitchSourceHeader {
272 type Params = SwitchSourceHeaderParams;
273 type Result = Option<SwitchSourceHeaderResult>;
274 const METHOD: &'static str = "textDocument/switchSourceHeader";
275}
276
277#[derive(Serialize, Deserialize, Debug)]
278#[serde(rename_all = "camelCase")]
279pub struct SwitchSourceHeaderParams(lsp::TextDocumentIdentifier);
280
281#[derive(Serialize, Deserialize, Debug, Default)]
282#[serde(rename_all = "camelCase")]
283pub struct SwitchSourceHeaderResult(pub String);
284
285#[derive(Default, Deserialize, Serialize, Debug)]
286#[serde(rename_all = "camelCase")]
287pub struct SwitchSourceHeader;
288
289#[async_trait(?Send)]
290impl LspCommand for SwitchSourceHeader {
291 type Response = SwitchSourceHeaderResult;
292 type LspRequest = LspSwitchSourceHeader;
293 type ProtoRequest = proto::LspExtSwitchSourceHeader;
294
295 fn display_name(&self) -> &str {
296 "Switch source header"
297 }
298
299 fn to_lsp(
300 &self,
301 path: &Path,
302 _: &Buffer,
303 _: &Arc<LanguageServer>,
304 _: &App,
305 ) -> Result<SwitchSourceHeaderParams> {
306 Ok(SwitchSourceHeaderParams(make_text_document_identifier(
307 path,
308 )?))
309 }
310
311 async fn response_from_lsp(
312 self,
313 message: Option<SwitchSourceHeaderResult>,
314 _: Entity<LspStore>,
315 _: Entity<Buffer>,
316 _: LanguageServerId,
317 _: AsyncApp,
318 ) -> anyhow::Result<SwitchSourceHeaderResult> {
319 Ok(message
320 .map(|message| SwitchSourceHeaderResult(message.0))
321 .unwrap_or_default())
322 }
323
324 fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtSwitchSourceHeader {
325 proto::LspExtSwitchSourceHeader {
326 project_id,
327 buffer_id: buffer.remote_id().into(),
328 }
329 }
330
331 async fn from_proto(
332 _: Self::ProtoRequest,
333 _: Entity<LspStore>,
334 _: Entity<Buffer>,
335 _: AsyncApp,
336 ) -> anyhow::Result<Self> {
337 Ok(Self {})
338 }
339
340 fn response_to_proto(
341 response: SwitchSourceHeaderResult,
342 _: &mut LspStore,
343 _: PeerId,
344 _: &clock::Global,
345 _: &mut App,
346 ) -> proto::LspExtSwitchSourceHeaderResponse {
347 proto::LspExtSwitchSourceHeaderResponse {
348 target_file: response.0,
349 }
350 }
351
352 async fn response_from_proto(
353 self,
354 message: proto::LspExtSwitchSourceHeaderResponse,
355 _: Entity<LspStore>,
356 _: Entity<Buffer>,
357 _: AsyncApp,
358 ) -> anyhow::Result<SwitchSourceHeaderResult> {
359 Ok(SwitchSourceHeaderResult(message.target_file))
360 }
361
362 fn buffer_id_from_proto(message: &proto::LspExtSwitchSourceHeader) -> Result<BufferId> {
363 BufferId::new(message.buffer_id)
364 }
365}