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