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