diff --git a/Cargo.lock b/Cargo.lock index 8ed4465e..ee8e1095 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "annotate-snippets" version = "0.12.4" @@ -182,7 +188,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn 2.0.106", "which", @@ -218,6 +224,12 @@ dependencies = [ "cfg_aliases", ] +[[package]] +name = "boxcar" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f64beae40a84da1b4b26ff2761a5b895c12adc41dc25aaee1c4f2bbfe97a6e" + [[package]] name = "bumpalo" version = "3.19.0" @@ -433,6 +445,34 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -724,6 +764,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -889,6 +935,20 @@ name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] [[package]] name = "heck" @@ -1137,6 +1197,24 @@ dependencies = [ "similar", ] +[[package]] +name = "intrusive-collections" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86" +dependencies = [ + "memoffset", +] + +[[package]] +name = "inventory" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "009ae045c87e7082cb72dab0ccd01ae075dd00141ddc108f43a0ea150a9e7227" +dependencies = [ + "rustversion", +] + [[package]] name = "io-uring" version = "0.7.10" @@ -1288,6 +1366,15 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.27" @@ -1613,6 +1700,29 @@ dependencies = [ "unicode-width 0.2.1", ] +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -1711,6 +1821,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + [[package]] name = "potential_utf" version = "0.1.3" @@ -1882,6 +1998,35 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.9.3", +] + [[package]] name = "regex" version = "1.11.2" @@ -1970,7 +2115,7 @@ dependencies = [ "countme", "hashbrown 0.14.5", "memoffset", - "rustc-hash", + "rustc-hash 1.1.0", "text-size", ] @@ -2006,6 +2151,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -2062,6 +2213,49 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "salsa" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f77debccd43ba198e9cee23efd7f10330ff445e46a98a2b107fed9094a1ee676" +dependencies = [ + "boxcar", + "crossbeam-queue", + "crossbeam-utils", + "hashbrown 0.15.5", + "hashlink", + "indexmap", + "intrusive-collections", + "inventory", + "parking_lot", + "portable-atomic", + "rayon", + "rustc-hash 2.1.1", + "salsa-macro-rules", + "salsa-macros", + "smallvec", + "thin-vec", + "tracing", +] + +[[package]] +name = "salsa-macro-rules" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea07adbf42d91cc076b7daf3b38bc8168c19eb362c665964118a89bc55ef19a5" + +[[package]] +name = "salsa-macros" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16d4d8b66451b9c75ddf740b7fc8399bc7b8ba33e854a5d7526d18708f67b05" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "synstructure", +] + [[package]] name = "same-file" version = "1.0.6" @@ -2080,6 +2274,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "sec1" version = "0.7.3" @@ -2466,6 +2666,7 @@ dependencies = [ "lsp-server", "lsp-types", "rowan", + "salsa", "serde", "serde_json", "simplelog", @@ -2656,6 +2857,12 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" +[[package]] +name = "thin-vec" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" + [[package]] name = "thiserror" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index 51d4f61a..3e0a8b97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ drop_bomb = "0.1.5" camino = "1.1.9" pg_query = "6.1.0" rowan = "0.15.15" +salsa = "0.26.0" smol_str = "0.3.2" enum-iterator = "2.1.0" itertools = "0.14.0" diff --git a/crates/squawk_server/Cargo.toml b/crates/squawk_server/Cargo.toml index 40890291..1b869e56 100644 --- a/crates/squawk_server/Cargo.toml +++ b/crates/squawk_server/Cargo.toml @@ -20,6 +20,7 @@ simplelog.workspace = true lsp-server.workspace = true lsp-types.workspace = true rowan.workspace = true +salsa.workspace = true serde.workspace = true serde_json.workspace = true squawk-ide.workspace = true diff --git a/crates/squawk_server/src/db.rs b/crates/squawk_server/src/db.rs new file mode 100644 index 00000000..e9b1ffd4 --- /dev/null +++ b/crates/squawk_server/src/db.rs @@ -0,0 +1,29 @@ +use ::line_index::LineIndex; +use salsa::Database as Db; +use salsa::Storage; +use squawk_syntax::{Parse, SourceFile}; + +#[salsa::input] +pub struct File { + #[returns(ref)] + pub content: String, + pub version: i32, +} + +#[salsa::tracked] +pub fn parse(db: &dyn Db, file: File) -> Parse { + SourceFile::parse(file.content(db)) +} + +#[salsa::tracked] +pub fn line_index(db: &dyn Db, file: File) -> LineIndex { + LineIndex::new(file.content(db)) +} + +#[salsa::db] +#[derive(Default)] +pub struct Database { + storage: Storage, +} + +impl salsa::Database for Database {} diff --git a/crates/squawk_server/src/lib.rs b/crates/squawk_server/src/lib.rs index a25fea2e..9fc44a10 100644 --- a/crates/squawk_server/src/lib.rs +++ b/crates/squawk_server/src/lib.rs @@ -1,6 +1,6 @@ +use ::line_index::LineIndex; use anyhow::{Context, Result}; use etcetera::BaseStrategy; -use line_index::LineIndex; use log::info; use lsp_server::{Connection, Message, Notification, Response}; use lsp_types::{ @@ -24,6 +24,7 @@ use lsp_types::{ }, }; use rowan::TextRange; +use salsa::Setter; use squawk_ide::completion::completion; use squawk_ide::document_symbols::{DocumentSymbolKind, document_symbols}; use squawk_ide::find_references::find_references; @@ -31,12 +32,13 @@ use squawk_ide::goto_definition::goto_definition; use squawk_ide::hover::hover; use squawk_ide::inlay_hints::inlay_hints; use squawk_ide::{builtins::BUILTINS_SQL, code_actions::code_actions}; -use squawk_syntax::SourceFile; use std::{collections::HashMap, fs, sync::OnceLock}; use diagnostic::DIAGNOSTIC_NAME; +use crate::db::{Database, File, line_index, parse}; use crate::diagnostic::AssociatedDiagnosticData; +mod db; mod diagnostic; mod ignore; mod lint; @@ -65,34 +67,47 @@ struct DocumentState { } trait FileSystem { - fn get_content(&self, uri: &Url) -> Option<&str>; + fn db(&self) -> &Database; + fn file(&self, uri: &Url) -> Option; fn set(&mut self, uri: Url, state: DocumentState); fn remove(&mut self, uri: &Url); } -struct InMemoryFileSystem { - documents: HashMap, +struct FileDatabase { + pub db: Database, + files: HashMap, } -impl InMemoryFileSystem { +impl FileDatabase { fn new() -> Self { Self { - documents: HashMap::new(), + db: Database::default(), + files: HashMap::new(), } } } -impl FileSystem for InMemoryFileSystem { - fn get_content(&self, uri: &Url) -> Option<&str> { - self.documents.get(uri).map(|doc| doc.content.as_str()) +impl FileSystem for FileDatabase { + fn db(&self) -> &Database { + return &self.db; + } + + fn file(&self, uri: &Url) -> Option { + self.files.get(uri).copied() } fn set(&mut self, uri: Url, state: DocumentState) { - self.documents.insert(uri, state); + if let Some(file) = self.files.get(&uri).copied() { + file.set_content(&mut self.db).to(state.content); + file.set_version(&mut self.db).to(state.version); + } else { + let file = File::new(&self.db, state.content, state.version); + self.files.insert(uri, file); + } } fn remove(&mut self, uri: &Url) { - self.documents.remove(uri); + self.files.remove(uri); } } @@ -154,7 +169,7 @@ 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 mut file_system = InMemoryFileSystem::new(); + let mut file_system = FileDatabase::new(); for msg in &connection.receiver { match msg { @@ -236,13 +251,13 @@ fn handle_goto_definition( let uri = params.text_document_position_params.text_document.uri; let position = params.text_document_position_params.position; - let content = file_system.get_content(&uri).unwrap_or(""); - let parse = SourceFile::parse(content); - let file = parse.tree(); - let line_index = LineIndex::new(content); + let db = file_system.db(); + let file = file_system.file(&uri).unwrap(); + let parse = parse(db, file); + let line_index = line_index(db, file); let offset = lsp_utils::offset(&line_index, position).unwrap(); - let ranges = goto_definition(&file, offset) + let ranges = goto_definition(&parse.tree(), offset) .into_iter() .filter_map(|location| { debug_assert!( @@ -288,13 +303,13 @@ fn handle_hover( let uri = params.text_document_position_params.text_document.uri; let position = params.text_document_position_params.position; - let content = file_system.get_content(&uri).unwrap_or(""); - let parse = SourceFile::parse(content); - let file = parse.tree(); - let line_index = LineIndex::new(content); + let db = file_system.db(); + let file = file_system.file(&uri).unwrap(); + let parse = parse(db, file); + let line_index = line_index(db, file); let offset = lsp_utils::offset(&line_index, position).unwrap(); - let type_info = hover(&file, offset); + let type_info = hover(&parse.tree(), offset); let result = type_info.map(|type_str| Hover { contents: HoverContents::Scalar(MarkedString::LanguageString(LanguageString { @@ -322,12 +337,13 @@ fn handle_inlay_hints( let params: InlayHintParams = serde_json::from_value(req.params)?; let uri = params.text_document.uri; - let content = file_system.get_content(&uri).unwrap_or(""); - let parse = SourceFile::parse(content); - let file = parse.tree(); - let line_index = LineIndex::new(content); + let db = file_system.db(); + let file = file_system.file(&uri).unwrap(); + let parse = parse(db, file); + let line_index = line_index(db, file); - let hints = inlay_hints(&file); + // TODO: move this to a tracked function + let hints = inlay_hints(&parse.tree()); let lsp_hints: Vec = hints .into_iter() @@ -397,12 +413,12 @@ fn handle_document_symbol( let params: DocumentSymbolParams = serde_json::from_value(req.params)?; let uri = params.text_document.uri; - let content = file_system.get_content(&uri).unwrap_or(""); - let parse = SourceFile::parse(content); - let file = parse.tree(); - let line_index = LineIndex::new(content); + let db = file_system.db(); + let file = file_system.file(&uri).unwrap(); + let parse = parse(db, file); + let line_index = line_index(db, file); - let symbols = document_symbols(&file); + let symbols = document_symbols(&parse.tree()); fn convert_symbol( sym: squawk_ide::document_symbols::DocumentSymbol, @@ -481,10 +497,11 @@ fn handle_selection_range( let params: SelectionRangeParams = serde_json::from_value(req.params)?; let uri = params.text_document.uri; - let content = file_system.get_content(&uri).unwrap_or(""); - let parse = SourceFile::parse(content); + let db = file_system.db(); + let file = file_system.file(&uri).unwrap(); + let parse = parse(db, file); let root = parse.syntax_node(); - let line_index = LineIndex::new(content); + let line_index = line_index(db, file); let mut selection_ranges = vec![]; @@ -539,13 +556,13 @@ fn handle_references( let uri = params.text_document_position.text_document.uri; let position = params.text_document_position.position; - let content = file_system.get_content(&uri).unwrap_or(""); - let parse = SourceFile::parse(content); - let file = parse.tree(); - let line_index = LineIndex::new(content); + let db = file_system.db(); + let file = file_system.file(&uri).unwrap(); + let parse = parse(db, file); + let line_index = line_index(db, file); let offset = lsp_utils::offset(&line_index, position).unwrap(); - let refs = find_references(&file, offset); + let refs = find_references(&parse.tree(), offset); let include_declaration = params.context.include_declaration; let locations: Vec = refs @@ -586,10 +603,10 @@ fn handle_completion( let uri = params.text_document_position.text_document.uri; let position = params.text_document_position.position; - let content = file_system.get_content(&uri).unwrap_or(""); - let parse = SourceFile::parse(content); - let file = parse.tree(); - let line_index = LineIndex::new(content); + let db = file_system.db(); + let file = file_system.file(&uri).unwrap(); + let parse = parse(db, file); + let line_index = line_index(db, file); let Some(offset) = lsp_utils::offset(&line_index, position) else { let resp = Response { @@ -601,7 +618,8 @@ fn handle_completion( return Ok(()); }; - let completion_items = completion(&file, offset) + // TODO: move this to a tracked function + let completion_items = completion(&parse.tree(), offset) .into_iter() .map(lsp_utils::completion_item) .collect(); @@ -628,13 +646,14 @@ fn handle_code_action( let mut actions: CodeActionResponse = Vec::new(); - let content = file_system.get_content(&uri).unwrap_or(""); - let parse = SourceFile::parse(content); - let file = parse.tree(); - let line_index = LineIndex::new(content); + let db = file_system.db(); + let file = file_system.file(&uri).unwrap(); + let parse = parse(db, file); + let line_index = line_index(db, file); let offset = lsp_utils::offset(&line_index, params.range.start).unwrap(); - let ide_actions = code_actions(file, offset).unwrap_or_default(); + // TODO: move this to a tracked function + let ide_actions = code_actions(parse.tree(), offset).unwrap_or_default(); for action in ide_actions { let lsp_action = lsp_utils::code_action(&line_index, uri.clone(), action); @@ -786,12 +805,12 @@ fn handle_did_open( let content = params.text_document.text; let version = params.text_document.version; - file_system.set(uri.clone(), DocumentState { content, version }); + // TODO: move this to a tracked function + let diagnostics = lint::lint(&content); - let content = file_system.get_content(&uri).unwrap_or(""); + file_system.set(uri.clone(), DocumentState { content, version }); // TODO: we need a better setup for "run func when input changed" - let diagnostics = lint::lint(content); publish_diagnostics(connection, uri, version, diagnostics)?; Ok(()) @@ -806,10 +825,13 @@ fn handle_did_change( let uri = params.text_document.uri; let version = params.text_document.version; - let content = file_system.get_content(&uri).unwrap_or(""); + let db = file_system.db(); + let file = file_system.file(&uri).unwrap(); + let content = file.content(db); let updated_content = lsp_utils::apply_incremental_changes(content, params.content_changes); + // TODO: move this to a tracked function let diagnostics = lint::lint(&updated_content); publish_diagnostics(connection, uri.clone(), version, diagnostics)?; @@ -868,9 +890,9 @@ fn handle_syntax_tree( info!("Generating syntax tree for: {uri}"); - let content = file_system.get_content(&uri).unwrap_or(""); - - let parse = SourceFile::parse(content); + let db = file_system.db(); + let file = file_system.file(&uri).unwrap(); + let parse = parse(db, file); let syntax_tree = format!("{:#?}", parse.syntax_node()); let resp = Response { @@ -899,8 +921,11 @@ fn handle_tokens( info!("Generating tokens for: {uri}"); - let content = file_system.get_content(&uri).unwrap_or(""); + let db = file_system.db(); + let file = file_system.file(&uri).unwrap(); + let content = file.content(db); + // TODO: move this to a tracked function let tokens = squawk_lexer::tokenize(content); let mut output = Vec::new();