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 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.read_with(cx, |project, cx| {
101 project.language_server_id_for_name(buffer.read(cx), &RUST_ANALYZER_NAME, cx)
102 })?
103 else {
104 return Ok(());
105 };
106 let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id().to_proto())?;
107
108 if let Some((client, project_id)) = upstream_client {
109 let request = proto::LspExtCancelFlycheck {
110 project_id,
111 buffer_id,
112 language_server_id: rust_analyzer_server.to_proto(),
113 };
114 client
115 .request(request)
116 .await
117 .context("lsp ext cancel flycheck proto request")?;
118 } else {
119 lsp_store
120 .read_with(cx, |lsp_store, _| {
121 if let Some(server) = lsp_store.language_server_for_id(rust_analyzer_server) {
122 server.notify::<lsp_store::lsp_ext_command::LspExtCancelFlycheck>(&())?;
123 }
124 anyhow::Ok(())
125 })?
126 .context("lsp ext cancel flycheck")?;
127 };
128 anyhow::Ok(())
129 })
130}
131
132pub fn run_flycheck(
133 project: Entity<Project>,
134 buffer_path: ProjectPath,
135 cx: &mut App,
136) -> Task<anyhow::Result<()>> {
137 let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client();
138 let lsp_store = project.read(cx).lsp_store();
139 let buffer = project.update(cx, |project, cx| {
140 project.buffer_store().update(cx, |buffer_store, cx| {
141 buffer_store.open_buffer(buffer_path, cx)
142 })
143 });
144
145 cx.spawn(async move |cx| {
146 let buffer = buffer.await?;
147 let Some(rust_analyzer_server) = project.read_with(cx, |project, cx| {
148 project.language_server_id_for_name(buffer.read(cx), &RUST_ANALYZER_NAME, cx)
149 })?
150 else {
151 return Ok(());
152 };
153 let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id().to_proto())?;
154
155 if let Some((client, project_id)) = upstream_client {
156 let request = proto::LspExtRunFlycheck {
157 project_id,
158 buffer_id,
159 language_server_id: rust_analyzer_server.to_proto(),
160 current_file_only: false,
161 };
162 client
163 .request(request)
164 .await
165 .context("lsp ext run flycheck proto request")?;
166 } else {
167 lsp_store
168 .read_with(cx, |lsp_store, _| {
169 if let Some(server) = lsp_store.language_server_for_id(rust_analyzer_server) {
170 server.notify::<lsp_store::lsp_ext_command::LspExtRunFlycheck>(
171 &lsp_store::lsp_ext_command::RunFlycheckParams {
172 text_document: None,
173 },
174 )?;
175 }
176 anyhow::Ok(())
177 })?
178 .context("lsp ext run flycheck")?;
179 };
180 anyhow::Ok(())
181 })
182}
183
184pub fn clear_flycheck(
185 project: Entity<Project>,
186 buffer_path: ProjectPath,
187 cx: &mut App,
188) -> Task<anyhow::Result<()>> {
189 let upstream_client = project.read(cx).lsp_store().read(cx).upstream_client();
190 let lsp_store = project.read(cx).lsp_store();
191 let buffer = project.update(cx, |project, cx| {
192 project.buffer_store().update(cx, |buffer_store, cx| {
193 buffer_store.open_buffer(buffer_path, cx)
194 })
195 });
196
197 cx.spawn(async move |cx| {
198 let buffer = buffer.await?;
199 let Some(rust_analyzer_server) = project.read_with(cx, |project, cx| {
200 project.language_server_id_for_name(buffer.read(cx), &RUST_ANALYZER_NAME, cx)
201 })?
202 else {
203 return Ok(());
204 };
205 let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id().to_proto())?;
206
207 if let Some((client, project_id)) = upstream_client {
208 let request = proto::LspExtClearFlycheck {
209 project_id,
210 buffer_id,
211 language_server_id: rust_analyzer_server.to_proto(),
212 };
213 client
214 .request(request)
215 .await
216 .context("lsp ext clear flycheck proto request")?;
217 } else {
218 lsp_store
219 .read_with(cx, |lsp_store, _| {
220 if let Some(server) = lsp_store.language_server_for_id(rust_analyzer_server) {
221 server.notify::<lsp_store::lsp_ext_command::LspExtClearFlycheck>(&())?;
222 }
223 anyhow::Ok(())
224 })?
225 .context("lsp ext clear flycheck")?;
226 };
227 anyhow::Ok(())
228 })
229}