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