lsp_ext_command.rs

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