diff --git a/Cargo.lock b/Cargo.lock index 8692bc82..df7ef7e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2514,6 +2514,7 @@ dependencies = [ "squawk-linter", "squawk-syntax", "squawk-thread", + "tracing", ] [[package]] diff --git a/crates/squawk_server/Cargo.toml b/crates/squawk_server/Cargo.toml index 93050679..71c3e632 100644 --- a/crates/squawk_server/Cargo.toml +++ b/crates/squawk_server/Cargo.toml @@ -33,6 +33,7 @@ etcetera.workspace = true rustc-hash.workspace = true squawk-thread.workspace = true crossbeam-channel.workspace = true +tracing.workspace = true [lints] workspace = true diff --git a/crates/squawk_server/src/dispatch.rs b/crates/squawk_server/src/dispatch.rs index 153ff758..423ba9c4 100644 --- a/crates/squawk_server/src/dispatch.rs +++ b/crates/squawk_server/src/dispatch.rs @@ -2,22 +2,22 @@ use anyhow::Result; use log::{error, info}; -use lsp_server::{Message, Response}; +use lsp_server::{RequestId, Response}; use lsp_types::{notification::Notification as LspNotification, request::Request as LspRequest}; use squawk_thread::ThreadIntent; -use crate::system::{GlobalState, System}; +use crate::global_state::{GlobalState, Snapshot}; pub(crate) struct RequestDispatcher<'a> { req: Option, - system: &'a mut GlobalState, + global_state: &'a mut GlobalState, } impl<'a> RequestDispatcher<'a> { pub(crate) fn new(req: lsp_server::Request, system: &'a mut GlobalState) -> Self { Self { req: Some(req), - system, + global_state: system, } } @@ -36,18 +36,29 @@ impl<'a> RequestDispatcher<'a> { lsp_server::ErrorCode::ParseError as i32, err.to_string(), ); - if let Err(err) = self.system.sender.send(Message::Response(response)) { - error!("Failed to send parse error response: {err}"); - } + self.global_state.respond(response); None } } } - pub(crate) fn on( - self, - handler: fn(&dyn System, R::Params) -> Result, - ) -> Result + pub(crate) fn on_sync_mut( + mut self, + handler: fn(&mut GlobalState, R::Params) -> Result, + ) -> Self + where + R: LspRequest, + R::Params: Send + 'static, + { + if let Some((id, params)) = self.parse::() { + let result = handler(self.global_state, params); + let response = result_to_response::(id, result); + self.global_state.respond(response); + } + self + } + + pub(crate) fn on(self, handler: fn(&Snapshot, R::Params) -> Result) -> Self where R: LspRequest, R::Params: Send + 'static, @@ -57,8 +68,8 @@ impl<'a> RequestDispatcher<'a> { pub(crate) fn on_latency_sensitive( self, - handler: fn(&dyn System, R::Params) -> Result, - ) -> Result + handler: fn(&Snapshot, R::Params) -> Result, + ) -> Self where R: LspRequest, R::Params: Send + 'static, @@ -69,33 +80,22 @@ impl<'a> RequestDispatcher<'a> { fn on_with_thread_intent( mut self, intent: ThreadIntent, - handler: fn(&dyn System, R::Params) -> Result, - ) -> Result + handler: fn(&Snapshot, R::Params) -> Result, + ) -> Self where R: LspRequest, R::Params: Send + 'static, { if let Some((id, params)) = self.parse::() { - let snapshot = self.system.snapshot(); - - self.system.task_pool.spawn(intent, move || { - let resp = match handler(&snapshot, params) { - Ok(result) => Response::new_ok(id, result), - Err(err) => { - error!("Request handler failed: {err}"); - Response::new_err( - id, - lsp_server::ErrorCode::InternalError as i32, - err.to_string(), - ) - } - }; - - Message::Response(resp) + let snapshot = self.global_state.snapshot(); + + self.global_state.task_pool.handle.spawn(intent, move || { + let result = handler(&snapshot, params); + result_to_response::(id, result) }); } - Ok(self) + self } pub(crate) fn finish(self) { @@ -105,6 +105,23 @@ impl<'a> RequestDispatcher<'a> { } } +fn result_to_response(id: RequestId, result: Result) -> Response +where + R: LspRequest, +{ + match result { + Ok(result) => Response::new_ok(id, result), + Err(err) => { + error!("Request handler failed: {err}"); + Response::new_err( + id, + lsp_server::ErrorCode::InternalError as i32, + err.to_string(), + ) + } + } +} + pub(crate) struct NotificationDispatcher<'a> { notif: Option, state: &'a mut GlobalState, diff --git a/crates/squawk_server/src/global_state.rs b/crates/squawk_server/src/global_state.rs new file mode 100644 index 00000000..3f17fa43 --- /dev/null +++ b/crates/squawk_server/src/global_state.rs @@ -0,0 +1,232 @@ +use std::{num::NonZeroUsize, sync::Arc, time::Instant}; + +use crossbeam_channel::{Receiver, Sender, select, unbounded}; +use log::info; +use lsp_server::{Message, Response}; +use lsp_types::Url; +use lsp_types::notification::Notification as _; +use lsp_types::notification::{ + Cancel, DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument, +}; +use rustc_hash::FxHashMap; +use salsa::Setter; +use squawk_ide::db::{Database, File}; +use squawk_thread::TaskPool; + +use lsp_types::request::{ + CodeActionRequest, Completion, DocumentDiagnosticRequest, DocumentSymbolRequest, + FoldingRangeRequest, GotoDefinition, HoverRequest, InlayHintRequest, References, + SelectionRangeRequest, Shutdown, +}; + +use crate::dispatch::{NotificationDispatcher, RequestDispatcher}; +use crate::handlers::{ + SyntaxTreeRequest, TokensRequest, handle_cancel, handle_code_action, handle_completion, + handle_did_change, handle_did_close, handle_did_open, handle_document_diagnostic, + handle_document_symbol, handle_folding_range, handle_goto_definition, handle_hover, + handle_inlay_hints, handle_references, handle_selection_range, handle_shutdown, + handle_syntax_tree, handle_tokens, +}; + +type ReqQueue = lsp_server::ReqQueue<(String, Instant), ()>; + +pub(crate) struct Handle { + pub(crate) handle: H, + pub(crate) receiver: C, +} + +pub(super) struct GlobalState { + db: Database, + files: Arc>, + req_queue: ReqQueue, + sender: Sender, + pub(crate) task_pool: Handle, Receiver>, + shutdown_requested: bool, +} + +impl GlobalState { + pub(super) fn new(sender: Sender) -> Self { + let threads = std::thread::available_parallelism().unwrap_or(NonZeroUsize::MIN); + let task_pool = { + let (sender, receiver) = unbounded(); + let handle = TaskPool::new_with_threads(sender.clone(), threads); + Handle { handle, receiver } + }; + Self { + db: Database::default(), + files: Arc::new(FxHashMap::default()), + req_queue: ReqQueue::default(), + task_pool, + sender, + shutdown_requested: false, + } + } + + /// Readonly snapshot of the database & files for request handlers + pub(crate) fn snapshot(&self) -> Snapshot { + Snapshot { + db: self.db.clone(), + files: self.files.clone(), + } + } + + pub(crate) fn db(&self) -> &Database { + &self.db + } + + pub(crate) fn file(&self, uri: &Url) -> Option { + self.files.get(uri).copied() + } + + pub(crate) fn set(&mut self, uri: Url, content: String) { + if let Some(file) = self.files.get(&uri).copied() { + file.set_content(&mut self.db).to(content.into()); + } else { + let file = File::new(&self.db, content.into()); + Arc::make_mut(&mut self.files).insert(uri, file); + } + } + + pub(crate) fn remove(&mut self, uri: &Url) { + Arc::make_mut(&mut self.files).remove(uri); + } + + /// Track the request time and support marking cancellation + pub(crate) fn register_request( + &mut self, + request: &lsp_server::Request, + request_received: Instant, + ) { + self.req_queue.incoming.register( + request.id.clone(), + (request.method.clone(), request_received), + ); + } + + /// Wrapper to check for cancellation before sending + pub(crate) fn respond(&mut self, response: Response) { + if let Some((method, start)) = self.req_queue.incoming.complete(&response.id) { + let duration = start.elapsed(); + tracing::debug!(name: "message response", method, %response.id, duration = format_args!("{:0.2?}", duration)); + self.send(response.into()); + } + } + + /// Mark the request as cancelled + pub(crate) fn cancel(&mut self, request_id: lsp_server::RequestId) { + if let Some(response) = self.req_queue.incoming.cancel(request_id) { + self.send(response.into()); + } + } + + pub(crate) fn request_shutdown(&mut self) { + self.shutdown_requested = true; + } + + #[track_caller] + pub(crate) fn send(&self, message: Message) { + self.sender.send(message).unwrap(); + } + + pub(crate) fn run(&mut self, inbox: Receiver) -> anyhow::Result<()> { + let outbox = &self.task_pool.receiver.clone(); + while let Ok(event) = self.next_event(&inbox, outbox) { + let loop_start = Instant::now(); + match event { + Event::Inbox(msg) => match msg { + Message::Request(req) => { + info!("Received request: method={}, id={:?}", req.method, req.id); + + self.register_request(&req, loop_start); + + if self.shutdown_requested { + tracing::warn!( + "Received request `{}` after server shutdown was requested, discarding", + &req.method + ); + + self.respond(Response::new_err( + req.id, + lsp_server::ErrorCode::InvalidRequest as i32, + "Shutdown already requested".to_owned(), + )); + continue; + } + + RequestDispatcher::new(req, self) + .on_sync_mut::(handle_shutdown) + .on::(handle_goto_definition) + .on::(handle_hover) + .on::(handle_code_action) + .on::(handle_selection_range) + .on::(handle_inlay_hints) + .on::(handle_document_symbol) + .on::(handle_folding_range) + .on_latency_sensitive::(handle_completion) + .on::(handle_document_diagnostic) + .on::(handle_syntax_tree) + .on::(handle_tokens) + .on::(handle_references) + .finish(); + } + Message::Response(resp) => { + info!("Received response: id={:?}", resp.id); + } + Message::Notification(notif) => { + info!("Received notification: method={}", notif.method); + + if notif.method == lsp_types::notification::Exit::METHOD { + return Ok(()); + } + + NotificationDispatcher::new(notif, self) + .on::(handle_cancel)? + .on::(handle_did_open)? + .on::(handle_did_change)? + .on::(handle_did_close)? + .finish(); + } + }, + Event::Outbox(response) => { + // Instead of having the tasks send directly via the sender + // channel, we handle them on the main thread so we can check + // for cancellation first. + self.respond(response) + } + } + } + + Ok(()) + } + + fn next_event( + &self, + inbox: &Receiver, + outbox: &Receiver, + ) -> Result { + select! { + recv(inbox) -> msg => msg.map(Event::Inbox), + recv(outbox) -> task => task.map(Event::Outbox), + } + } +} + +pub(crate) struct Snapshot { + pub(crate) db: Database, + pub(crate) files: Arc>, +} + +impl Snapshot { + pub(crate) fn db(&self) -> &Database { + &self.db + } + + pub(crate) fn file(&self, uri: &Url) -> Option { + self.files.get(uri).copied() + } +} + +enum Event { + Inbox(Message), + Outbox(Response), +} diff --git a/crates/squawk_server/src/handlers.rs b/crates/squawk_server/src/handlers.rs index c6c31227..3f0f8998 100644 --- a/crates/squawk_server/src/handlers.rs +++ b/crates/squawk_server/src/handlers.rs @@ -9,6 +9,7 @@ mod inlay_hints; mod notifications; mod references; mod selection_range; +mod shutdown; mod syntax_tree; mod tokens; @@ -25,5 +26,6 @@ pub(crate) use notifications::{ }; pub(crate) use references::handle_references; pub(crate) use selection_range::handle_selection_range; +pub(crate) use shutdown::handle_shutdown; pub(crate) use syntax_tree::{SyntaxTreeRequest, handle_syntax_tree}; pub(crate) use tokens::{TokensRequest, handle_tokens}; diff --git a/crates/squawk_server/src/handlers/code_action.rs b/crates/squawk_server/src/handlers/code_action.rs index 358262a1..d1a4462b 100644 --- a/crates/squawk_server/src/handlers/code_action.rs +++ b/crates/squawk_server/src/handlers/code_action.rs @@ -8,11 +8,11 @@ use squawk_ide::code_actions::code_actions; use squawk_ide::db::line_index; use crate::diagnostic::{AssociatedDiagnosticData, DIAGNOSTIC_NAME}; +use crate::global_state::Snapshot; use crate::lsp_utils; -use crate::system::System; pub(crate) fn handle_code_action( - system: &dyn System, + system: &Snapshot, params: CodeActionParams, ) -> Result> { let uri = params.text_document.uri; diff --git a/crates/squawk_server/src/handlers/completion.rs b/crates/squawk_server/src/handlers/completion.rs index 5c04777e..a1e20737 100644 --- a/crates/squawk_server/src/handlers/completion.rs +++ b/crates/squawk_server/src/handlers/completion.rs @@ -3,11 +3,11 @@ use lsp_types::{CompletionParams, CompletionResponse}; use squawk_ide::completion::completion; use squawk_ide::db::line_index; +use crate::global_state::Snapshot; use crate::lsp_utils; -use crate::system::System; pub(crate) fn handle_completion( - system: &dyn System, + system: &Snapshot, params: CompletionParams, ) -> Result> { let uri = params.text_document_position.text_document.uri; diff --git a/crates/squawk_server/src/handlers/diagnostic.rs b/crates/squawk_server/src/handlers/diagnostic.rs index 51b5ac28..713687a7 100644 --- a/crates/squawk_server/src/handlers/diagnostic.rs +++ b/crates/squawk_server/src/handlers/diagnostic.rs @@ -4,10 +4,10 @@ use lsp_types::{ FullDocumentDiagnosticReport, RelatedFullDocumentDiagnosticReport, }; -use crate::system::System; +use crate::global_state::Snapshot; pub(crate) fn handle_document_diagnostic( - system: &dyn System, + system: &Snapshot, params: DocumentDiagnosticParams, ) -> Result { let uri = params.text_document.uri; diff --git a/crates/squawk_server/src/handlers/document_symbol.rs b/crates/squawk_server/src/handlers/document_symbol.rs index df31ae94..aa356a6c 100644 --- a/crates/squawk_server/src/handlers/document_symbol.rs +++ b/crates/squawk_server/src/handlers/document_symbol.rs @@ -4,11 +4,11 @@ use lsp_types::{DocumentSymbol, DocumentSymbolParams, DocumentSymbolResponse, Sy use squawk_ide::db::line_index; use squawk_ide::document_symbols::{DocumentSymbolKind, document_symbols}; +use crate::global_state::Snapshot; use crate::lsp_utils; -use crate::system::System; pub(crate) fn handle_document_symbol( - system: &dyn System, + system: &Snapshot, params: DocumentSymbolParams, ) -> Result> { let uri = params.text_document.uri; diff --git a/crates/squawk_server/src/handlers/folding_range.rs b/crates/squawk_server/src/handlers/folding_range.rs index 2fecfb6f..f04f67eb 100644 --- a/crates/squawk_server/src/handlers/folding_range.rs +++ b/crates/squawk_server/src/handlers/folding_range.rs @@ -3,11 +3,11 @@ use lsp_types::{FoldingRange, FoldingRangeParams}; use squawk_ide::db::line_index; use squawk_ide::folding_ranges::folding_ranges; +use crate::global_state::Snapshot; use crate::lsp_utils; -use crate::system::System; pub(crate) fn handle_folding_range( - system: &dyn System, + system: &Snapshot, params: FoldingRangeParams, ) -> Result>> { let uri = params.text_document.uri; diff --git a/crates/squawk_server/src/handlers/goto_definition.rs b/crates/squawk_server/src/handlers/goto_definition.rs index 65b2d80c..0b01b0e8 100644 --- a/crates/squawk_server/src/handlers/goto_definition.rs +++ b/crates/squawk_server/src/handlers/goto_definition.rs @@ -3,11 +3,11 @@ use lsp_types::{GotoDefinitionParams, GotoDefinitionResponse}; use squawk_ide::db::line_index; use squawk_ide::goto_definition::goto_definition; +use crate::global_state::Snapshot; use crate::lsp_utils::{self, to_location}; -use crate::system::System; pub(crate) fn handle_goto_definition( - system: &dyn System, + system: &Snapshot, params: GotoDefinitionParams, ) -> Result> { let uri = params.text_document_position_params.text_document.uri; diff --git a/crates/squawk_server/src/handlers/hover.rs b/crates/squawk_server/src/handlers/hover.rs index 87a6120c..3183234d 100644 --- a/crates/squawk_server/src/handlers/hover.rs +++ b/crates/squawk_server/src/handlers/hover.rs @@ -3,10 +3,10 @@ use lsp_types::{Hover, HoverContents, HoverParams, LanguageString, MarkedString} use squawk_ide::db::line_index; use squawk_ide::hover::hover; +use crate::global_state::Snapshot; use crate::lsp_utils; -use crate::system::System; -pub(crate) fn handle_hover(system: &dyn System, params: HoverParams) -> Result> { +pub(crate) fn handle_hover(system: &Snapshot, params: HoverParams) -> Result> { let uri = params.text_document_position_params.text_document.uri; let position = params.text_document_position_params.position; diff --git a/crates/squawk_server/src/handlers/inlay_hints.rs b/crates/squawk_server/src/handlers/inlay_hints.rs index 91700ecc..73b70166 100644 --- a/crates/squawk_server/src/handlers/inlay_hints.rs +++ b/crates/squawk_server/src/handlers/inlay_hints.rs @@ -6,11 +6,11 @@ use squawk_ide::builtins::{builtins_line_index, builtins_url}; use squawk_ide::db::line_index; use squawk_ide::inlay_hints::inlay_hints; +use crate::global_state::Snapshot; use crate::lsp_utils; -use crate::system::System; pub(crate) fn handle_inlay_hints( - system: &dyn System, + system: &Snapshot, params: InlayHintParams, ) -> Result>> { let uri = params.text_document.uri; diff --git a/crates/squawk_server/src/handlers/notifications.rs b/crates/squawk_server/src/handlers/notifications.rs index 88847062..10207a3c 100644 --- a/crates/squawk_server/src/handlers/notifications.rs +++ b/crates/squawk_server/src/handlers/notifications.rs @@ -6,10 +6,15 @@ use lsp_types::{ notification::{Notification as _, PublishDiagnostics}, }; +use crate::global_state::GlobalState; use crate::lsp_utils; -use crate::system::GlobalState; -pub(crate) fn handle_cancel(_state: &mut GlobalState, _params: CancelParams) -> Result<()> { +pub(crate) fn handle_cancel(state: &mut GlobalState, params: CancelParams) -> Result<()> { + let id: lsp_server::RequestId = match params.id { + lsp_types::NumberOrString::Number(id) => id.into(), + lsp_types::NumberOrString::String(id) => id.into(), + }; + state.cancel(id); Ok(()) } @@ -61,7 +66,7 @@ pub(crate) fn handle_did_close( params: serde_json::to_value(publish_params)?, }; - state.sender.send(Message::Notification(notification))?; + state.send(Message::Notification(notification)); Ok(()) } diff --git a/crates/squawk_server/src/handlers/references.rs b/crates/squawk_server/src/handlers/references.rs index e32b010f..332ccd9f 100644 --- a/crates/squawk_server/src/handlers/references.rs +++ b/crates/squawk_server/src/handlers/references.rs @@ -3,11 +3,11 @@ use lsp_types::{Location, ReferenceParams}; use squawk_ide::db::line_index; use squawk_ide::find_references::find_references; +use crate::global_state::Snapshot; use crate::lsp_utils::{self, to_location}; -use crate::system::System; pub(crate) fn handle_references( - system: &dyn System, + system: &Snapshot, params: ReferenceParams, ) -> Result>> { let uri = params.text_document_position.text_document.uri; diff --git a/crates/squawk_server/src/handlers/selection_range.rs b/crates/squawk_server/src/handlers/selection_range.rs index 23a52c76..ea3a3986 100644 --- a/crates/squawk_server/src/handlers/selection_range.rs +++ b/crates/squawk_server/src/handlers/selection_range.rs @@ -3,11 +3,11 @@ use lsp_types::SelectionRangeParams; use rowan::TextRange; use squawk_ide::db::{line_index, parse}; +use crate::global_state::Snapshot; use crate::lsp_utils; -use crate::system::System; pub(crate) fn handle_selection_range( - system: &dyn System, + system: &Snapshot, params: SelectionRangeParams, ) -> Result>> { let uri = params.text_document.uri; diff --git a/crates/squawk_server/src/handlers/shutdown.rs b/crates/squawk_server/src/handlers/shutdown.rs new file mode 100644 index 00000000..3972da97 --- /dev/null +++ b/crates/squawk_server/src/handlers/shutdown.rs @@ -0,0 +1,9 @@ +use anyhow::Result; + +use crate::global_state::GlobalState; + +pub(crate) fn handle_shutdown(state: &mut GlobalState, _params: ()) -> Result<()> { + state.request_shutdown(); + + Ok(()) +} diff --git a/crates/squawk_server/src/handlers/syntax_tree.rs b/crates/squawk_server/src/handlers/syntax_tree.rs index 6894d4e6..2c0fb7f9 100644 --- a/crates/squawk_server/src/handlers/syntax_tree.rs +++ b/crates/squawk_server/src/handlers/syntax_tree.rs @@ -3,7 +3,7 @@ use log::info; use lsp_types::request::Request; use squawk_ide::db::parse; -use crate::system::System; +use crate::global_state::Snapshot; #[derive(serde::Deserialize, serde::Serialize)] pub(crate) struct SyntaxTreeParams { @@ -19,7 +19,7 @@ impl Request for SyntaxTreeRequest { const METHOD: &'static str = "squawk/syntaxTree"; } -pub(crate) fn handle_syntax_tree(system: &dyn System, params: SyntaxTreeParams) -> Result { +pub(crate) fn handle_syntax_tree(system: &Snapshot, params: SyntaxTreeParams) -> Result { let uri = params.text_document.uri; info!("Generating syntax tree for: {uri}"); diff --git a/crates/squawk_server/src/handlers/tokens.rs b/crates/squawk_server/src/handlers/tokens.rs index fec367d1..16b19863 100644 --- a/crates/squawk_server/src/handlers/tokens.rs +++ b/crates/squawk_server/src/handlers/tokens.rs @@ -2,7 +2,7 @@ use anyhow::Result; use log::info; use lsp_types::request::Request; -use crate::system::System; +use crate::global_state::Snapshot; #[derive(serde::Deserialize, serde::Serialize)] pub(crate) struct TokensParams { @@ -18,7 +18,7 @@ impl Request for TokensRequest { const METHOD: &'static str = "squawk/tokens"; } -pub(crate) fn handle_tokens(system: &dyn System, params: TokensParams) -> Result { +pub(crate) fn handle_tokens(system: &Snapshot, params: TokensParams) -> Result { let uri = params.text_document.uri; info!("Generating tokens for: {uri}"); diff --git a/crates/squawk_server/src/lib.rs b/crates/squawk_server/src/lib.rs index bb5c7ce8..d121151b 100644 --- a/crates/squawk_server/src/lib.rs +++ b/crates/squawk_server/src/lib.rs @@ -1,10 +1,10 @@ mod diagnostic; mod dispatch; +mod global_state; mod handlers; mod ignore; mod lint; mod lsp_utils; mod server; -mod system; pub use server::run; diff --git a/crates/squawk_server/src/lsp_utils.rs b/crates/squawk_server/src/lsp_utils.rs index acbf3fb7..06d44adf 100644 --- a/crates/squawk_server/src/lsp_utils.rs +++ b/crates/squawk_server/src/lsp_utils.rs @@ -13,7 +13,7 @@ use squawk_ide::code_actions::ActionKind; use squawk_ide::db::line_index; use squawk_ide::folding_ranges::{Fold, FoldKind}; -use crate::system::System; +use crate::global_state::Snapshot; fn text_range(index: &LineIndex, range: lsp_types::Range) -> Option { let start = offset(index, range.start)?; @@ -212,7 +212,7 @@ pub(crate) fn apply_incremental_changes( pub(crate) fn to_location( db: &dyn salsa::Database, - system: &dyn System, + system: &Snapshot, uri: &Url, loc: squawk_ide::goto_definition::Location, ) -> Option { diff --git a/crates/squawk_server/src/server.rs b/crates/squawk_server/src/server.rs index f635a27b..4fcb1ccf 100644 --- a/crates/squawk_server/src/server.rs +++ b/crates/squawk_server/src/server.rs @@ -1,30 +1,14 @@ -use std::num::NonZeroUsize; - use anyhow::Result; use log::info; -use lsp_server::{Connection, Message}; +use lsp_server::Connection; use lsp_types::{ CodeActionKind, CodeActionOptions, CodeActionProviderCapability, CompletionOptions, DiagnosticOptions, DiagnosticServerCapabilities, FoldingRangeProviderCapability, HoverProviderCapability, InitializeParams, OneOf, SelectionRangeProviderCapability, ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind, WorkDoneProgressOptions, - notification::{Cancel, DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument}, - request::{ - CodeActionRequest, Completion, DocumentDiagnosticRequest, DocumentSymbolRequest, - FoldingRangeRequest, GotoDefinition, HoverRequest, InlayHintRequest, References, - SelectionRangeRequest, - }, }; -use crate::dispatch::{NotificationDispatcher, RequestDispatcher}; -use crate::handlers::{ - SyntaxTreeRequest, TokensRequest, handle_cancel, handle_code_action, handle_completion, - handle_did_change, handle_did_close, handle_did_open, handle_document_diagnostic, - handle_document_symbol, handle_folding_range, handle_goto_definition, handle_hover, - handle_inlay_hints, handle_references, handle_selection_range, handle_syntax_tree, - handle_tokens, -}; -use crate::system::GlobalState; +use crate::global_state::GlobalState; pub fn run() -> Result<()> { info!("Starting Squawk LSP server"); @@ -93,48 +77,5 @@ fn main_loop(connection: Connection, params: serde_json::Value) -> Result<()> { let client_name = init_params.client_info.map(|x| x.name); info!("Client name: {client_name:?}"); - let threads = std::thread::available_parallelism().unwrap_or(NonZeroUsize::MIN); - let mut state = GlobalState::new(connection.sender.clone(), threads); - - for msg in &connection.receiver { - match msg { - Message::Request(req) => { - info!("Received request: method={}, id={:?}", req.method, req.id); - - if connection.handle_shutdown(&req)? { - info!("Received shutdown request, exiting"); - return Ok(()); - } - - RequestDispatcher::new(req, &mut state) - .on::(handle_goto_definition)? - .on::(handle_hover)? - .on::(handle_code_action)? - .on::(handle_selection_range)? - .on::(handle_inlay_hints)? - .on::(handle_document_symbol)? - .on::(handle_folding_range)? - .on_latency_sensitive::(handle_completion)? - .on::(handle_document_diagnostic)? - .on::(handle_syntax_tree)? - .on::(handle_tokens)? - .on::(handle_references)? - .finish(); - } - Message::Response(resp) => { - info!("Received response: id={:?}", resp.id); - } - Message::Notification(notif) => { - info!("Received notification: method={}", notif.method); - - NotificationDispatcher::new(notif, &mut state) - .on::(handle_cancel)? - .on::(handle_did_open)? - .on::(handle_did_change)? - .on::(handle_did_close)? - .finish(); - } - } - } - Ok(()) + GlobalState::new(connection.sender).run(connection.receiver) } diff --git a/crates/squawk_server/src/system.rs b/crates/squawk_server/src/system.rs deleted file mode 100644 index 0420a254..00000000 --- a/crates/squawk_server/src/system.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::{num::NonZeroUsize, sync::Arc}; - -use crossbeam_channel::Sender; -use lsp_server::Message; -use lsp_types::Url; -use rustc_hash::FxHashMap; -use salsa::Setter; -use squawk_ide::db::{Database, File}; -use squawk_thread::TaskPool; - -pub(crate) trait System { - fn db(&self) -> &Database; - fn file(&self, uri: &Url) -> Option; -} - -pub(crate) struct Snapshot { - db: Database, - files: Arc>, -} - -impl System for Snapshot { - fn db(&self) -> &Database { - &self.db - } - - fn file(&self, uri: &Url) -> Option { - self.files.get(uri).copied() - } -} - -pub(super) struct GlobalState { - db: Database, - files: Arc>, - pub(crate) sender: Sender, - pub(crate) task_pool: TaskPool, -} - -impl GlobalState { - pub(super) fn new(sender: Sender, threads: NonZeroUsize) -> Self { - Self { - db: Database::default(), - files: Arc::new(FxHashMap::default()), - task_pool: TaskPool::new_with_threads(sender.clone(), threads), - sender, - } - } - - pub(crate) fn snapshot(&self) -> Snapshot { - Snapshot { - db: self.db.clone(), - files: self.files.clone(), - } - } - - pub(crate) fn db(&self) -> &Database { - &self.db - } - - pub(crate) fn file(&self, uri: &Url) -> Option { - self.files.get(uri).copied() - } - - pub(crate) fn set(&mut self, uri: Url, content: String) { - if let Some(file) = self.files.get(&uri).copied() { - file.set_content(&mut self.db).to(content.into()); - } else { - let file = File::new(&self.db, content.into()); - Arc::make_mut(&mut self.files).insert(uri, file); - } - } - - pub(crate) fn remove(&mut self, uri: &Url) { - Arc::make_mut(&mut self.files).remove(uri); - } -}