rust_analyzer_ext.rs

  1use ::serde::{Deserialize, Serialize};
  2use anyhow::Context as _;
  3use gpui::{App, Entity, Task, WeakEntity};
  4use language::ServerHealth;
  5use lsp::{LanguageServer, LanguageServerName};
  6use rpc::proto;
  7
  8use crate::{LspStore, LspStoreEvent, Project, ProjectPath, lsp_store};
  9
 10pub const RUST_ANALYZER_NAME: LanguageServerName = LanguageServerName::new_static("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            move |params, cx| {
 38                let message = params.message;
 39                let log_message = message.as_ref().map(|message| {
 40                    format!("Language server {name} (id {server_id}) status update: {message}")
 41                });
 42                let status = match &params.health {
 43                    ServerHealth::Ok => {
 44                        if let Some(log_message) = log_message {
 45                            log::info!("{log_message}");
 46                        }
 47                        proto::ServerHealth::Ok
 48                    }
 49                    ServerHealth::Warning => {
 50                        if let Some(log_message) = log_message {
 51                            log::warn!("{log_message}");
 52                        }
 53                        proto::ServerHealth::Warning
 54                    }
 55                    ServerHealth::Error => {
 56                        if let Some(log_message) = log_message {
 57                            log::error!("{log_message}");
 58                        }
 59                        proto::ServerHealth::Error
 60                    }
 61                };
 62
 63                lsp_store
 64                    .update(cx, |_, cx| {
 65                        cx.emit(LspStoreEvent::LanguageServerUpdate {
 66                            language_server_id: server_id,
 67                            name: Some(name.clone()),
 68                            message: proto::update_language_server::Variant::StatusUpdate(
 69                                proto::StatusUpdate {
 70                                    message,
 71                                    status: Some(proto::status_update::Status::Health(
 72                                        status as i32,
 73                                    )),
 74                                },
 75                            ),
 76                        });
 77                    })
 78                    .ok();
 79            }
 80        })
 81        .detach();
 82}
 83
 84pub fn cancel_flycheck(
 85    project: Entity<Project>,
 86    buffer_path: ProjectPath,
 87    cx: &mut App,
 88) -> Task<anyhow::Result<()>> {
 89    let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client();
 90    let lsp_store = project.read(cx).lsp_store();
 91    let buffer = project.update(cx, |project, cx| {
 92        project.buffer_store().update(cx, |buffer_store, cx| {
 93            buffer_store.open_buffer(buffer_path, cx)
 94        })
 95    });
 96
 97    cx.spawn(async move |cx| {
 98        let buffer = buffer.await?;
 99        let Some(rust_analyzer_server) = project.read_with(cx, |project, cx| {
100            project.language_server_id_for_name(buffer.read(cx), &RUST_ANALYZER_NAME, cx)
101        })?
102        else {
103            return Ok(());
104        };
105        let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id().to_proto())?;
106
107        if let Some((client, project_id)) = upstream_client {
108            let request = proto::LspExtCancelFlycheck {
109                project_id,
110                buffer_id,
111                language_server_id: rust_analyzer_server.to_proto(),
112            };
113            client
114                .request(request)
115                .await
116                .context("lsp ext cancel flycheck proto request")?;
117        } else {
118            lsp_store
119                .read_with(cx, |lsp_store, _| {
120                    if let Some(server) = lsp_store.language_server_for_id(rust_analyzer_server) {
121                        server.notify::<lsp_store::lsp_ext_command::LspExtCancelFlycheck>(&())?;
122                    }
123                    anyhow::Ok(())
124                })?
125                .context("lsp ext cancel flycheck")?;
126        };
127        anyhow::Ok(())
128    })
129}
130
131pub fn run_flycheck(
132    project: Entity<Project>,
133    buffer_path: ProjectPath,
134    cx: &mut App,
135) -> Task<anyhow::Result<()>> {
136    let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client();
137    let lsp_store = project.read(cx).lsp_store();
138    let buffer = project.update(cx, |project, cx| {
139        project.buffer_store().update(cx, |buffer_store, cx| {
140            buffer_store.open_buffer(buffer_path, cx)
141        })
142    });
143
144    cx.spawn(async move |cx| {
145        let buffer = buffer.await?;
146        let Some(rust_analyzer_server) = project.read_with(cx, |project, cx| {
147            project.language_server_id_for_name(buffer.read(cx), &RUST_ANALYZER_NAME, cx)
148        })?
149        else {
150            return Ok(());
151        };
152        let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id().to_proto())?;
153
154        if let Some((client, project_id)) = upstream_client {
155            let request = proto::LspExtRunFlycheck {
156                project_id,
157                buffer_id,
158                language_server_id: rust_analyzer_server.to_proto(),
159                current_file_only: false,
160            };
161            client
162                .request(request)
163                .await
164                .context("lsp ext run flycheck proto request")?;
165        } else {
166            lsp_store
167                .read_with(cx, |lsp_store, _| {
168                    if let Some(server) = lsp_store.language_server_for_id(rust_analyzer_server) {
169                        server.notify::<lsp_store::lsp_ext_command::LspExtRunFlycheck>(
170                            &lsp_store::lsp_ext_command::RunFlycheckParams {
171                                text_document: None,
172                            },
173                        )?;
174                    }
175                    anyhow::Ok(())
176                })?
177                .context("lsp ext run flycheck")?;
178        };
179        anyhow::Ok(())
180    })
181}
182
183pub fn clear_flycheck(
184    project: Entity<Project>,
185    buffer_path: ProjectPath,
186    cx: &mut App,
187) -> Task<anyhow::Result<()>> {
188    let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client();
189    let lsp_store = project.read(cx).lsp_store();
190    let buffer = project.update(cx, |project, cx| {
191        project.buffer_store().update(cx, |buffer_store, cx| {
192            buffer_store.open_buffer(buffer_path, cx)
193        })
194    });
195
196    cx.spawn(async move |cx| {
197        let buffer = buffer.await?;
198        let Some(rust_analyzer_server) = project.read_with(cx, |project, cx| {
199            project.language_server_id_for_name(buffer.read(cx), &RUST_ANALYZER_NAME, cx)
200        })?
201        else {
202            return Ok(());
203        };
204        let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id().to_proto())?;
205
206        if let Some((client, project_id)) = upstream_client {
207            let request = proto::LspExtClearFlycheck {
208                project_id,
209                buffer_id,
210                language_server_id: rust_analyzer_server.to_proto(),
211            };
212            client
213                .request(request)
214                .await
215                .context("lsp ext clear flycheck proto request")?;
216        } else {
217            lsp_store
218                .read_with(cx, |lsp_store, _| {
219                    if let Some(server) = lsp_store.language_server_for_id(rust_analyzer_server) {
220                        server.notify::<lsp_store::lsp_ext_command::LspExtClearFlycheck>(&())?;
221                    }
222                    anyhow::Ok(())
223                })?
224                .context("lsp ext clear flycheck")?;
225        };
226        anyhow::Ok(())
227    })
228}