rust_analyzer_ext.rs

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