rust_analyzer_ext.rs

  1use ::serde::{Deserialize, Serialize};
  2use anyhow::Context as _;
  3use gpui::{App, Entity, SharedString, Task, WeakEntity};
  4use language::{LanguageServerStatusUpdate, ServerHealth};
  5use lsp::LanguageServer;
  6use rpc::proto;
  7
  8use crate::{LspStore, Project, ProjectPath, lsp_store};
  9
 10pub const RUST_ANALYZER_NAME: &str = "rust-analyzer";
 11pub const CARGO_DIAGNOSTICS_SOURCE_NAME: &str = "rustc";
 12
 13/// Experimental: Informs the end user about the state of the server
 14///
 15/// [Rust Analyzer Specification](https://rust-analyzer.github.io/book/contributing/lsp-extensions.html#server-status)
 16#[derive(Debug)]
 17enum ServerStatus {}
 18
 19#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
 20#[serde(rename_all = "camelCase")]
 21struct ServerStatusParams {
 22    pub health: ServerHealth,
 23    pub message: Option<String>,
 24}
 25
 26impl lsp::notification::Notification for ServerStatus {
 27    type Params = ServerStatusParams;
 28    const METHOD: &'static str = "experimental/serverStatus";
 29}
 30
 31pub fn register_notifications(lsp_store: WeakEntity<LspStore>, language_server: &LanguageServer) {
 32    let name = language_server.name();
 33    let server_id = language_server.server_id();
 34
 35    language_server
 36        .on_notification::<ServerStatus, _>({
 37            let name = name.clone();
 38            move |params, cx| {
 39                let status = params.message;
 40                let log_message =
 41                    format!("Language server {name} (id {server_id}) status update: {status:?}");
 42                match &params.health {
 43                    ServerHealth::Ok => log::info!("{log_message}"),
 44                    ServerHealth::Warning => log::warn!("{log_message}"),
 45                    ServerHealth::Error => log::error!("{log_message}"),
 46                }
 47
 48                lsp_store
 49                    .update(cx, |lsp_store, _| {
 50                        lsp_store.languages.update_lsp_status(
 51                            name.clone(),
 52                            LanguageServerStatusUpdate::Health(
 53                                params.health,
 54                                status.map(SharedString::from),
 55                            ),
 56                        );
 57                    })
 58                    .ok();
 59            }
 60        })
 61        .detach();
 62}
 63
 64pub fn cancel_flycheck(
 65    project: Entity<Project>,
 66    buffer_path: ProjectPath,
 67    cx: &mut App,
 68) -> Task<anyhow::Result<()>> {
 69    let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client();
 70    let lsp_store = project.read(cx).lsp_store();
 71    let buffer = project.update(cx, |project, cx| {
 72        project.buffer_store().update(cx, |buffer_store, cx| {
 73            buffer_store.open_buffer(buffer_path, cx)
 74        })
 75    });
 76
 77    cx.spawn(async move |cx| {
 78        let buffer = buffer.await?;
 79        let Some(rust_analyzer_server) = project
 80            .update(cx, |project, cx| {
 81                buffer.update(cx, |buffer, cx| {
 82                    project.language_server_id_for_name(buffer, RUST_ANALYZER_NAME, cx)
 83                })
 84            })?
 85            .await
 86        else {
 87            return Ok(());
 88        };
 89        let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id().to_proto())?;
 90
 91        if let Some((client, project_id)) = upstream_client {
 92            let request = proto::LspExtCancelFlycheck {
 93                project_id,
 94                buffer_id,
 95                language_server_id: rust_analyzer_server.to_proto(),
 96            };
 97            client
 98                .request(request)
 99                .await
100                .context("lsp ext cancel flycheck proto request")?;
101        } else {
102            lsp_store
103                .read_with(cx, |lsp_store, _| {
104                    if let Some(server) = lsp_store.language_server_for_id(rust_analyzer_server) {
105                        server.notify::<lsp_store::lsp_ext_command::LspExtCancelFlycheck>(&())?;
106                    }
107                    anyhow::Ok(())
108                })?
109                .context("lsp ext cancel flycheck")?;
110        };
111        anyhow::Ok(())
112    })
113}
114
115pub fn run_flycheck(
116    project: Entity<Project>,
117    buffer_path: ProjectPath,
118    cx: &mut App,
119) -> Task<anyhow::Result<()>> {
120    let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client();
121    let lsp_store = project.read(cx).lsp_store();
122    let buffer = project.update(cx, |project, cx| {
123        project.buffer_store().update(cx, |buffer_store, cx| {
124            buffer_store.open_buffer(buffer_path, cx)
125        })
126    });
127
128    cx.spawn(async move |cx| {
129        let buffer = buffer.await?;
130        let Some(rust_analyzer_server) = project
131            .update(cx, |project, cx| {
132                buffer.update(cx, |buffer, cx| {
133                    project.language_server_id_for_name(buffer, RUST_ANALYZER_NAME, cx)
134                })
135            })?
136            .await
137        else {
138            return Ok(());
139        };
140        let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id().to_proto())?;
141
142        if let Some((client, project_id)) = upstream_client {
143            let request = proto::LspExtRunFlycheck {
144                project_id,
145                buffer_id,
146                language_server_id: rust_analyzer_server.to_proto(),
147                current_file_only: false,
148            };
149            client
150                .request(request)
151                .await
152                .context("lsp ext run flycheck proto request")?;
153        } else {
154            lsp_store
155                .read_with(cx, |lsp_store, _| {
156                    if let Some(server) = lsp_store.language_server_for_id(rust_analyzer_server) {
157                        server.notify::<lsp_store::lsp_ext_command::LspExtRunFlycheck>(
158                            &lsp_store::lsp_ext_command::RunFlycheckParams {
159                                text_document: None,
160                            },
161                        )?;
162                    }
163                    anyhow::Ok(())
164                })?
165                .context("lsp ext run flycheck")?;
166        };
167        anyhow::Ok(())
168    })
169}
170
171pub fn clear_flycheck(
172    project: Entity<Project>,
173    buffer_path: ProjectPath,
174    cx: &mut App,
175) -> Task<anyhow::Result<()>> {
176    let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client();
177    let lsp_store = project.read(cx).lsp_store();
178    let buffer = project.update(cx, |project, cx| {
179        project.buffer_store().update(cx, |buffer_store, cx| {
180            buffer_store.open_buffer(buffer_path, cx)
181        })
182    });
183
184    cx.spawn(async move |cx| {
185        let buffer = buffer.await?;
186        let Some(rust_analyzer_server) = project
187            .update(cx, |project, cx| {
188                buffer.update(cx, |buffer, cx| {
189                    project.language_server_id_for_name(buffer, RUST_ANALYZER_NAME, cx)
190                })
191            })?
192            .await
193        else {
194            return Ok(());
195        };
196        let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id().to_proto())?;
197
198        if let Some((client, project_id)) = upstream_client {
199            let request = proto::LspExtClearFlycheck {
200                project_id,
201                buffer_id,
202                language_server_id: rust_analyzer_server.to_proto(),
203            };
204            client
205                .request(request)
206                .await
207                .context("lsp ext clear flycheck proto request")?;
208        } else {
209            lsp_store
210                .read_with(cx, |lsp_store, _| {
211                    if let Some(server) = lsp_store.language_server_for_id(rust_analyzer_server) {
212                        server.notify::<lsp_store::lsp_ext_command::LspExtClearFlycheck>(&())?;
213                    }
214                    anyhow::Ok(())
215                })?
216                .context("lsp ext clear flycheck")?;
217        };
218        anyhow::Ok(())
219    })
220}