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 unsafe { std::env::set_var("MIMALLOC_PURGE_DELAY", "0") };
75
76 let _log_guard = init_logging();
77 set_panic_hook();
78
79 info!("language server starting");
80 env_config::report_to_logs();
81
82 let exit_code = match Backend::new() {
83 Ok(backend) => {
84 if let Err(err) = backend.run().map(|handle| handle.join()) {
85 error!("language server encountered an unrecoverable error: {err}");
86 ExitCode::from(1)
87 } else {
88 ExitCode::from(0)
89 }
90 }
91 Err(err) => {
92 error!("language server failed during initialization: {err}");
93 ExitCode::from(1)
94 }
95 };
96
97 info!("language server stopped");
98 exit_code
99}
100
101fn init_logging() -> Option<impl Drop> {
105 use std::fs;
106 use std::io::IsTerminal;
107
108 use tracing_chrome::ChromeLayerBuilder;
109 use tracing_subscriber::filter::{EnvFilter, LevelFilter, Targets};
110 use tracing_subscriber::fmt::Layer;
111 use tracing_subscriber::fmt::time::Uptime;
112 use tracing_subscriber::prelude::*;
113
114 let mut guard = None;
115
116 let fmt_layer = Layer::new()
117 .with_writer(io::stderr)
118 .with_timer(Uptime::default())
119 .with_ansi(io::stderr().is_terminal())
120 .with_filter(
121 EnvFilter::builder()
122 .with_default_directive(LevelFilter::WARN.into())
123 .with_env_var(env_config::CAIRO_LS_LOG)
124 .from_env_lossy(),
125 );
126
127 let profile_layer = if env_config::tracing_profile() {
128 let mut path = PathBuf::from(format!(
129 "./cairols-profile-{}.json",
130 SystemTime::UNIX_EPOCH.elapsed().unwrap().as_micros()
131 ));
132
133 let profile_file = fs::File::create(&path).expect("Failed to create profile file.");
135
136 if let Ok(canonical) = fs::canonicalize(&path) {
138 path = canonical;
139 }
140
141 eprintln!("this LS run will output tracing profile to: {}", path.display());
142 eprintln!(
143 "open that file with https://ui.perfetto.dev (or chrome://tracing) to analyze it"
144 );
145
146 let (profile_layer, profile_layer_guard) =
147 ChromeLayerBuilder::new().writer(profile_file).include_args(true).build();
148
149 let profile_layer = profile_layer.with_filter(
152 Targets::new().with_default(LevelFilter::TRACE).with_target("salsa", LevelFilter::WARN),
153 );
154
155 guard = Some(profile_layer_guard);
156 Some(profile_layer)
157 } else {
158 None
159 };
160
161 tracing::subscriber::set_global_default(
162 tracing_subscriber::registry().with(fmt_layer).with(profile_layer),
163 )
164 .expect("Could not set up global logger.");
165
166 guard
167}
168
169fn set_panic_hook() {
171 let previous_hook = panic::take_hook();
172 panic::set_hook(Box::new(move |info| {
173 if !is_cancelled(info.payload()) {
174 previous_hook(info);
175 }
176 }))
177}
178
179struct Backend {
180 connection: Connection,
181 state: State,
182}
183
184impl Backend {
185 fn new() -> Result<Self> {
186 let connection_initializer = ConnectionInitializer::stdio();
187
188 Self::initialize(connection_initializer, std::env::current_dir()?)
189 }
190
191 fn initialize(connection_initializer: ConnectionInitializer, cwd: PathBuf) -> Result<Self> {
195 let (id, init_params) = connection_initializer.initialize_start()?;
196
197 let client_capabilities = init_params.capabilities;
198 let server_capabilities = collect_server_capabilities(&client_capabilities);
199
200 let connection = connection_initializer.initialize_finish(id, server_capabilities)?;
201 let state = State::new(connection.make_sender(), client_capabilities, cwd);
202
203 Ok(Self { connection, state })
204 }
205
206 fn run(self) -> Result<JoinHandle<Result<()>>> {
208 event_loop_thread(move || {
209 let Self { mut state, connection } = self;
210 let proc_macro_channels = state.proc_macro_controller.channels();
211 let project_updates_receiver = state.project_controller.response_receiver();
212 let analysis_progress_receiver =
213 state.analysis_progress_controller.get_status_receiver();
214 let code_lens_request_refresh_receiver =
215 state.code_lens_controller.request_refresh_receiver();
216
217 let mut scheduler = Scheduler::new(&mut state, connection.make_sender());
218
219 Self::dispatch_setup_tasks(&mut scheduler);
220
221 scheduler.on_sync_mut_task(Self::register_mutation_in_swapper);
223
224 scheduler.on_sync_mut_task(Self::maybe_swap_database);
228
229 scheduler.on_sync_mut_task(Self::refresh_diagnostics);
233
234 scheduler.on_sync_mut_task(|state, _, _| {
236 state.analysis_progress_controller.mutation();
237 });
238
239 let inactivity_swap_request_receiver = scheduler
240 .meta_state
241 .lock()
242 .expect("should be able to acquire the MetaState")
243 .inactivity_monitor
244 .swap_request_receiver();
245
246 let result = Self::event_loop(
247 &connection,
248 proc_macro_channels,
249 project_updates_receiver,
250 analysis_progress_receiver,
251 code_lens_request_refresh_receiver,
252 inactivity_swap_request_receiver,
253 scheduler,
254 );
255
256 state.db.cancel_all();
257
258 if let Err(err) = connection.close() {
259 error!("failed to close connection to the language server: {err:?}");
260 }
261
262 result
263 })
264 }
265
266 fn dispatch_setup_tasks(scheduler: &mut Scheduler<'_>) {
268 scheduler.local_mut(Self::register_dynamic_capabilities);
269
270 scheduler.local_mut(|state, _notifier, requester, _responder| {
271 let _ = state.config.reload_on_start(
272 requester,
273 &mut state.db,
274 &mut state.proc_macro_controller,
275 &mut state.analysis_progress_controller,
276 &state.client_capabilities,
277 );
278 });
279 }
280
281 fn register_dynamic_capabilities(
282 state: &mut State,
283 _notifier: Notifier,
284 requester: &mut Requester<'_>,
285 _responder: Responder,
286 ) {
287 let registrations = collect_dynamic_registrations(&state.client_capabilities);
288
289 let _ = requester
290 .request::<lsp_types::request::RegisterCapability>(
291 RegistrationParams { registrations },
292 |()| {
293 debug!("capabilities successfully registered dynamically");
294 Task::nothing()
295 },
296 )
297 .inspect_err(|e| {
298 error!(
299 "failed to register dynamic capabilities, some features may not work \
300 properly: {e:?}"
301 )
302 });
303 }
304
305 fn event_loop(
312 connection: &Connection,
313 proc_macro_channels: ProcMacroChannels,
314 project_updates_receiver: Receiver<ProjectUpdate>,
315 analysis_progress_status_receiver: Receiver<AnalysisStatus>,
316 code_lens_request_refresh_receiver: Receiver<()>,
317 inactivity_swap_request_receiver: Receiver<()>,
318 mut scheduler: Scheduler<'_>,
319 ) -> Result<()> {
320 let incoming = connection.incoming();
321 let (retry_sender, retry_receiver) = channel::unbounded();
322
323 loop {
324 select_biased! {
325 recv(project_updates_receiver) -> project_update => {
329 let Ok(project_update) = project_update else { break };
330
331 scheduler.local_mut(move |state, notifier, _, _| ProjectController::handle_update(state, notifier, project_update));
332 }
333 recv(incoming) -> msg => {
334 let Ok(msg) = msg else { break };
335
336 if connection.handle_shutdown(&msg)? {
337 break;
338 }
339 scheduler.meta_state
340 .lock()
341 .expect("should be able to acquire the MetaState")
342 .inactivity_monitor
343 .notify_activity();
344 let task = match msg {
345 Message::Request(req) => server::request(req, retry_sender.clone()),
346 Message::Notification(notification) => server::notification(notification),
347 Message::Response(response) => scheduler.response(response),
348 };
349 scheduler.dispatch(task);
350 }
351 recv(proc_macro_channels.poll_responses_receiver) -> response => {
352 let Ok(()) = response else { break };
353
354 scheduler.local_mut(Self::on_proc_macro_response);
355 }
356 recv(proc_macro_channels.error_receiver) -> error => {
357 let Ok(()) = error else { break };
358
359 scheduler.local_mut(Self::on_proc_macro_error);
360 }
361 recv(retry_receiver) -> retry => {
362 let Ok((retry_info, handler)) = retry else { break };
363
364 let task = retry_info.task(handler);
365 scheduler.dispatch(task);
366 }
367 recv(analysis_progress_status_receiver) -> analysis_progress_status => {
368 let Ok(analysis_status) = analysis_progress_status else { break };
369
370 match analysis_status {
371 AnalysisStatus::Started => {
372 let mut ms = scheduler.meta_state
373 .lock()
374 .expect("should be able to acquire the MetaState");
375 ms.db_swapper.start_stopwatch();
376 }
377 AnalysisStatus::Finished => {
378 scheduler.meta_state
379 .lock()
380 .expect("should be able to acquire the MetaState")
381 .db_swapper
382 .stop_stopwatch();
383
384 scheduler.local(|state, _, _notifier, requester, _responder|
385 Self::on_stopped_analysis(state, requester)
386 );
387 }
388 };
389 }
390 recv(code_lens_request_refresh_receiver) -> error => {
391 let Ok(()) = error else { break };
392
393 scheduler.local(|state: &State, _, _, requester, _| {
394 if state.client_capabilities.workspace_code_lens_refresh_support() {
395 CodeLensController::handle_refresh(requester);
396 }
397 });
398 }
399 recv(inactivity_swap_request_receiver) -> result => {
400 let Ok(()) = result else { break };
401
402 let meta_state = scheduler.meta_state.clone();
403 scheduler.local_mut(move |state, _, _, _| {
404 let mut ms = meta_state
405 .lock()
406 .expect("should be able to acquire the MetaState");
407 ms.inactivity_monitor.notify_swap_triggered();
408 ms.db_swapper.swap_on_inactivity(
409 &mut state.db,
410 &state.open_files,
411 );
412 });
413 }
414 }
415 }
416
417 Ok(())
418 }
419
420 fn on_proc_macro_error(state: &mut State, _: Notifier, _: &mut Requester<'_>, _: Responder) {
423 state.proc_macro_controller.force_restart(&mut state.db, &state.config);
424 }
425
426 fn on_proc_macro_response(
429 state: &mut State,
430 _: Notifier,
431 requester: &mut Requester<'_>,
432 _: Responder,
433 ) {
434 let has_responses = match state.db.proc_macro_input().proc_macro_server_status(&state.db) {
439 ServerStatus::Connected(client) => client.available_responses().len() != 0,
440 _ => false,
441 };
442
443 if has_responses {
444 state.proc_macro_controller.handle_response(
445 &mut state.db,
446 &state.config,
447 &state.client_capabilities,
448 requester,
449 );
450 }
451 }
452
453 fn on_stopped_analysis(state: &State, requester: &mut Requester<'_>) {
454 proc_macros::cache::save_proc_macro_cache(&state.db);
455 state
456 .code_lens_controller
457 .schedule_refreshing_all_lenses(state.db.clone(), state.config.clone());
458
459 if state.client_capabilities.workspace_semantic_tokens_refresh_support()
460 && let Err(err) = requester.request::<SemanticTokensRefresh>((), |_| Task::nothing())
461 {
462 error!("semantic tokens refresh failed: {err:#?}");
463 }
464 }
465
466 fn register_mutation_in_swapper(
467 _state: &mut State,
468 meta_state: MetaState,
469 _notifier: Notifier,
470 ) {
471 let mut ms = meta_state.lock().expect("should be able to acquire the MetaState");
472 ms.db_swapper.register_mutation();
473 ms.inactivity_monitor.notify_activity();
474 }
475
476 fn maybe_swap_database(state: &mut State, meta_state: MetaState, _notifier: Notifier) {
478 meta_state
479 .lock()
480 .expect("should be able to acquire the MetaState")
481 .db_swapper
482 .maybe_swap(&mut state.db, &state.open_files);
483 }
484
485 fn refresh_diagnostics(state: &mut State, _meta_state: MetaState, _notifier: Notifier) {
487 state.diagnostics_controller.refresh(
488 &state.db,
489 &state.open_files,
490 &state.config,
491 &state.project_controller.configs_registry(),
492 );
493 }
494
495 fn reload(state: &mut State, requester: &mut Requester<'_>) -> LSPResult<()> {
497 state.project_controller.clear_loaded_workspaces();
498 state.config.reload(requester, &state.client_capabilities)?;
499
500 for uri in state.open_files.iter() {
501 let Some(file_id) = state.db.file_for_url(uri) else { continue };
502 if let FileLongId::OnDisk(file_path) = file_id.long(&state.db) {
503 state.project_controller.request_updating_project_for_file(file_path.clone());
504 }
505 }
506
507 Ok(())
508 }
509}