lsp_ext_command.rs

  1use crate::{
  2    LocationLink,
  3    lsp_command::{
  4        LspCommand, file_path_to_lsp_url, location_link_from_lsp, location_link_from_proto,
  5        location_link_to_proto, location_links_from_lsp, location_links_from_proto,
  6        location_links_to_proto,
  7    },
  8    lsp_store::LspStore,
  9    make_lsp_text_document_position, make_text_document_identifier,
 10};
 11use anyhow::{Context as _, Result};
 12use async_trait::async_trait;
 13use collections::HashMap;
 14use gpui::{App, AsyncApp, Entity};
 15use language::{
 16    Buffer, point_to_lsp,
 17    proto::{deserialize_anchor, serialize_anchor},
 18};
 19use lsp::{AdapterServerCapabilities, LanguageServer, LanguageServerId};
 20use rpc::proto::{self, PeerId};
 21use serde::{Deserialize, Serialize};
 22use std::{
 23    path::{Path, PathBuf},
 24    sync::Arc,
 25};
 26use task::{ShellKind, TaskTemplate};
 27use text::{BufferId, PointUtf16, ToPointUtf16};
 28
 29pub enum LspExtExpandMacro {}
 30
 31impl lsp::request::Request for LspExtExpandMacro {
 32    type Params = ExpandMacroParams;
 33    type Result = Option<ExpandedMacro>;
 34    const METHOD: &'static str = "rust-analyzer/expandMacro";
 35}
 36
 37#[derive(Deserialize, Serialize, Debug)]
 38#[serde(rename_all = "camelCase")]
 39pub struct ExpandMacroParams {
 40    pub text_document: lsp::TextDocumentIdentifier,
 41    pub position: lsp::Position,
 42}
 43
 44#[derive(Default, Deserialize, Serialize, Debug)]
 45#[serde(rename_all = "camelCase")]
 46pub struct ExpandedMacro {
 47    pub name: String,
 48    pub expansion: String,
 49}
 50
 51impl ExpandedMacro {
 52    pub fn is_empty(&self) -> bool {
 53        self.name.is_empty() && self.expansion.is_empty()
 54    }
 55}
 56#[derive(Debug)]
 57pub struct ExpandMacro {
 58    pub position: PointUtf16,
 59}
 60
 61#[async_trait(?Send)]
 62impl LspCommand for ExpandMacro {
 63    type Response = ExpandedMacro;
 64    type LspRequest = LspExtExpandMacro;
 65    type ProtoRequest = proto::LspExtExpandMacro;
 66
 67    fn display_name(&self) -> &str {
 68        "Expand macro"
 69    }
 70
 71    fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool {
 72        true
 73    }
 74
 75    fn to_lsp(
 76        &self,
 77        path: &Path,
 78        _: &Buffer,
 79        _: &Arc<LanguageServer>,
 80        _: &App,
 81    ) -> Result<ExpandMacroParams> {
 82        Ok(ExpandMacroParams {
 83            text_document: make_text_document_identifier(path)?,
 84            position: point_to_lsp(self.position),
 85        })
 86    }
 87
 88    async fn response_from_lsp(
 89        self,
 90        message: Option<ExpandedMacro>,
 91        _: Entity<LspStore>,
 92        _: Entity<Buffer>,
 93        _: LanguageServerId,
 94        _: AsyncApp,
 95    ) -> anyhow::Result<ExpandedMacro> {
 96        Ok(message
 97            .map(|message| ExpandedMacro {
 98                name: message.name,
 99                expansion: message.expansion,
100            })
101            .unwrap_or_default())
102    }
103
104    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtExpandMacro {
105        proto::LspExtExpandMacro {
106            project_id,
107            buffer_id: buffer.remote_id().into(),
108            position: Some(language::proto::serialize_anchor(
109                &buffer.anchor_before(self.position),
110            )),
111        }
112    }
113
114    async fn from_proto(
115        message: Self::ProtoRequest,
116        _: Entity<LspStore>,
117        buffer: Entity<Buffer>,
118        cx: AsyncApp,
119    ) -> anyhow::Result<Self> {
120        let position = message
121            .position
122            .and_then(deserialize_anchor)
123            .context("invalid position")?;
124        Ok(Self {
125            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?,
126        })
127    }
128
129    fn response_to_proto(
130        response: ExpandedMacro,
131        _: &mut LspStore,
132        _: PeerId,
133        _: &clock::Global,
134        _: &mut App,
135    ) -> proto::LspExtExpandMacroResponse {
136        proto::LspExtExpandMacroResponse {
137            name: response.name,
138            expansion: response.expansion,
139        }
140    }
141
142    async fn response_from_proto(
143        self,
144        message: proto::LspExtExpandMacroResponse,
145        _: Entity<LspStore>,
146        _: Entity<Buffer>,
147        _: AsyncApp,
148    ) -> anyhow::Result<ExpandedMacro> {
149        Ok(ExpandedMacro {
150            name: message.name,
151            expansion: message.expansion,
152        })
153    }
154
155    fn buffer_id_from_proto(message: &proto::LspExtExpandMacro) -> Result<BufferId> {
156        BufferId::new(message.buffer_id)
157    }
158}
159
160pub enum LspOpenDocs {}
161
162impl lsp::request::Request for LspOpenDocs {
163    type Params = OpenDocsParams;
164    type Result = Option<DocsUrls>;
165    const METHOD: &'static str = "experimental/externalDocs";
166}
167
168#[derive(Serialize, Deserialize, Debug)]
169#[serde(rename_all = "camelCase")]
170pub struct OpenDocsParams {
171    pub text_document: lsp::TextDocumentIdentifier,
172    pub position: lsp::Position,
173}
174
175#[derive(Serialize, Deserialize, Debug, Default)]
176#[serde(rename_all = "camelCase")]
177pub struct DocsUrls {
178    pub web: Option<String>,
179    pub local: Option<String>,
180}
181
182impl DocsUrls {
183    pub fn is_empty(&self) -> bool {
184        self.web.is_none() && self.local.is_none()
185    }
186}
187
188#[derive(Debug)]
189pub struct OpenDocs {
190    pub position: PointUtf16,
191}
192
193#[async_trait(?Send)]
194impl LspCommand for OpenDocs {
195    type Response = DocsUrls;
196    type LspRequest = LspOpenDocs;
197    type ProtoRequest = proto::LspExtOpenDocs;
198
199    fn display_name(&self) -> &str {
200        "Open docs"
201    }
202
203    fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool {
204        true
205    }
206
207    fn to_lsp(
208        &self,
209        path: &Path,
210        _: &Buffer,
211        _: &Arc<LanguageServer>,
212        _: &App,
213    ) -> Result<OpenDocsParams> {
214        Ok(OpenDocsParams {
215            text_document: lsp::TextDocumentIdentifier {
216                uri: lsp::Uri::from_file_path(path).unwrap(),
217            },
218            position: point_to_lsp(self.position),
219        })
220    }
221
222    async fn response_from_lsp(
223        self,
224        message: Option<DocsUrls>,
225        _: Entity<LspStore>,
226        _: Entity<Buffer>,
227        _: LanguageServerId,
228        _: AsyncApp,
229    ) -> anyhow::Result<DocsUrls> {
230        Ok(message
231            .map(|message| DocsUrls {
232                web: message.web,
233                local: message.local,
234            })
235            .unwrap_or_default())
236    }
237
238    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtOpenDocs {
239        proto::LspExtOpenDocs {
240            project_id,
241            buffer_id: buffer.remote_id().into(),
242            position: Some(language::proto::serialize_anchor(
243                &buffer.anchor_before(self.position),
244            )),
245        }
246    }
247
248    async fn from_proto(
249        message: Self::ProtoRequest,
250        _: Entity<LspStore>,
251        buffer: Entity<Buffer>,
252        cx: AsyncApp,
253    ) -> anyhow::Result<Self> {
254        let position = message
255            .position
256            .and_then(deserialize_anchor)
257            .context("invalid position")?;
258        Ok(Self {
259            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?,
260        })
261    }
262
263    fn response_to_proto(
264        response: DocsUrls,
265        _: &mut LspStore,
266        _: PeerId,
267        _: &clock::Global,
268        _: &mut App,
269    ) -> proto::LspExtOpenDocsResponse {
270        proto::LspExtOpenDocsResponse {
271            web: response.web,
272            local: response.local,
273        }
274    }
275
276    async fn response_from_proto(
277        self,
278        message: proto::LspExtOpenDocsResponse,
279        _: Entity<LspStore>,
280        _: Entity<Buffer>,
281        _: AsyncApp,
282    ) -> anyhow::Result<DocsUrls> {
283        Ok(DocsUrls {
284            web: message.web,
285            local: message.local,
286        })
287    }
288
289    fn buffer_id_from_proto(message: &proto::LspExtOpenDocs) -> Result<BufferId> {
290        BufferId::new(message.buffer_id)
291    }
292}
293
294pub enum LspSwitchSourceHeader {}
295
296impl lsp::request::Request for LspSwitchSourceHeader {
297    type Params = SwitchSourceHeaderParams;
298    type Result = Option<SwitchSourceHeaderResult>;
299    const METHOD: &'static str = "textDocument/switchSourceHeader";
300}
301
302#[derive(Serialize, Deserialize, Debug)]
303#[serde(rename_all = "camelCase")]
304pub struct SwitchSourceHeaderParams(lsp::TextDocumentIdentifier);
305
306#[derive(Serialize, Deserialize, Debug, Default)]
307#[serde(rename_all = "camelCase")]
308pub struct SwitchSourceHeaderResult(pub String);
309
310#[derive(Default, Deserialize, Serialize, Debug)]
311#[serde(rename_all = "camelCase")]
312pub struct SwitchSourceHeader;
313
314#[derive(Debug)]
315pub struct GoToParentModule {
316    pub position: PointUtf16,
317}
318
319pub struct LspGoToParentModule {}
320
321impl lsp::request::Request for LspGoToParentModule {
322    type Params = lsp::TextDocumentPositionParams;
323    type Result = Option<Vec<lsp::LocationLink>>;
324    const METHOD: &'static str = "experimental/parentModule";
325}
326
327#[async_trait(?Send)]
328impl LspCommand for SwitchSourceHeader {
329    type Response = SwitchSourceHeaderResult;
330    type LspRequest = LspSwitchSourceHeader;
331    type ProtoRequest = proto::LspExtSwitchSourceHeader;
332
333    fn display_name(&self) -> &str {
334        "Switch source header"
335    }
336
337    fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool {
338        true
339    }
340
341    fn to_lsp(
342        &self,
343        path: &Path,
344        _: &Buffer,
345        _: &Arc<LanguageServer>,
346        _: &App,
347    ) -> Result<SwitchSourceHeaderParams> {
348        Ok(SwitchSourceHeaderParams(make_text_document_identifier(
349            path,
350        )?))
351    }
352
353    async fn response_from_lsp(
354        self,
355        message: Option<SwitchSourceHeaderResult>,
356        _: Entity<LspStore>,
357        _: Entity<Buffer>,
358        _: LanguageServerId,
359        _: AsyncApp,
360    ) -> anyhow::Result<SwitchSourceHeaderResult> {
361        Ok(message
362            .map(|message| SwitchSourceHeaderResult(message.0))
363            .unwrap_or_default())
364    }
365
366    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtSwitchSourceHeader {
367        proto::LspExtSwitchSourceHeader {
368            project_id,
369            buffer_id: buffer.remote_id().into(),
370        }
371    }
372
373    async fn from_proto(
374        _: Self::ProtoRequest,
375        _: Entity<LspStore>,
376        _: Entity<Buffer>,
377        _: AsyncApp,
378    ) -> anyhow::Result<Self> {
379        Ok(Self {})
380    }
381
382    fn response_to_proto(
383        response: SwitchSourceHeaderResult,
384        _: &mut LspStore,
385        _: PeerId,
386        _: &clock::Global,
387        _: &mut App,
388    ) -> proto::LspExtSwitchSourceHeaderResponse {
389        proto::LspExtSwitchSourceHeaderResponse {
390            target_file: response.0,
391        }
392    }
393
394    async fn response_from_proto(
395        self,
396        message: proto::LspExtSwitchSourceHeaderResponse,
397        _: Entity<LspStore>,
398        _: Entity<Buffer>,
399        _: AsyncApp,
400    ) -> anyhow::Result<SwitchSourceHeaderResult> {
401        Ok(SwitchSourceHeaderResult(message.target_file))
402    }
403
404    fn buffer_id_from_proto(message: &proto::LspExtSwitchSourceHeader) -> Result<BufferId> {
405        BufferId::new(message.buffer_id)
406    }
407}
408
409#[async_trait(?Send)]
410impl LspCommand for GoToParentModule {
411    type Response = Vec<LocationLink>;
412    type LspRequest = LspGoToParentModule;
413    type ProtoRequest = proto::LspExtGoToParentModule;
414
415    fn display_name(&self) -> &str {
416        "Go to parent module"
417    }
418
419    fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool {
420        true
421    }
422
423    fn to_lsp(
424        &self,
425        path: &Path,
426        _: &Buffer,
427        _: &Arc<LanguageServer>,
428        _: &App,
429    ) -> Result<lsp::TextDocumentPositionParams> {
430        make_lsp_text_document_position(path, self.position)
431    }
432
433    async fn response_from_lsp(
434        self,
435        links: Option<Vec<lsp::LocationLink>>,
436        lsp_store: Entity<LspStore>,
437        buffer: Entity<Buffer>,
438        server_id: LanguageServerId,
439        cx: AsyncApp,
440    ) -> anyhow::Result<Vec<LocationLink>> {
441        location_links_from_lsp(
442            links.map(lsp::GotoDefinitionResponse::Link),
443            lsp_store,
444            buffer,
445            server_id,
446            cx,
447        )
448        .await
449    }
450
451    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtGoToParentModule {
452        proto::LspExtGoToParentModule {
453            project_id,
454            buffer_id: buffer.remote_id().to_proto(),
455            position: Some(language::proto::serialize_anchor(
456                &buffer.anchor_before(self.position),
457            )),
458        }
459    }
460
461    async fn from_proto(
462        request: Self::ProtoRequest,
463        _: Entity<LspStore>,
464        buffer: Entity<Buffer>,
465        cx: AsyncApp,
466    ) -> anyhow::Result<Self> {
467        let position = request
468            .position
469            .and_then(deserialize_anchor)
470            .context("bad request with bad position")?;
471        Ok(Self {
472            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?,
473        })
474    }
475
476    fn response_to_proto(
477        links: Vec<LocationLink>,
478        lsp_store: &mut LspStore,
479        peer_id: PeerId,
480        _: &clock::Global,
481        cx: &mut App,
482    ) -> proto::LspExtGoToParentModuleResponse {
483        proto::LspExtGoToParentModuleResponse {
484            links: location_links_to_proto(links, lsp_store, peer_id, cx),
485        }
486    }
487
488    async fn response_from_proto(
489        self,
490        message: proto::LspExtGoToParentModuleResponse,
491        lsp_store: Entity<LspStore>,
492        _: Entity<Buffer>,
493        cx: AsyncApp,
494    ) -> anyhow::Result<Vec<LocationLink>> {
495        location_links_from_proto(message.links, lsp_store, cx).await
496    }
497
498    fn buffer_id_from_proto(message: &proto::LspExtGoToParentModule) -> Result<BufferId> {
499        BufferId::new(message.buffer_id)
500    }
501}
502
503// https://rust-analyzer.github.io/book/contributing/lsp-extensions.html#runnables
504// Taken from https://github.com/rust-lang/rust-analyzer/blob/a73a37a757a58b43a796d3eb86a1f7dfd0036659/crates/rust-analyzer/src/lsp/ext.rs#L425-L489
505pub enum Runnables {}
506
507impl lsp::request::Request for Runnables {
508    type Params = RunnablesParams;
509    type Result = Vec<Runnable>;
510    const METHOD: &'static str = "experimental/runnables";
511}
512
513#[derive(Serialize, Deserialize, Debug, Clone)]
514#[serde(rename_all = "camelCase")]
515pub struct RunnablesParams {
516    pub text_document: lsp::TextDocumentIdentifier,
517    #[serde(default)]
518    pub position: Option<lsp::Position>,
519}
520
521#[derive(Deserialize, Serialize, Debug, Clone)]
522#[serde(rename_all = "camelCase")]
523pub struct Runnable {
524    pub label: String,
525    #[serde(default, skip_serializing_if = "Option::is_none")]
526    pub location: Option<lsp::LocationLink>,
527    pub kind: RunnableKind,
528    pub args: RunnableArgs,
529}
530
531#[derive(Deserialize, Serialize, Debug, Clone)]
532#[serde(rename_all = "camelCase")]
533#[serde(untagged)]
534pub enum RunnableArgs {
535    Cargo(CargoRunnableArgs),
536    Shell(ShellRunnableArgs),
537}
538
539#[derive(Serialize, Deserialize, Debug, Clone)]
540#[serde(rename_all = "lowercase")]
541pub enum RunnableKind {
542    Cargo,
543    Shell,
544}
545
546#[derive(Deserialize, Serialize, Debug, Clone)]
547#[serde(rename_all = "camelCase")]
548pub struct CargoRunnableArgs {
549    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
550    pub environment: HashMap<String, String>,
551    pub cwd: PathBuf,
552    /// Command to be executed instead of cargo
553    #[serde(default)]
554    pub override_cargo: Option<String>,
555    #[serde(default, skip_serializing_if = "Option::is_none")]
556    pub workspace_root: Option<PathBuf>,
557    // command, --package and --lib stuff
558    #[serde(default)]
559    pub cargo_args: Vec<String>,
560    // stuff after --
561    #[serde(default)]
562    pub executable_args: Vec<String>,
563}
564
565#[derive(Deserialize, Serialize, Debug, Clone)]
566#[serde(rename_all = "camelCase")]
567pub struct ShellRunnableArgs {
568    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
569    pub environment: HashMap<String, String>,
570    pub cwd: PathBuf,
571    pub program: String,
572    #[serde(default)]
573    pub args: Vec<String>,
574}
575
576#[derive(Debug)]
577pub struct GetLspRunnables {
578    pub buffer_id: BufferId,
579    pub position: Option<text::Anchor>,
580}
581
582#[derive(Debug, Default)]
583pub struct LspRunnables {
584    pub runnables: Vec<(Option<LocationLink>, TaskTemplate)>,
585}
586
587#[async_trait(?Send)]
588impl LspCommand for GetLspRunnables {
589    type Response = LspRunnables;
590    type LspRequest = Runnables;
591    type ProtoRequest = proto::LspExtRunnables;
592
593    fn display_name(&self) -> &str {
594        "LSP Runnables"
595    }
596
597    fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool {
598        true
599    }
600
601    fn to_lsp(
602        &self,
603        path: &Path,
604        buffer: &Buffer,
605        _: &Arc<LanguageServer>,
606        _: &App,
607    ) -> Result<RunnablesParams> {
608        let url = file_path_to_lsp_url(path)?;
609        Ok(RunnablesParams {
610            text_document: lsp::TextDocumentIdentifier::new(url),
611            position: self
612                .position
613                .map(|anchor| point_to_lsp(anchor.to_point_utf16(&buffer.snapshot()))),
614        })
615    }
616
617    async fn response_from_lsp(
618        self,
619        lsp_runnables: Vec<Runnable>,
620        lsp_store: Entity<LspStore>,
621        buffer: Entity<Buffer>,
622        server_id: LanguageServerId,
623        mut cx: AsyncApp,
624    ) -> Result<LspRunnables> {
625        let mut runnables = Vec::with_capacity(lsp_runnables.len());
626
627        for runnable in lsp_runnables {
628            let location = match runnable.location {
629                Some(location) => Some(
630                    location_link_from_lsp(location, &lsp_store, &buffer, server_id, &mut cx)
631                        .await?,
632                ),
633                None => None,
634            };
635            let mut task_template = TaskTemplate::default();
636            task_template.label = runnable.label;
637            match runnable.args {
638                RunnableArgs::Cargo(cargo) => {
639                    match cargo.override_cargo {
640                        Some(override_cargo) => {
641                            let mut override_parts =
642                                override_cargo.split(" ").map(|s| s.to_string());
643                            task_template.command = override_parts
644                                .next()
645                                .unwrap_or_else(|| override_cargo.clone());
646                            task_template.args.extend(override_parts);
647                        }
648                        None => task_template.command = "cargo".to_string(),
649                    };
650                    task_template.env = cargo.environment;
651                    task_template.cwd = Some(
652                        cargo
653                            .workspace_root
654                            .unwrap_or(cargo.cwd)
655                            .to_string_lossy()
656                            .to_string(),
657                    );
658                    task_template.args.extend(cargo.cargo_args);
659                    if !cargo.executable_args.is_empty() {
660                        let shell_kind = task_template
661                            .shell
662                            .as_ref()
663                            .map_or_else(ShellKind::system, |shell| {
664                                shell.shell_kind(cfg!(windows))
665                            });
666                        task_template.args.push("--".to_string());
667                        task_template.args.extend(
668                            cargo
669                                .executable_args
670                                .into_iter()
671                                // rust-analyzer's doctest data may be smth. like
672                                // ```
673                                // command: "cargo",
674                                // args: [
675                                //     "test",
676                                //     "--doc",
677                                //     "--package",
678                                //     "cargo-output-parser",
679                                //     "--",
680                                //     "X<T>::new",
681                                //     "--show-output",
682                                // ],
683                                // ```
684                                // and `X<T>::new` will cause troubles if not escaped properly, as later
685                                // the task runs as `$SHELL -i -c "cargo test ..."`.
686                                //
687                                // We cannot escape all shell arguments unconditionally, as we use this for ssh commands, which may involve paths starting with `~`.
688                                // That bit is not auto-expanded when using single quotes.
689                                // Escape extra cargo args unconditionally as those are unlikely to contain `~`.
690                                .flat_map(|extra_arg| {
691                                    shell_kind.try_quote(&extra_arg).map(|s| s.to_string())
692                                }),
693                        );
694                    }
695                }
696                RunnableArgs::Shell(shell) => {
697                    task_template.command = shell.program;
698                    task_template.args = shell.args;
699                    task_template.env = shell.environment;
700                    task_template.cwd = Some(shell.cwd.to_string_lossy().into_owned());
701                }
702            }
703
704            runnables.push((location, task_template));
705        }
706
707        Ok(LspRunnables { runnables })
708    }
709
710    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtRunnables {
711        proto::LspExtRunnables {
712            project_id,
713            buffer_id: buffer.remote_id().to_proto(),
714            position: self.position.as_ref().map(serialize_anchor),
715        }
716    }
717
718    async fn from_proto(
719        message: proto::LspExtRunnables,
720        _: Entity<LspStore>,
721        _: Entity<Buffer>,
722        _: AsyncApp,
723    ) -> Result<Self> {
724        let buffer_id = Self::buffer_id_from_proto(&message)?;
725        let position = message.position.and_then(deserialize_anchor);
726        Ok(Self {
727            buffer_id,
728            position,
729        })
730    }
731
732    fn response_to_proto(
733        response: LspRunnables,
734        lsp_store: &mut LspStore,
735        peer_id: PeerId,
736        _: &clock::Global,
737        cx: &mut App,
738    ) -> proto::LspExtRunnablesResponse {
739        proto::LspExtRunnablesResponse {
740            runnables: response
741                .runnables
742                .into_iter()
743                .map(|(location, task_template)| proto::LspRunnable {
744                    location: location
745                        .map(|location| location_link_to_proto(location, lsp_store, peer_id, cx)),
746                    task_template: serde_json::to_vec(&task_template).unwrap(),
747                })
748                .collect(),
749        }
750    }
751
752    async fn response_from_proto(
753        self,
754        message: proto::LspExtRunnablesResponse,
755        lsp_store: Entity<LspStore>,
756        _: Entity<Buffer>,
757        mut cx: AsyncApp,
758    ) -> Result<LspRunnables> {
759        let mut runnables = LspRunnables {
760            runnables: Vec::new(),
761        };
762
763        for lsp_runnable in message.runnables {
764            let location = match lsp_runnable.location {
765                Some(location) => {
766                    Some(location_link_from_proto(location, lsp_store.clone(), &mut cx).await?)
767                }
768                None => None,
769            };
770            let task_template = serde_json::from_slice(&lsp_runnable.task_template)
771                .context("deserializing task template from proto")?;
772            runnables.runnables.push((location, task_template));
773        }
774
775        Ok(runnables)
776    }
777
778    fn buffer_id_from_proto(message: &proto::LspExtRunnables) -> Result<BufferId> {
779        BufferId::new(message.buffer_id)
780    }
781}
782
783#[derive(Debug)]
784pub struct LspExtCancelFlycheck {}
785
786#[derive(Debug)]
787pub struct LspExtRunFlycheck {}
788
789#[derive(Debug)]
790pub struct LspExtClearFlycheck {}
791
792impl lsp::notification::Notification for LspExtCancelFlycheck {
793    type Params = ();
794    const METHOD: &'static str = "rust-analyzer/cancelFlycheck";
795}
796
797impl lsp::notification::Notification for LspExtRunFlycheck {
798    type Params = RunFlycheckParams;
799    const METHOD: &'static str = "rust-analyzer/runFlycheck";
800}
801
802#[derive(Deserialize, Serialize, Debug)]
803#[serde(rename_all = "camelCase")]
804pub struct RunFlycheckParams {
805    pub text_document: Option<lsp::TextDocumentIdentifier>,
806}
807
808impl lsp::notification::Notification for LspExtClearFlycheck {
809    type Params = ();
810    const METHOD: &'static str = "rust-analyzer/clearFlycheck";
811}