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 ¶ms.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}