diff --git a/crates/completion/Cargo.toml b/crates/completion/Cargo.toml new file mode 100644 index 0000000..f4aa6bf --- /dev/null +++ b/crates/completion/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "completion" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs new file mode 100644 index 0000000..7d12d9a --- /dev/null +++ b/crates/completion/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: usize, right: usize) -> usize { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/crates/database/Cargo.toml b/crates/database/Cargo.toml new file mode 100644 index 0000000..6f2d88b --- /dev/null +++ b/crates/database/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "database" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +fst = "0.4.7" +lsp-types = "0.93.1" +salsa = "0.17.0-pre.2" +tree-sitter = "0.20.9" + +types = {path = "../types", version = "0.1.0"} +queries = { path = "../queries", version = "0.1.0" } +parking_lot = "0.12.1" diff --git a/crates/server/src/global_state/cache.rs b/crates/database/src/cache.rs similarity index 100% rename from crates/server/src/global_state/cache.rs rename to crates/database/src/cache.rs diff --git a/crates/database/src/db.rs b/crates/database/src/db.rs new file mode 100644 index 0000000..fd4ca82 --- /dev/null +++ b/crates/database/src/db.rs @@ -0,0 +1,36 @@ +// Here we have our database + +use std::fmt::Debug; + +use lsp_types::Url; +use types::SourceFile; + +// input query group +#[salsa::query_group(SourceDatabaseStorage)] +pub trait SourceDatabase: salsa::Database { + // query + #[salsa::input] + fn source(&self, name: Url) -> SourceFile; +} + +#[salsa::database(SourceDatabaseStorage)] +#[derive(Default)] +pub struct RootDatabase { + pub storage: salsa::Storage, +} + +impl salsa::Database for RootDatabase {} + +impl salsa::ParallelDatabase for RootDatabase { + fn snapshot(&self) -> salsa::Snapshot { + salsa::Snapshot::new(RootDatabase { + storage: self.storage.snapshot(), + }) + } +} + +impl Debug for RootDatabase { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RootDatabase").finish() + } +} diff --git a/crates/database/src/lib.rs b/crates/database/src/lib.rs new file mode 100644 index 0000000..cbca73a --- /dev/null +++ b/crates/database/src/lib.rs @@ -0,0 +1,4 @@ +mod db; +mod state; + +pub use self::{db::*, state::*}; diff --git a/crates/database/src/state.rs b/crates/database/src/state.rs new file mode 100644 index 0000000..9a8d5ed --- /dev/null +++ b/crates/database/src/state.rs @@ -0,0 +1,56 @@ +use std::{collections::HashMap, sync::Arc}; + +use lsp_types::{Diagnostic, Url}; +use parking_lot::Mutex; +use queries::errors::build_diagnostics; +use salsa::ParallelDatabase; +use tree_sitter::Tree; +use types::SourceFile; + +use crate::db::{RootDatabase, SourceDatabase}; + +// Here is our global state +#[derive(Default, Debug)] +pub struct GlobalState { + pub database: RootDatabase, + + // store all the asts into a hashmap + pub asts: Arc>>, + + // We store the diagnostics in a hashmap for fast lookup + pub diagnostics: Arc>>>, +} + +impl GlobalState { + pub fn new() -> Self { + Self::default() + } + + // store the content of the file in the database + pub fn set_source_inputs(&mut self, url: Url, content: SourceFile) { + self.database.set_source(url, content); + } + + pub fn update_diagnostics(&mut self, url: Url) { + let binding = self.asts.lock(); + let root_node = binding.get(&url).unwrap().root_node(); + self.diagnostics.lock().insert( + url.clone(), + build_diagnostics(self.database.source(url).text.into_bytes(), &root_node), + ); + } + + pub fn snapshot(&self) -> GlobalStateSnapshot { + GlobalStateSnapshot { + db: self.database.snapshot(), + asts: Arc::clone(&self.asts), + diagnostics: Arc::clone(&self.diagnostics), + } + } +} + +pub struct GlobalStateSnapshot { + pub db: salsa::Snapshot, + pub asts: Arc>>, + pub diagnostics: Arc>>>, +} diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index 8df48fd..a64c749 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -19,4 +19,7 @@ libloading = "0.7.2" queries = { path = "../queries", version = "0.1.0" } helper = { path = "../helper", version = "0.1.0" } +database = { path = "../database", version = "0.1.0" } +types = { path = "../types", version = "0.1.0" } + diff --git a/crates/server/src/global_state/mod.rs b/crates/server/src/global_state/mod.rs deleted file mode 100644 index b8f0cb0..0000000 --- a/crates/server/src/global_state/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod cache; -mod state; - -pub use self::state::*; diff --git a/crates/server/src/global_state/state.rs b/crates/server/src/global_state/state.rs deleted file mode 100644 index 17da62b..0000000 --- a/crates/server/src/global_state/state.rs +++ /dev/null @@ -1,153 +0,0 @@ -use std::collections::HashMap; - -use helper::types::Symbol; -use log::warn; -use lsp_types::{Diagnostic, Position, Url}; -use queries::errors::build_diagnostics; -use tree_sitter::{Node, Range, Tree}; - -type Byte = u8; -type ScopeID = usize; - -#[derive(Debug, Clone)] -pub struct Properties { - pub ast: Tree, - // pub source_code: TextDocumentItem, - pub language_id: String, - pub version: i32, - - // use byte vector store the source code - pub source_code: Vec, - pub keywords: Vec, - pub ordered_scopes: Vec, - pub definitions_lookup_map: HashMap>, - pub identifiers: HashMap>, -} - -impl Properties { - pub fn clear(&mut self) { - self.identifiers.clear(); - self.definitions_lookup_map.clear(); - self.ordered_scopes.clear(); - self.keywords.clear(); - self.source_code.clear(); - self.version = 0; - self.language_id.clear(); - - // Ast not changed, in case fast parse when it opened again - } -} - -#[derive(Debug, Clone)] -pub struct GlobalState { - pub sources: HashMap, - pub diagnostics: HashMap>, -} - -impl GlobalState { - /// Create a new GlobalState - pub fn new() -> Self { - GlobalState { - sources: HashMap::new(), - diagnostics: HashMap::new(), - } - } - - /// Get the diagnostics of a given url - pub fn get_diagnostics(&self, uri: &lsp_types::Url) -> Option> { - self.diagnostics.get(uri).cloned() - } - - // update the diagnostics of a given url - pub fn update_diagnostics(&mut self, uri: &lsp_types::Url) -> Result<(), String> { - let properties = match self.sources.get(uri) { - Some(properties) => properties, - None => return Err("No properties found".into()), - }; - let diagnostics = build_diagnostics( - properties.source_code.clone(), - &self.sources.get(uri).unwrap().ast.root_node(), - ); - self.diagnostics.insert(uri.clone(), diagnostics); - Ok(()) - } - - /// Get a inmutable reference to the ast - pub fn get_tree(&self, url: &Url) -> Option<&Tree> { - match self.sources.get(url) { - Some(properties) => Some(&properties.ast), - None => None, - } - } - - pub fn get_snapshot_tree(&self, url: &Url) -> Option { - self.sources - .get(url) - .map(|properties| properties.ast.clone()) - } - - /// Get the source code of a given url, return None if not found, byte vector otherwise - pub fn get_source_code(&self, url: &Url) -> Option> { - self.sources - .get(url) - .map(|properties| properties.source_code.clone()) - } - - /// Get the language_id of a given url, return None if not found, language_id otherwise - pub fn get_language_id(&self, url: &Url) -> Option { - self.sources - .get(url) - .map(|properties| properties.language_id.to_string()) - } - - /// Update the source code of a given url - pub fn update_source_code(&mut self, url: &Url, new_source_code: Vec) { - if let Some(properties) = self.sources.get_mut(url) { - properties.source_code = new_source_code; - } - } - - pub fn update_tree(&mut self, url: &Url, new_tree: Tree) { - if let Some(properties) = self.sources.get_mut(url) { - properties.ast = new_tree; - } - } - - /// Get node at a given position - pub fn _get_node_at_position(&self, url: &Url, position: Position) -> Option { - let properties = self.sources.get(url)?; - let node = properties.ast.root_node(); - node.descendant_for_point_range( - tree_sitter::Point { - row: position.line as usize, - column: position.character as usize, - }, - tree_sitter::Point { - row: position.line as usize, - column: position.character as usize, - }, - ) - } - - /// Get document version of a given url, return 0 if not found, version otherwise - pub fn get_version(&self, uri: &lsp_types::Url) -> Option { - let source = self.sources.get(uri); - match source { - Some(source) => Some(source.version), - // we don't have a version, return 0 - None => Some(0), - } - } - - pub fn clear(&mut self, uri: &lsp_types::Url) { - match self.sources.get_mut(uri) { - Some(properties) => { - properties.clear(); - } - None => { - warn!("clear: no properties found for uri: {:?}", uri); - } - }; - self.diagnostics.clear(); - } -} diff --git a/crates/server/src/handler/completion.rs b/crates/server/src/handler/completion.rs index 6143d43..2f70c93 100644 --- a/crates/server/src/handler/completion.rs +++ b/crates/server/src/handler/completion.rs @@ -1,3 +1,4 @@ +use database::GlobalState; use helper::types::Symbol; use log::debug; use lsp_server::{ErrorCode::InternalError, RequestId, Response}; @@ -6,8 +7,6 @@ use lsp_types::{ }; use queries::utils::get_smallest_scope_id_by_position; -use crate::global_state::GlobalState; - pub fn completion(id: RequestId, params: CompletionParams, state: GlobalState) -> Response { debug!("got completion request #{}: {:?}", id, params); diff --git a/crates/server/src/handler/did_change.rs b/crates/server/src/handler/did_change.rs index 59fa03c..d1768ef 100644 --- a/crates/server/src/handler/did_change.rs +++ b/crates/server/src/handler/did_change.rs @@ -1,3 +1,4 @@ +use database::{GlobalState, SourceDatabase}; use helper::{ convert::{offset_to_position, position_to_offset}, tree_mutator::{get_parser, perform_edit}, @@ -6,13 +7,10 @@ use log::{debug, error}; use lsp_types::{self, DidChangeTextDocumentParams}; use tree_sitter::{InputEdit, Point}; -use crate::global_state::GlobalState; - pub fn did_change(params: DidChangeTextDocumentParams, global_state: &mut GlobalState) { - let language_id = global_state - .get_language_id(¶ms.text_document.uri) - .unwrap_or_default(); - let mut parser = match get_parser(language_id) { + let mut source_file = global_state.database.source(params.text_document.uri); + // get the language id + let mut parser = match get_parser(source_file.language_id) { Some(parser) => parser, None => { error!("No parser found for language"); @@ -21,11 +19,7 @@ pub fn did_change(params: DidChangeTextDocumentParams, global_state: &mut Global }; // Check version - if params.text_document.version - <= global_state - .get_version(¶ms.text_document.uri) - .unwrap_or(0) - { + if params.text_document.version <= source_file.version { error!("Received outdated version of text document"); return; } @@ -36,9 +30,7 @@ pub fn did_change(params: DidChangeTextDocumentParams, global_state: &mut Global } // update cache - let mut source_code = global_state - .get_source_code(¶ms.text_document.uri) - .unwrap_or_else(|| "".as_bytes().to_vec()); + let mut source_code = source_file.text.into_bytes(); // make a clone of the source code let mut start_byte; let mut end_byte; let mut start_position; @@ -54,7 +46,7 @@ pub fn did_change(params: DidChangeTextDocumentParams, global_state: &mut Global }; // copy to a new tree - let mut old_tree = match global_state.get_tree(¶ms.text_document.uri) { + let mut old_tree = match global_state.asts.get(&source_file.url) { Some(tree) => tree.clone(), None => return, }; @@ -131,32 +123,14 @@ pub fn did_change(params: DidChangeTextDocumentParams, global_state: &mut Global } // update cache - global_state.update_source_code(¶ms.text_document.uri, source_code.clone()); + global_state.set_source_inputs(source_file.url, source_file); // Use final source code and final tree to generate new AST - let new_tree = match parser.parse(source_code.clone(), Some(&old_tree)) { + let new_tree = match parser.parse(source_code, Some(&old_tree)) { Some(tree) => tree, None => return, }; // update tree - global_state.update_tree(¶ms.text_document.uri, new_tree); - - // update diagnostics - match global_state.update_diagnostics(¶ms.text_document.uri) { - Ok(()) => (), - Err(e) => { - error!("{}", e); - } - } - // pospone the cache update to did_save - // global_state.update_cache( - // lsp_types::TextDocumentItem { - // language_id, - // uri: params.text_document.uri.to_owned(), - // version: params.text_document.version, - // text: String::from_utf8(source_code).unwrap(), - // }, - // &new_tree, - // ); + global_state.asts.insert(source_file.url, new_tree); } diff --git a/crates/server/src/handler/did_close.rs b/crates/server/src/handler/did_close.rs index 35bf29b..5c6586b 100644 --- a/crates/server/src/handler/did_close.rs +++ b/crates/server/src/handler/did_close.rs @@ -1,4 +1,4 @@ -use crate::global_state::GlobalState; +use database::GlobalState; pub fn did_close(params: lsp_types::DidCloseTextDocumentParams, global_state: &mut GlobalState) { global_state.clear(¶ms.text_document.uri); diff --git a/crates/server/src/handler/did_open.rs b/crates/server/src/handler/did_open.rs index ee1a740..49260d8 100644 --- a/crates/server/src/handler/did_open.rs +++ b/crates/server/src/handler/did_open.rs @@ -1,7 +1,7 @@ +use database::GlobalState; use helper::tree_mutator::get_parser; use log::{debug, error}; - -use crate::global_state::GlobalState; +use types::SourceFile; pub fn did_open(params: lsp_types::DidOpenTextDocumentParams, global_state: &mut GlobalState) { debug!("Received a DidOpenTextDocument: {:?}", params); @@ -22,6 +22,14 @@ pub fn did_open(params: lsp_types::DidOpenTextDocumentParams, global_state: &mut return; } }; - - global_state.build_cache(params.text_document, Some(&tree)); + global_state.set_source_inputs( + params.text_document.uri, + SourceFile { + url: params.text_document.uri, + text: params.text_document.text, + version: params.text_document.version, + language_id: params.text_document.language_id, + }, + ); + global_state.asts.insert(params.text_document.uri, tree); } diff --git a/crates/server/src/handler/did_save.rs b/crates/server/src/handler/did_save.rs index 211070c..498dc07 100644 --- a/crates/server/src/handler/did_save.rs +++ b/crates/server/src/handler/did_save.rs @@ -1,40 +1,7 @@ -use log::{debug, error}; -use queries::{highlight::update_identifiers_kind, locals::build_definitions_and_scopes}; - -use crate::global_state::GlobalState; +use database::GlobalState; +use log::debug; pub fn did_save(params: lsp_types::DidSaveTextDocumentParams, global_state: &mut GlobalState) { debug!("Received a DidSaveTextDocumentParams: {:?}", params); - - let mut properties = global_state - .sources - .get_mut(¶ms.text_document.uri) - .unwrap(); - - // we rebuild the identifier - let (definitions_lookup_map, ordered_scopes, mut identifiers) = build_definitions_and_scopes( - &properties.source_code.to_vec(), - &properties.ast.root_node(), - &properties.language_id, - ); - - update_identifiers_kind( - &mut identifiers, - &ordered_scopes, - &properties.source_code, - &properties.ast, - &properties.language_id, - ); - // Save it to the global state - properties.identifiers = identifiers; - properties.definitions_lookup_map = definitions_lookup_map; - properties.ordered_scopes = ordered_scopes; - - // update diagnostics - match global_state.update_diagnostics(¶ms.text_document.uri) { - Ok(()) => (), - Err(e) => { - error!("{}", e); - } - } + // TODO: update diagnostics } diff --git a/crates/server/src/handler/document_symbol.rs b/crates/server/src/handler/document_symbol.rs index 3c8b83d..a693e19 100644 --- a/crates/server/src/handler/document_symbol.rs +++ b/crates/server/src/handler/document_symbol.rs @@ -1,9 +1,8 @@ +use database::GlobalState; use helper::convert::ts_range_to_lsp_range; use lsp_server::{ErrorCode::ParseError, RequestId, Response}; use lsp_types::{DocumentSymbol, DocumentSymbolParams}; -use crate::global_state::GlobalState; - pub fn document_symbol( id: RequestId, params: DocumentSymbolParams, diff --git a/crates/server/src/handler/format.rs b/crates/server/src/handler/format.rs index 5db03a3..93e613f 100644 --- a/crates/server/src/handler/format.rs +++ b/crates/server/src/handler/format.rs @@ -1,9 +1,8 @@ +use database::GlobalState; use lsp_server::{ErrorCode::ParseError, RequestId, Response}; use lsp_types::{error_codes::REQUEST_CANCELLED, DocumentFormattingParams, TextEdit}; use queries::indents::text_edits; -use crate::global_state::GlobalState; - pub fn format( id: RequestId, params: DocumentFormattingParams, diff --git a/crates/server/src/handler/goto_definition.rs b/crates/server/src/handler/goto_definition.rs index d32a854..14f50a3 100644 --- a/crates/server/src/handler/goto_definition.rs +++ b/crates/server/src/handler/goto_definition.rs @@ -1,12 +1,11 @@ +use database::GlobalStateSnapshot; use log::{debug, error}; use lsp_server::ErrorCode::ParseError; -use crate::global_state::GlobalState; - pub fn goto_definition( id: lsp_server::RequestId, params: lsp_types::GotoDefinitionParams, - global_state: GlobalState, + global_state: GlobalStateSnapshot, ) -> lsp_server::Response { debug!("got gotoDefinition request #{}: {:?}", id, params); let properties = global_state diff --git a/crates/server/src/handler/publish_diagnostics.rs b/crates/server/src/handler/publish_diagnostics.rs index 32b89c1..2be8467 100644 --- a/crates/server/src/handler/publish_diagnostics.rs +++ b/crates/server/src/handler/publish_diagnostics.rs @@ -1,25 +1,27 @@ +use database::{GlobalStateSnapshot, SourceDatabase}; use log::debug; -use lsp_types::notification::Notification; - -use crate::global_state::GlobalState; +use lsp_types::notification::{Notification, PublishDiagnostics}; pub fn publish_diagnostics( uri: lsp_types::Url, - global_state: GlobalState, + global_state: GlobalStateSnapshot, ) -> lsp_server::Notification { - let diagnostics = global_state.get_diagnostics(&uri).unwrap_or_default(); + // accuire the lock + let diagnostics = global_state.diagnostics.lock(); + // get the diagnostics for the file + let diagnostics = diagnostics.get(&uri).unwrap(); debug!("publish_diagnostics: {:?}", diagnostics); let params = lsp_types::PublishDiagnosticsParams { uri: uri.clone(), diagnostics: diagnostics.to_vec(), - version: global_state.get_version(&uri), + version: Some(global_state.db.source(uri).version), }; let result = serde_json::to_value(¶ms).unwrap(); lsp_server::Notification { - method: lsp_types::notification::PublishDiagnostics::METHOD.to_string(), + method: PublishDiagnostics::METHOD.to_string(), params: result, } } diff --git a/crates/server/src/handler/references.rs b/crates/server/src/handler/references.rs index 57543ef..88591c2 100644 --- a/crates/server/src/handler/references.rs +++ b/crates/server/src/handler/references.rs @@ -1,11 +1,10 @@ +use database::{GlobalState, GlobalStateSnapshot}; use helper::tree_walker::get_named_node_by_position; use lsp_server::{ErrorCode::ParseError, RequestId, Response}; use lsp_types::ReferenceParams; use queries::utils::get_smallest_scope_id_by_node; -use crate::global_state::GlobalState; - -pub fn references(id: RequestId, params: ReferenceParams, state: GlobalState) -> Response { +pub fn references(id: RequestId, params: ReferenceParams, state: GlobalStateSnapshot) -> Response { let uri = params.text_document_position.text_document.uri; let position = params.text_document_position.position; let tree = if let Some(tree) = state.get_tree(&uri) { diff --git a/crates/server/src/handler/rename.rs b/crates/server/src/handler/rename.rs index dabe6aa..0ae838f 100644 --- a/crates/server/src/handler/rename.rs +++ b/crates/server/src/handler/rename.rs @@ -1,12 +1,11 @@ use std::collections::HashMap; +use database::GlobalState; use helper::{tree_walker::get_named_node_by_position, types::Symbol}; use lsp_server::{ErrorCode::ParseError, RequestId, Response}; use lsp_types::{RenameParams, TextEdit, Url, WorkspaceEdit}; use queries::utils::get_smallest_scope_id_by_node; -use crate::global_state::GlobalState; - /// Setp: /// 1. Find the smallest scope id of the node /// 2. Find all nodes in the smallest scope id diff --git a/crates/server/src/lib.rs b/crates/server/src/lib.rs index 4cb0083..9f36a7a 100644 --- a/crates/server/src/lib.rs +++ b/crates/server/src/lib.rs @@ -1,5 +1,4 @@ mod caps; -mod global_state; mod handler; mod macros; mod main_loop; diff --git a/crates/server/src/macros.rs b/crates/server/src/macros.rs index 80b5842..2f8e0a1 100644 --- a/crates/server/src/macros.rs +++ b/crates/server/src/macros.rs @@ -49,10 +49,8 @@ pub mod notification { match not_res { Ok(params) => { handler::$method(params.clone(), &mut $state); - let not = handler::publish_diagnostics( - params.text_document.uri, - $state.get_snapshot(), - ); + let not = + handler::publish_diagnostics(params.text_document.uri, $state.snapshot()); $conn.sender.send(Message::Notification(not))?; continue; } diff --git a/crates/server/src/main_loop.rs b/crates/server/src/main_loop.rs index e97ddd7..a6a65fe 100644 --- a/crates/server/src/main_loop.rs +++ b/crates/server/src/main_loop.rs @@ -1,10 +1,11 @@ use std::error::Error; +use database::GlobalState; use log::{debug, error, warn}; use lsp_server::{Connection, Message}; use lsp_types::InitializeParams; -use crate::{global_state, handler, not, not_match, req, req_match}; +use crate::{handler, not, not_match, req, req_match}; pub fn main_loop( connection: Connection, @@ -12,7 +13,7 @@ pub fn main_loop( ) -> Result<(), Box> { warn!("starting main loop"); - let mut global_state = global_state::GlobalState::new(); + let mut global_state = GlobalState::new(); for msg in &connection.receiver { // debug!("got msg: {:#?}", msg); @@ -23,7 +24,7 @@ pub fn main_loop( } debug!("got request: {:?}", req); - req_match!(req, connection, global_state.get_snapshot()); + req_match!(req, connection, global_state.snapshot()); } Message::Response(resp) => { debug!("got response: {:?}", resp); diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml new file mode 100644 index 0000000..ce679c3 --- /dev/null +++ b/crates/types/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "types" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +lsp-types = "0.93.1" diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs new file mode 100644 index 0000000..e19dd7b --- /dev/null +++ b/crates/types/src/lib.rs @@ -0,0 +1,11 @@ +// Internal representation of all types in this project + +use lsp_types::Url; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct SourceFile { + pub url: Url, + pub text: String, + pub language_id: String, + pub version: i32, +}