1use std::path::PathBuf;
18use std::process::ExitCode;
19use std::time::SystemTime;
20use std::{io, panic};
21
22use anyhow::Result;
23use cairo_lang_filesystem::ids::FileLongId;
24use crossbeam::channel::{self, Receiver, select_biased};
25use lsp_server::Message;
26use lsp_types::RegistrationParams;
27use lsp_types::request::SemanticTokensRefresh;
28use mimalloc::MiMalloc;
29use tracing::{debug, error, info};
30
31use crate::ide::analysis_progress::AnalysisStatus;
32use crate::ide::code_lens::CodeLensController;
33use crate::lang::lsp::LsProtoGroup;
34use crate::lang::proc_macros;
35use crate::lang::proc_macros::client::ServerStatus;
36use crate::lang::proc_macros::controller::ProcMacroChannels;
37use crate::lang::proc_macros::db::ProcMacroGroup;
38use crate::lsp::capabilities::client::ClientCapabilitiesExt;
39use crate::lsp::capabilities::server::{
40 collect_dynamic_registrations, collect_server_capabilities,
41};
42use crate::lsp::result::LSPResult;
43use crate::project::{ProjectController, ProjectUpdate};
44use crate::server::client::{Notifier, Requester, Responder};
45use crate::server::connection::{Connection, ConnectionInitializer};
46use crate::server::panic::is_cancelled;
47use crate::server::schedule::thread::JoinHandle;
48use crate::server::schedule::{Scheduler, Task, event_loop_thread};
49use crate::state::{MetaState, State};
50
51mod config;
52mod env_config;
53mod ide;
54mod lang;
55pub mod lsp;
56mod project;
57mod server;
58mod state;
59#[cfg(feature = "testing")]
60pub mod testing;
61mod toolchain;
62
63#[global_allocator]
64static GLOBAL: MiMalloc = MiMalloc;
65
66pub fn start() -> ExitCode {
72 let _log_guard = init_logging();
73 set_panic_hook();
74
75 info!("language server starting");
76 env_config::report_to_logs();
77
78 let exit_code = match Backend::new() {
79 Ok(backend) => {
80 if let Err(err) = backend.run().map(|handle| handle.join()) {
81 error!("language server encountered an unrecoverable error: {err}");
82 ExitCode::from(1)
83 } else {
84 ExitCode::from(0)
85 }
86 }
87 Err(err) => {
88 error!("language server failed during initialization: {err}");
89 ExitCode::from(1)
90 }
91 };
92
93 info!("language server stopped");
94 exit_code
95}
96
97fn init_logging() -> Option<impl Drop> {
101 use std::fs;
102 use std::io::IsTerminal;
103
104 use tracing_chrome::ChromeLayerBuilder;
105 use tracing_subscriber::filter::{EnvFilter, LevelFilter, Targets};
106 use tracing_subscriber::fmt::Layer;
107 use tracing_subscriber::fmt::time::Uptime;
108 use tracing_subscriber::prelude::*;
109
110 let mut guard = None;
111
112 let fmt_layer = Layer::new()
113 .with_writer(io::stderr)
114 .with_timer(Uptime::default())
115 .with_ansi(io::stderr().is_terminal())
116 .with_filter(
117 EnvFilter::builder()
118 .with_default_directive(LevelFilter::WARN.into())
119 .with_env_var(env_config::CAIRO_LS_LOG)
120 .from_env_lossy(),
121 );
122
123 let profile_layer = if env_config::tracing_profile() {
124 let mut path = PathBuf::from(format!(
125 "./cairols-profile-{}.json",
126 SystemTime::UNIX_EPOCH.elapsed().unwrap().as_micros()
127 ));
128
129 let profile_file = fs::File::create(&path).expect("Failed to create profile file.");
131
132 if let Ok(canonical) = fs::canonicalize(&path) {
134 path = canonical;
135 }
136
137 eprintln!("this LS run will output tracing profile to: {}", path.display());
138 eprintln!(
139 "open that file with https://ui.perfetto.dev (or chrome://tracing) to analyze it"
140 );
141
142 let (profile_layer, profile_layer_guard) =
143 ChromeLayerBuilder::new().writer(profile_file).include_args(true).build();
144
145 let profile_layer = profile_layer.with_filter(
148 Targets::new().with_default(LevelFilter::TRACE).with_target("salsa", LevelFilter::WARN),
149 );
150
151 guard = Some(profile_layer_guard);
152 Some(profile_layer)
153 } else {
154 None
155 };
156
157 tracing::subscriber::set_global_default(
158 tracing_subscriber::registry().with(fmt_layer).with(profile_layer),
159 )
160 .expect("Could not set up global logger.");
161
162 guard
163}
164
165fn set_panic_hook() {
167 let previous_hook = panic::take_hook();
168 panic::set_hook(Box::new(move |info| {
169 if !is_cancelled(info.payload()) {
170 previous_hook(info);
171 }
172 }))
173}
174
175struct Backend {
176 connection: Connection,
177 state: State,
178}
179
180impl Backend {
181 fn new() -> Result<Self> {
182 let connection_initializer = ConnectionInitializer::stdio();
183
184 Self::initialize(connection_initializer, std::env::current_dir()?)
185 }
186
187 fn initialize(connection_initializer: ConnectionInitializer, cwd: PathBuf) -> Result<Self> {
191 let (id, init_params) = connection_initializer.initialize_start()?;
192
193 let client_capabilities = init_params.capabilities;
194 let server_capabilities = collect_server_capabilities(&client_capabilities);
195
196 let connection = connection_initializer.initialize_finish(id, server_capabilities)?;
197 let state = State::new(connection.make_sender(), client_capabilities, cwd);
198
199 Ok(Self { connection, state })
200 }
201
202 fn run(self) -> Result<JoinHandle<Result<()>>> {
204 event_loop_thread(move || {
205 let Self { mut state, connection } = self;
206 let proc_macro_channels = state.proc_macro_controller.channels();
207 let project_updates_receiver = state.project_controller.response_receiver();
208 let analysis_progress_receiver =
209 state.analysis_progress_controller.get_status_receiver();
210 let code_lens_request_refresh_receiver =
211 state.code_lens_controller.request_refresh_receiver();
212
213 let mut scheduler = Scheduler::new(&mut state, connection.make_sender());
214
215 Self::dispatch_setup_tasks(&mut scheduler);
216
217 scheduler.on_sync_mut_task(Self::register_mutation_in_swapper);
219
220 scheduler.on_sync_mut_task(Self::maybe_swap_database);
224
225 scheduler.on_sync_mut_task(Self::refresh_diagnostics);
229
230 scheduler.on_sync_mut_task(|state, _, _| {
232 state.analysis_progress_controller.mutation();
233 });
234
235 let result = Self::event_loop(
236 &connection,
237 proc_macro_channels,
238 project_updates_receiver,
239 analysis_progress_receiver,
240 code_lens_request_refresh_receiver,
241 scheduler,
242 );
243
244 state.db.cancel_all();
245
246 if let Err(err) = connection.close() {
247 error!("failed to close connection to the language server: {err:?}");
248 }
249
250 result
251 })
252 }
253
254 fn dispatch_setup_tasks(scheduler: &mut Scheduler<'_>) {
256 scheduler.local_mut(Self::register_dynamic_capabilities);
257
258 scheduler.local_mut(|state, _notifier, requester, _responder| {
259 let _ = state.config.reload_on_start(
260 requester,
261 &mut state.db,
262 &mut state.proc_macro_controller,
263 &mut state.analysis_progress_controller,
264 &state.client_capabilities,
265 );
266 });
267 }
268
269 fn register_dynamic_capabilities(
270 state: &mut State,
271 _notifier: Notifier,
272 requester: &mut Requester<'_>,
273 _responder: Responder,
274 ) {
275 let registrations = collect_dynamic_registrations(&state.client_capabilities);
276
277 let _ = requester
278 .request::<lsp_types::request::RegisterCapability>(
279 RegistrationParams { registrations },
280 |()| {
281 debug!("capabilities successfully registered dynamically");
282 Task::nothing()
283 },
284 )
285 .inspect_err(|e| {
286 error!(
287 "failed to register dynamic capabilities, some features may not work \
288 properly: {e:?}"
289 )
290 });
291 }
292
293 fn event_loop(
300 connection: &Connection,
301 proc_macro_channels: ProcMacroChannels,
302 project_updates_receiver: Receiver<ProjectUpdate>,
303 analysis_progress_status_receiver: Receiver<AnalysisStatus>,
304 code_lens_request_refresh_receiver: Receiver<()>,
305 mut scheduler: Scheduler<'_>,
306 ) -> Result<()> {
307 let incoming = connection.incoming();
308 let (retry_sender, retry_receiver) = channel::unbounded();
309
310 loop {
311 select_biased! {
312 recv(project_updates_receiver) -> project_update => {
316 let Ok(project_update) = project_update else { break };
317
318 scheduler.local_mut(move |state, notifier, _, _| ProjectController::handle_update(state, notifier, project_update));
319 }
320 recv(incoming) -> msg => {
321 let Ok(msg) = msg else { break };
322
323 if connection.handle_shutdown(&msg)? {
324 break;
325 }
326 let task = match msg {
327 Message::Request(req) => server::request(req, retry_sender.clone()),
328 Message::Notification(notification) => server::notification(notification),
329 Message::Response(response) => scheduler.response(response),
330 };
331 scheduler.dispatch(task);
332 }
333 recv(proc_macro_channels.poll_responses_receiver) -> response => {
334 let Ok(()) = response else { break };
335
336 scheduler.local_mut(Self::on_proc_macro_response);
337 }
338 recv(proc_macro_channels.error_receiver) -> error => {
339 let Ok(()) = error else { break };
340
341 scheduler.local_mut(Self::on_proc_macro_error);
342 }
343 recv(retry_receiver) -> retry => {
344 let Ok((retry_info, handler)) = retry else { break };
345
346 let task = retry_info.task(handler);
347 scheduler.dispatch(task);
348 }
349 recv(analysis_progress_status_receiver) -> analysis_progress_status => {
350 let Ok(analysis_status) = analysis_progress_status else { break };
351
352 match analysis_status {
353 AnalysisStatus::Started => {
354 scheduler.meta_state
355 .lock()
356 .expect("should be able to acquire the MetaState")
357 .db_swapper
358 .start_stopwatch();
359 }
360 AnalysisStatus::Finished => {
361 scheduler.meta_state
362 .lock()
363 .expect("should be able to acquire the MetaState")
364 .db_swapper
365 .stop_stopwatch();
366
367 scheduler.local(|state, _, _notifier, requester, _responder|
368 Self::on_stopped_analysis(state, requester)
369 );
370 }
371 };
372 }
373 recv(code_lens_request_refresh_receiver) -> error => {
374 let Ok(()) = error else { break };
375
376 scheduler.local(|state: &State, _, _, requester, _| {
377 if state.client_capabilities.workspace_code_lens_refresh_support() {
378 CodeLensController::handle_refresh(requester);
379 }
380 });
381 }
382 }
383 }
384
385 Ok(())
386 }
387
388 fn on_proc_macro_error(state: &mut State, _: Notifier, _: &mut Requester<'_>, _: Responder) {
391 state.proc_macro_controller.force_restart(&mut state.db, &state.config);
392 }
393
394 fn on_proc_macro_response(
397 state: &mut State,
398 _: Notifier,
399 requester: &mut Requester<'_>,
400 _: Responder,
401 ) {
402 if let ServerStatus::Connected(client) =
403 state.db.proc_macro_input().proc_macro_server_status(&state.db)
404 && client.available_responses().len() != 0
405 {
406 state.proc_macro_controller.handle_response(
407 &mut state.db,
408 &state.config,
409 &state.client_capabilities,
410 requester,
411 );
412 }
413 }
414
415 fn on_stopped_analysis(state: &State, requester: &mut Requester<'_>) {
416 proc_macros::cache::save_proc_macro_cache(&state.db);
417 state
418 .code_lens_controller
419 .schedule_refreshing_all_lenses(state.db.clone(), state.config.clone());
420
421 if state.client_capabilities.workspace_semantic_tokens_refresh_support()
422 && let Err(err) = requester.request::<SemanticTokensRefresh>((), |_| Task::nothing())
423 {
424 error!("semantic tokens refresh failed: {err:#?}");
425 }
426 }
427
428 fn register_mutation_in_swapper(
429 _state: &mut State,
430 meta_state: MetaState,
431 _notifier: Notifier,
432 ) {
433 meta_state
434 .lock()
435 .expect("should be able to acquire the MetaState")
436 .db_swapper
437 .register_mutation();
438 }
439
440 fn maybe_swap_database(state: &mut State, meta_state: MetaState, _notifier: Notifier) {
442 meta_state.lock().expect("should be able to acquire the MetaState").db_swapper.maybe_swap(
443 &mut state.db,
444 &state.open_files,
445 &mut state.project_controller,
446 &state.proc_macro_controller,
447 );
448 }
449
450 fn refresh_diagnostics(state: &mut State, _meta_state: MetaState, _notifier: Notifier) {
452 state.diagnostics_controller.refresh(state);
453 }
454
455 fn reload(state: &mut State, requester: &mut Requester<'_>) -> LSPResult<()> {
457 state.project_controller.clear_loaded_workspaces();
458 state.config.reload(requester, &state.client_capabilities)?;
459
460 for uri in state.open_files.iter() {
461 let Some(file_id) = state.db.file_for_url(uri) else { continue };
462 if let FileLongId::OnDisk(file_path) = file_id.long(&state.db) {
463 state.project_controller.request_updating_project_for_file(file_path.clone());
464 }
465 }
466
467 Ok(())
468 }
469}