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