diff --git a/crates/pg_analyse/src/filter.rs b/crates/pg_analyse/src/filter.rs index 391831f3..045f5f51 100644 --- a/crates/pg_analyse/src/filter.rs +++ b/crates/pg_analyse/src/filter.rs @@ -42,7 +42,7 @@ impl<'analysis> AnalysisFilter<'analysis> { /// Return `true` if the group `G` matches this filter pub fn match_group(&self) -> bool { self.match_category::() - && self.enabled_rules.map_or(true, |enabled_rules| { + && self.enabled_rules.is_none_or(|enabled_rules| { enabled_rules.iter().any(|filter| filter.match_group::()) }) && !self @@ -54,7 +54,7 @@ impl<'analysis> AnalysisFilter<'analysis> { /// Return `true` if the rule `R` matches this filter pub fn match_rule(&self) -> bool { self.match_category::<::Category>() - && self.enabled_rules.map_or(true, |enabled_rules| { + && self.enabled_rules.is_none_or(|enabled_rules| { enabled_rules.iter().any(|filter| filter.match_rule::()) }) && !self @@ -94,13 +94,13 @@ impl<'a> RuleFilter<'a> { } } -impl<'a> Debug for RuleFilter<'a> { +impl Debug for RuleFilter<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { Display::fmt(self, f) } } -impl<'a> Display for RuleFilter<'a> { +impl Display for RuleFilter<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { RuleFilter::Group(group) => { @@ -113,7 +113,7 @@ impl<'a> Display for RuleFilter<'a> { } } -impl<'a> pg_console::fmt::Display for RuleFilter<'a> { +impl pg_console::fmt::Display for RuleFilter<'_> { fn fmt(&self, fmt: &mut pg_console::fmt::Formatter) -> std::io::Result<()> { match self { RuleFilter::Group(group) => { diff --git a/crates/pg_cli/src/commands/daemon.rs b/crates/pg_cli/src/commands/daemon.rs index 714327f1..f35b6111 100644 --- a/crates/pg_cli/src/commands/daemon.rs +++ b/crates/pg_cli/src/commands/daemon.rs @@ -180,7 +180,7 @@ pub(crate) fn read_most_recent_log_file( let most_recent = fs::read_dir(biome_log_path)? .flatten() - .filter(|file| file.file_type().map_or(false, |ty| ty.is_file())) + .filter(|file| file.file_type().is_ok_and(|ty| ty.is_file())) .filter_map(|file| { match file .file_name() diff --git a/crates/pg_cli/src/commands/mod.rs b/crates/pg_cli/src/commands/mod.rs index a0934a90..22708491 100644 --- a/crates/pg_cli/src/commands/mod.rs +++ b/crates/pg_cli/src/commands/mod.rs @@ -230,7 +230,7 @@ impl PgLspCommand { pub fn is_verbose(&self) -> bool { self.cli_options() - .map_or(false, |cli_options| cli_options.verbose) + .is_some_and(|cli_options| cli_options.verbose) } pub fn log_level(&self) -> LoggingLevel { diff --git a/crates/pg_cli/src/execute/traverse.rs b/crates/pg_cli/src/execute/traverse.rs index 934edc40..b67e9f3d 100644 --- a/crates/pg_cli/src/execute/traverse.rs +++ b/crates/pg_cli/src/execute/traverse.rs @@ -413,7 +413,7 @@ pub(crate) struct TraversalOptions<'ctx, 'app> { pub(crate) evaluated_paths: RwLock>, } -impl<'ctx, 'app> TraversalOptions<'ctx, 'app> { +impl TraversalOptions<'_, '_> { pub(crate) fn increment_changed(&self, path: &PgLspPath) { self.changed.fetch_add(1, Ordering::Relaxed); self.evaluated_paths @@ -441,7 +441,7 @@ impl<'ctx, 'app> TraversalOptions<'ctx, 'app> { } } -impl<'ctx, 'app> TraversalContext for TraversalOptions<'ctx, 'app> { +impl TraversalContext for TraversalOptions<'_, '_> { fn interner(&self) -> &PathInterner { &self.interner } diff --git a/crates/pg_cli/src/reporter/github.rs b/crates/pg_cli/src/reporter/github.rs index 783ed758..6b1588b1 100644 --- a/crates/pg_cli/src/reporter/github.rs +++ b/crates/pg_cli/src/reporter/github.rs @@ -16,7 +16,7 @@ impl Reporter for GithubReporter { } pub(crate) struct GithubReporterVisitor<'a>(pub(crate) &'a mut dyn Console); -impl<'a> ReporterVisitor for GithubReporterVisitor<'a> { +impl ReporterVisitor for GithubReporterVisitor<'_> { fn report_summary( &mut self, _execution: &Execution, diff --git a/crates/pg_cli/src/reporter/gitlab.rs b/crates/pg_cli/src/reporter/gitlab.rs index 2948ddb3..9be7974e 100644 --- a/crates/pg_cli/src/reporter/gitlab.rs +++ b/crates/pg_cli/src/reporter/gitlab.rs @@ -57,7 +57,7 @@ impl<'a> GitLabReporterVisitor<'a> { } } -impl<'a> ReporterVisitor for GitLabReporterVisitor<'a> { +impl ReporterVisitor for GitLabReporterVisitor<'_> { fn report_summary(&mut self, _: &Execution, _: TraversalSummary) -> std::io::Result<()> { Ok(()) } @@ -80,7 +80,7 @@ struct GitLabDiagnostics<'a>( Option<&'a Path>, ); -impl<'a> GitLabDiagnostics<'a> { +impl GitLabDiagnostics<'_> { fn attempt_to_relativize(&self, subject: &str) -> Option { let Ok(resolved) = Path::new(subject).absolutize() else { return None; @@ -116,7 +116,7 @@ impl<'a> GitLabDiagnostics<'a> { } } -impl<'a> Display for GitLabDiagnostics<'a> { +impl Display for GitLabDiagnostics<'_> { fn fmt(&self, fmt: &mut Formatter) -> std::io::Result<()> { let mut hasher = self.1.write().unwrap(); let gitlab_diagnostics: Vec<_> = self diff --git a/crates/pg_cli/src/reporter/junit.rs b/crates/pg_cli/src/reporter/junit.rs index 358b5953..c10059fc 100644 --- a/crates/pg_cli/src/reporter/junit.rs +++ b/crates/pg_cli/src/reporter/junit.rs @@ -24,7 +24,7 @@ struct JunitDiagnostic<'a> { diagnostic: &'a Error, } -impl<'a> Display for JunitDiagnostic<'a> { +impl Display for JunitDiagnostic<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.diagnostic.description(f) } @@ -39,7 +39,7 @@ impl<'a> JunitReporterVisitor<'a> { } } -impl<'a> ReporterVisitor for JunitReporterVisitor<'a> { +impl ReporterVisitor for JunitReporterVisitor<'_> { fn report_summary( &mut self, _execution: &Execution, diff --git a/crates/pg_cli/src/reporter/terminal.rs b/crates/pg_cli/src/reporter/terminal.rs index c2802a7c..5fd493a2 100644 --- a/crates/pg_cli/src/reporter/terminal.rs +++ b/crates/pg_cli/src/reporter/terminal.rs @@ -53,7 +53,7 @@ struct FixedPathsDiagnostic { pub(crate) struct ConsoleReporterVisitor<'a>(pub(crate) &'a mut dyn Console); -impl<'a> ReporterVisitor for ConsoleReporterVisitor<'a> { +impl ReporterVisitor for ConsoleReporterVisitor<'_> { fn report_summary( &mut self, execution: &Execution, @@ -132,7 +132,7 @@ impl fmt::Display for Files { struct SummaryDetail<'a>(pub(crate) &'a TraversalMode, usize); -impl<'a> fmt::Display for SummaryDetail<'a> { +impl fmt::Display for SummaryDetail<'_> { fn fmt(&self, fmt: &mut Formatter) -> io::Result<()> { if self.1 > 0 { fmt.write_markup(markup! { @@ -147,7 +147,7 @@ impl<'a> fmt::Display for SummaryDetail<'a> { } struct SummaryTotal<'a>(&'a TraversalMode, usize, &'a Duration); -impl<'a> fmt::Display for SummaryTotal<'a> { +impl fmt::Display for SummaryTotal<'_> { fn fmt(&self, fmt: &mut Formatter) -> io::Result<()> { let files = Files(self.1); match self.0 { @@ -165,7 +165,7 @@ pub(crate) struct ConsoleTraversalSummary<'a>( pub(crate) &'a TraversalMode, pub(crate) &'a TraversalSummary, ); -impl<'a> fmt::Display for ConsoleTraversalSummary<'a> { +impl fmt::Display for ConsoleTraversalSummary<'_> { fn fmt(&self, fmt: &mut Formatter) -> io::Result<()> { let summary = SummaryTotal(self.0, self.1.changed + self.1.unchanged, &self.1.duration); let detail = SummaryDetail(self.0, self.1.changed); diff --git a/crates/pg_completions/Cargo.toml b/crates/pg_completions/Cargo.toml index 140ef910..0fee5ef0 100644 --- a/crates/pg_completions/Cargo.toml +++ b/crates/pg_completions/Cargo.toml @@ -16,6 +16,7 @@ async-std = "1.12.0" text-size.workspace = true + pg_schema_cache.workspace = true pg_treesitter_queries.workspace = true serde = { workspace = true, features = ["derive"] } diff --git a/crates/pg_completions/src/relevance.rs b/crates/pg_completions/src/relevance.rs index f7a42b16..5227e9bf 100644 --- a/crates/pg_completions/src/relevance.rs +++ b/crates/pg_completions/src/relevance.rs @@ -7,7 +7,7 @@ pub(crate) enum CompletionRelevanceData<'a> { Column(&'a pg_schema_cache::Column), } -impl<'a> CompletionRelevanceData<'a> { +impl CompletionRelevanceData<'_> { pub fn get_score(self, ctx: &CompletionContext) -> i32 { CompletionRelevance::from(self).into_score(ctx) } @@ -28,7 +28,7 @@ pub(crate) struct CompletionRelevance<'a> { data: CompletionRelevanceData<'a>, } -impl<'a> CompletionRelevance<'a> { +impl CompletionRelevance<'_> { pub fn into_score(mut self, ctx: &CompletionContext) -> i32 { self.check_matches_schema(ctx); self.check_matches_query_input(ctx); diff --git a/crates/pg_configuration/src/analyser/mod.rs b/crates/pg_configuration/src/analyser/mod.rs index 2273eff0..5dae9d19 100644 --- a/crates/pg_configuration/src/analyser/mod.rs +++ b/crates/pg_configuration/src/analyser/mod.rs @@ -362,7 +362,7 @@ impl serde::Serialize for RuleSelector { impl<'de> serde::Deserialize<'de> for RuleSelector { fn deserialize>(deserializer: D) -> Result { struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { + impl serde::de::Visitor<'_> for Visitor { type Value = RuleSelector; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("/") diff --git a/crates/pg_flags/src/lib.rs b/crates/pg_flags/src/lib.rs index 21b2a15c..864ea6a6 100644 --- a/crates/pg_flags/src/lib.rs +++ b/crates/pg_flags/src/lib.rs @@ -26,15 +26,15 @@ impl PgLspEnv { fn new() -> Self { Self { pglsp_log_path: PgLspEnvVariable::new( - "BIOME_LOG_PATH", + "PGLSP_LOG_PATH", "The directory where the Daemon logs will be saved.", ), pglsp_log_prefix: PgLspEnvVariable::new( - "BIOME_LOG_PREFIX_NAME", + "PGLSP_LOG_PREFIX_NAME", "A prefix that's added to the name of the log. Default: `server.log.`", ), pglsp_config_path: PgLspEnvVariable::new( - "BIOME_CONFIG_PATH", + "PGLSP_CONFIG_PATH", "A path to the configuration file", ), } diff --git a/crates/pg_lsp_new/src/handlers/text_document.rs b/crates/pg_lsp_new/src/handlers/text_document.rs index 98ecfc8a..173d7e99 100644 --- a/crates/pg_lsp_new/src/handlers/text_document.rs +++ b/crates/pg_lsp_new/src/handlers/text_document.rs @@ -5,7 +5,7 @@ use pg_workspace_new::workspace::{ ChangeFileParams, ChangeParams, CloseFileParams, GetFileContentParams, OpenFileParams, }; use tower_lsp::lsp_types; -use tracing::{error, field, info}; +use tracing::{error, field}; /// Handler for `textDocument/didOpen` LSP notification #[tracing::instrument( diff --git a/crates/pg_query_ext/src/lib.rs b/crates/pg_query_ext/src/lib.rs index 8cbbd2d7..433f881f 100644 --- a/crates/pg_query_ext/src/lib.rs +++ b/crates/pg_query_ext/src/lib.rs @@ -26,8 +26,7 @@ pub fn parse(sql: &str) -> Result { .nodes() .iter() .find(|n| n.1 == 1) - .unwrap() - .0 - .to_enum() - }) + .map(|n| n.0.to_enum()) + .ok_or_else(|| Error::Parse("Unable to find root node".to_string())) + })? } diff --git a/crates/pg_statement_splitter/src/lib.rs b/crates/pg_statement_splitter/src/lib.rs index ab4bafa8..242c2a26 100644 --- a/crates/pg_statement_splitter/src/lib.rs +++ b/crates/pg_statement_splitter/src/lib.rs @@ -56,6 +56,11 @@ mod tests { assert_eq!(*expected, self.input[*range].to_string()); } + assert!( + self.parse.ranges.is_sorted_by_key(|r| r.start()), + "Ranges are not sorted" + ); + self } diff --git a/crates/pg_statement_splitter/src/parser.rs b/crates/pg_statement_splitter/src/parser.rs index 33fcfaf7..25fe685e 100644 --- a/crates/pg_statement_splitter/src/parser.rs +++ b/crates/pg_statement_splitter/src/parser.rs @@ -186,6 +186,6 @@ impl Parser { } fn is_irrelevant_token(t: &Token) -> bool { - return WHITESPACE_TOKENS.contains(&t.kind) - && (t.kind != SyntaxKind::Newline || t.text.chars().count() == 1); + WHITESPACE_TOKENS.contains(&t.kind) + && (t.kind != SyntaxKind::Newline || t.text.chars().count() == 1) } diff --git a/crates/pg_syntax/src/statement_parser.rs b/crates/pg_syntax/src/statement_parser.rs index 40fcd641..56bba2c2 100644 --- a/crates/pg_syntax/src/statement_parser.rs +++ b/crates/pg_syntax/src/statement_parser.rs @@ -435,7 +435,7 @@ struct Ancestors<'a> { current_node: NodeIndex, } -impl<'a> Iterator for Ancestors<'a> { +impl Iterator for Ancestors<'_> { type Item = NodeIndex; fn next(&mut self) -> Option { diff --git a/crates/pg_type_resolver/src/types.rs b/crates/pg_type_resolver/src/types.rs index 76a0d1e8..8e8a0694 100644 --- a/crates/pg_type_resolver/src/types.rs +++ b/crates/pg_type_resolver/src/types.rs @@ -27,7 +27,7 @@ pub fn resolve_type(node: &pg_query_ext::NodeEnum, schema_cache: &SchemaCache) - .types .iter() .filter(|t| { - types.iter().any(|i| &i == &&t.name) && t.schema == "pg_catalog" + types.iter().any(|i| i == &t.name) && t.schema == "pg_catalog" }) .map(|t| t.id) .collect(), @@ -63,8 +63,7 @@ pub fn resolve_type(node: &pg_query_ext::NodeEnum, schema_cache: &SchemaCache) - .types .iter() .filter(|t| { - (types.iter().any(|i| &i == &&t.name) - && t.schema == "pg_catalog") + (types.iter().any(|i| i == &t.name) && t.schema == "pg_catalog") || t.enums.values.contains(&v.sval) }) .map(|t| t.id) diff --git a/crates/pg_workspace_new/src/dome.rs b/crates/pg_workspace_new/src/dome.rs index 8cc8ed5e..4d59837c 100644 --- a/crates/pg_workspace_new/src/dome.rs +++ b/crates/pg_workspace_new/src/dome.rs @@ -37,7 +37,7 @@ pub struct DomeIterator<'a> { impl<'a> DomeIterator<'a> { pub fn next_config(&mut self) -> Option<&'a PgLspPath> { - return if let Some(path) = self.iter.peek() { + if let Some(path) = self.iter.peek() { if path.is_config() { self.iter.next() } else { @@ -45,11 +45,11 @@ impl<'a> DomeIterator<'a> { } } else { None - }; + } } pub fn next_ignore(&mut self) -> Option<&'a PgLspPath> { - return if let Some(path) = self.iter.peek() { + if let Some(path) = self.iter.peek() { if path.is_ignore() { self.iter.next() } else { @@ -57,7 +57,7 @@ impl<'a> DomeIterator<'a> { } } else { None - }; + } } } @@ -69,4 +69,4 @@ impl<'a> Iterator for DomeIterator<'a> { } } -impl<'a> FusedIterator for DomeIterator<'a> {} +impl FusedIterator for DomeIterator<'_> {} diff --git a/crates/pg_workspace_new/src/matcher/pattern.rs b/crates/pg_workspace_new/src/matcher/pattern.rs index afbaae34..35c6c8cb 100644 --- a/crates/pg_workspace_new/src/matcher/pattern.rs +++ b/crates/pg_workspace_new/src/matcher/pattern.rs @@ -149,7 +149,7 @@ impl Pattern { tokens.push(AnyRecursiveSequence); } else { // A pattern is absolute if it starts with a path separator, eg. "/home" or "\\?\C:\Users" - let mut is_absolute = chars.first().map_or(false, |c| path::is_separator(*c)); + let mut is_absolute = chars.first().is_some_and(|c| path::is_separator(*c)); // On windows a pattern may also be absolute if it starts with a // drive letter, a colon and a separator, eg. "c:/Users" or "G:\Users" @@ -368,7 +368,7 @@ impl Pattern { /// `Pattern` using the default match options (i.e. `MatchOptions::new()`). pub fn matches_path(&self, path: &Path) -> bool { // FIXME (#9639): This needs to handle non-utf8 paths - path.to_str().map_or(false, |s| self.matches(s)) + path.to_str().is_some_and(|s| self.matches(s)) } /// Return if the given `str` matches this `Pattern` using the specified @@ -381,8 +381,7 @@ impl Pattern { /// `Pattern` using the specified match options. pub fn matches_path_with(&self, path: &Path, options: MatchOptions) -> bool { // FIXME (#9639): This needs to handle non-utf8 paths - path.to_str() - .map_or(false, |s| self.matches_with(s, options)) + path.to_str().is_some_and(|s| self.matches_with(s, options)) } /// Access the original glob pattern. @@ -551,7 +550,7 @@ fn chars_eq(a: char, b: char, case_sensitive: bool) -> bool { true } else if !case_sensitive && a.is_ascii() && b.is_ascii() { // FIXME: work with non-ascii chars properly (issue #9084) - a.to_ascii_lowercase() == b.to_ascii_lowercase() + a.eq_ignore_ascii_case(&b) } else { a == b } diff --git a/crates/pg_workspace_new/src/settings.rs b/crates/pg_workspace_new/src/settings.rs index 1950ef8d..376a4aa6 100644 --- a/crates/pg_workspace_new/src/settings.rs +++ b/crates/pg_workspace_new/src/settings.rs @@ -48,7 +48,7 @@ impl<'a> SettingsHandle<'a> { } } -impl<'a> AsRef for SettingsHandle<'a> { +impl AsRef for SettingsHandle<'_> { fn as_ref(&self) -> &Settings { &self.inner } @@ -62,7 +62,7 @@ impl<'a> SettingsHandleMut<'a> { } } -impl<'a> AsMut for SettingsHandleMut<'a> { +impl AsMut for SettingsHandleMut<'_> { fn as_mut(&mut self) -> &mut Settings { &mut self.inner } diff --git a/crates/pg_workspace_new/src/workspace.rs b/crates/pg_workspace_new/src/workspace.rs index 8a5e2a5c..cbfd3756 100644 --- a/crates/pg_workspace_new/src/workspace.rs +++ b/crates/pg_workspace_new/src/workspace.rs @@ -286,7 +286,7 @@ impl<'app, W: Workspace + ?Sized> FileGuard<'app, W> { // } } -impl<'app, W: Workspace + ?Sized> Drop for FileGuard<'app, W> { +impl Drop for FileGuard<'_, W> { fn drop(&mut self) { self.workspace .close_file(CloseFileParams { diff --git a/crates/pg_workspace_new/src/workspace/server.rs b/crates/pg_workspace_new/src/workspace/server.rs index 37ec4897..fd7f4416 100644 --- a/crates/pg_workspace_new/src/workspace/server.rs +++ b/crates/pg_workspace_new/src/workspace/server.rs @@ -3,7 +3,7 @@ use std::{fs, future::Future, panic::RefUnwindSafe, path::Path, sync::RwLock}; use analyser::AnalyserVisitorBuilder; use change::StatementChange; use dashmap::{DashMap, DashSet}; -use document::{Document, StatementRef}; +use document::{Document, Statement}; use pg_analyse::{AnalyserOptions, AnalysisFilter}; use pg_analyser::{Analyser, AnalyserConfig, AnalyserContext}; use pg_diagnostics::{serde::Diagnostic as SDiagnostic, Diagnostic, DiagnosticExt, Severity}; @@ -12,7 +12,6 @@ use pg_query::PgQueryStore; use pg_schema_cache::SchemaCache; use sqlx::PgPool; use std::sync::LazyLock; -use store::Store; use tokio::runtime::Runtime; use tracing::info; use tree_sitter::TreeSitterStore; @@ -33,7 +32,6 @@ mod analyser; mod change; mod document; mod pg_query; -mod store; mod tree_sitter; /// Simple helper to manage the db connection and the associated connection string @@ -78,7 +76,7 @@ pub(super) struct WorkspaceServer { pg_query: PgQueryStore, /// Stores the statements that have changed since the last analysis - changed_stmts: DashSet, + changed_stmts: DashSet, connection: RwLock, } @@ -220,10 +218,9 @@ impl Workspace for WorkspaceServer { let doc = Document::new(params.path.clone(), params.content, params.version); - doc.statements.iter().for_each(|s| { - let stmt = doc.statement(s); - self.tree_sitter.add_statement(&stmt); - self.pg_query.add_statement(&stmt); + doc.iter_statements_with_text().for_each(|(stmt, content)| { + self.tree_sitter.add_statement(&stmt, content); + self.pg_query.add_statement(&stmt, content); }); self.documents.insert(params.path, doc); @@ -238,8 +235,9 @@ impl Workspace for WorkspaceServer { .remove(¶ms.path) .ok_or_else(WorkspaceError::not_found)?; - for stmt in doc.statement_refs() { + for stmt in doc.iter_statements() { self.tree_sitter.remove_statement(&stmt); + self.pg_query.remove_statement(&stmt); } Ok(()) @@ -260,12 +258,12 @@ impl Workspace for WorkspaceServer { for c in &doc.apply_file_change(¶ms) { match c { - StatementChange::Added(s) => { - tracing::info!("Adding statement: {:?}", s); - self.tree_sitter.add_statement(s); - self.pg_query.add_statement(s); + StatementChange::Added(added) => { + tracing::info!("Adding statement: {:?}", added); + self.tree_sitter.add_statement(&added.stmt, &added.text); + self.pg_query.add_statement(&added.stmt, &added.text); - self.changed_stmts.insert(s.ref_.to_owned()); + self.changed_stmts.insert(added.stmt.clone()); } StatementChange::Deleted(s) => { tracing::info!("Deleting statement: {:?}", s); @@ -279,8 +277,8 @@ impl Workspace for WorkspaceServer { self.tree_sitter.modify_statement(s); self.pg_query.modify_statement(s); - self.changed_stmts.remove(&s.old.ref_); - self.changed_stmts.insert(s.new_ref.to_owned()); + self.changed_stmts.remove(&s.old_stmt); + self.changed_stmts.insert(s.new_stmt.clone()); } } } @@ -339,13 +337,12 @@ impl Workspace for WorkspaceServer { }); let diagnostics: Vec = doc - .statement_refs_with_ranges() - .iter() + .iter_statements_with_range() .flat_map(|(stmt, r)| { let mut stmt_diagnostics = vec![]; - stmt_diagnostics.extend(self.pg_query.diagnostics(stmt)); - let ast = self.pg_query.load(stmt); + stmt_diagnostics.extend(self.pg_query.get_diagnostics(&stmt)); + let ast = self.pg_query.get_ast(&stmt); if let Some(ast) = ast { stmt_diagnostics.extend( analyser @@ -402,6 +399,12 @@ impl Workspace for WorkspaceServer { &self, params: super::CompletionParams, ) -> Result { + tracing::debug!( + "Getting completions for file {:?} at position {:?}", + ¶ms.path, + ¶ms.position + ); + let doc = self .documents .get(¶ms.path) @@ -413,20 +416,19 @@ impl Workspace for WorkspaceServer { ¶ms.position ); - let statement = match doc.statement_at_offset(¶ms.position) { + let (statement, stmt_range, text) = match doc + .iter_statements_with_text_and_range() + .find(|(_, r, _)| r.contains(params.position)) + { Some(s) => s, None => return Ok(pg_completions::CompletionResult::default()), }; // `offset` is the position in the document, // but we need the position within the *statement*. - let stmt_range = doc - .statement_range(&statement.ref_) - .expect("Range of statement should be defined."); let position = params.position - stmt_range.start(); - let tree = self.tree_sitter.load(&statement.ref_); - let text = statement.text; + let tree = self.tree_sitter.get_parse_tree(&statement); tracing::debug!("Found the statement. We're looking for position {:?}. Statement Range {:?} to {:?}. Statement: {}", position, stmt_range.start(), stmt_range.end(), text); @@ -439,7 +441,7 @@ impl Workspace for WorkspaceServer { position, schema: &schema_cache, tree: tree.as_deref(), - text, + text: text.to_string(), }); Ok(result) diff --git a/crates/pg_workspace_new/src/workspace/server/analyser.rs b/crates/pg_workspace_new/src/workspace/server/analyser.rs index 7f6aa443..e0ad4c2f 100644 --- a/crates/pg_workspace_new/src/workspace/server/analyser.rs +++ b/crates/pg_workspace_new/src/workspace/server/analyser.rs @@ -99,7 +99,7 @@ impl<'a, 'b> LintVisitor<'a, 'b> { } } -impl<'a, 'b> RegistryVisitor for LintVisitor<'a, 'b> { +impl RegistryVisitor for LintVisitor<'_, '_> { fn record_category(&mut self) { if C::CATEGORY == RuleCategory::Lint { C::record_groups(self) diff --git a/crates/pg_workspace_new/src/workspace/server/change.rs b/crates/pg_workspace_new/src/workspace/server/change.rs index 4e8c0405..ccf9b35b 100644 --- a/crates/pg_workspace_new/src/workspace/server/change.rs +++ b/crates/pg_workspace_new/src/workspace/server/change.rs @@ -3,45 +3,60 @@ use text_size::{TextLen, TextRange, TextSize}; use crate::workspace::{ChangeFileParams, ChangeParams}; -use super::{document::Statement, Document, StatementRef}; +use super::{Document, Statement}; #[derive(Debug, PartialEq, Eq)] pub enum StatementChange { - Added(Statement), - Deleted(StatementRef), - Modified(ChangedStatement), + Added(AddedStatement), + Deleted(Statement), + Modified(ModifiedStatement), } #[derive(Debug, PartialEq, Eq)] -pub struct ChangedStatement { - pub old: Statement, - pub new_ref: StatementRef, - - pub range: TextRange, +pub struct AddedStatement { + pub stmt: Statement, pub text: String, } -impl ChangedStatement { - pub fn new_statement(&self) -> Statement { - Statement { - ref_: self.new_ref.clone(), - text: apply_text_change(&self.old.text, Some(self.range), &self.text), - } - } +#[derive(Debug, PartialEq, Eq)] +pub struct ModifiedStatement { + pub old_stmt: Statement, + pub old_stmt_text: String, + + pub new_stmt: Statement, + pub new_stmt_text: String, + + pub change_range: TextRange, + pub change_text: String, } impl StatementChange { #[allow(dead_code)] - pub fn statement_ref(&self) -> &StatementRef { + pub fn statement(&self) -> &Statement { match self { - StatementChange::Added(stmt) => &stmt.ref_, - StatementChange::Deleted(ref_) => ref_, - StatementChange::Modified(changed) => &changed.new_ref, + StatementChange::Added(stmt) => &stmt.stmt, + StatementChange::Deleted(stmt) => stmt, + StatementChange::Modified(changed) => &changed.new_stmt, } } } +/// Returns all relevant details about the change and its effects on the current state of the document. +struct Affected { + /// Full range of the change, including the range of all statements that intersect with the change + affected_range: TextRange, + /// All indices of affected statement positions + affected_indices: Vec, + /// The index of the first statement position before the change, if any + prev_index: Option, + /// The index of the first statement position after the change, if any + next_index: Option, + /// the full affected range includng the prev and next statement + full_affected_range: TextRange, +} + impl Document { + /// Applies a file change to the document and returns the affected statements pub fn apply_file_change(&mut self, change: &ChangeFileParams) -> Vec { let changes = change .changes @@ -54,235 +69,278 @@ impl Document { changes } - fn apply_change(&mut self, change: &ChangeParams) -> Vec { - self.debug_statements(); + /// Applies a full change to the document and returns the affected statements + fn apply_full_change(&mut self, text: &str) -> Vec { + let mut changes = Vec::new(); - let mut changed: Vec = Vec::with_capacity(self.statements.len()); + changes.extend(self.positions.drain(..).map(|(id, _)| { + StatementChange::Deleted(Statement { + id, + path: self.path.clone(), + }) + })); - tracing::info!("applying change: {:?}", change); + self.content = text.to_string(); - if change.range.is_none() { - // apply full text change and return early - changed.extend( - self.statements - .drain(..) - .map(|(id, _)| { - StatementChange::Deleted(StatementRef { - id, + changes.extend( + pg_statement_splitter::split(&self.content) + .ranges + .into_iter() + .map(|range| { + let id = self.id_generator.next(); + let text = self.content[range].to_string(); + self.positions.push((id, range)); + + StatementChange::Added(AddedStatement { + stmt: Statement { path: self.path.clone(), - }) + id, + }, + text, }) - .collect::>(), - ); - - self.content = change.text.clone(); - - for (id, range) in pg_statement_splitter::split(&self.content) - .ranges - .iter() - .map(|r| (self.id_generator.next(), *r)) - { - self.statements.push((id, range)); - changed.push(StatementChange::Added(Statement { - ref_: StatementRef { - path: self.path.clone(), - id, - }, - text: self.content[range].to_string(), - })) - } + }), + ); - return changed; - } + changes + } - // no matter where the change is, we can never be sure if its a modification or a deletion/addition - // e.g. if a statement is "select 1", and the change is "select 2; select 2", its an addition even though its in the middle of the statement. - // hence we only have three "real" cases: - // 1. the change touches no statement at all (addition) - // 2. the change touches exactly one statement AND splitting the statement results in just - // one statement (modification) - // 3. the change touches more than one statement (addition/deletion) + fn insert_statement(&mut self, range: TextRange) -> usize { + let pos = self + .positions + .binary_search_by(|(_, r)| r.start().cmp(&range.start())) + .unwrap_err(); - let new_content = change.apply_to_text(&self.content); + let new_id = self.id_generator.next(); + self.positions.insert(pos, (new_id, range)); - let mut affected = vec![]; + new_id + } - for (idx, (id, r)) in self.statements.iter_mut().enumerate() { - if r.intersect(change.range.unwrap()).is_some() { - affected.push((idx, (*id, *r))); - } else if r.start() > change.range.unwrap().end() { - if change.is_addition() { - *r += change.diff_size(); - } else if change.is_deletion() { - *r -= change.diff_size(); - } + /// Returns all relevant details about the change and its effects on the current state of the document. + /// - The affected range is the full range of the change, including the range of all statements that intersect with the change + /// - All indices of affected statement positions + /// - The index of the first statement position before the change, if any + /// - The index of the first statement position after the change, if any + /// - the full affected range includng the prev and next statement + fn get_affected( + &self, + change_range: TextRange, + content_size: TextSize, + diff_size: TextSize, + is_addition: bool, + ) -> Affected { + let mut start = change_range.start(); + let mut end = change_range.end().min(content_size); + + let mut affected_indices = Vec::new(); + let mut prev_index = None; + let mut next_index = None; + + for (index, (_, pos_range)) in self.positions.iter().enumerate() { + if pos_range.intersect(change_range).is_some() { + affected_indices.push(index); + start = start.min(pos_range.start()); + end = end.max(pos_range.end()); + } else if pos_range.end() <= change_range.start() { + prev_index = Some(index); + } else if pos_range.start() >= change_range.end() && next_index.is_none() { + next_index = Some(index); + break; } } - // special case: if no statement is affected, the affected range is between the prev and - // the next statement - if affected.is_empty() { - let start = self - .statements - .iter() - .rev() - .find(|(_, r)| r.end() <= change.range.unwrap().start()) - .map(|(_, r)| r.end()) - .unwrap_or(TextSize::new(0)); - let end = self - .statements - .iter() - .find(|(_, r)| r.start() >= change.range.unwrap().end()) - .map(|(_, r)| r.start()) - .unwrap_or_else(|| self.content.text_len()); + let start_incl = prev_index + .map(|i| self.positions[i].1.start()) + .unwrap_or(start); + let end_incl = next_index + .map(|i| self.positions[i].1.end()) + .unwrap_or_else(|| end); - let affected = new_content - .as_str() - .get(usize::from(start)..usize::from(end)) - .unwrap(); + let end_incl = if is_addition { + end_incl.add(diff_size) + } else { + end_incl.sub(diff_size) + }; - // add new statements - for range in pg_statement_splitter::split(affected).ranges { - let doc_range = range + start; - match self - .statements - .binary_search_by(|(_, r)| r.start().cmp(&doc_range.start())) - { - Ok(_) => {} - Err(pos) => { - let new_id = self.id_generator.next(); - self.statements.insert(pos, (new_id, doc_range)); - changed.push(StatementChange::Added(Statement { - ref_: StatementRef { - path: self.path.clone(), - id: new_id, - }, - text: new_content[doc_range].to_string(), - })); - } - } - } + let end = if is_addition { + end.add(diff_size) } else { - // get full affected range - let mut start = change.range.unwrap().start(); - let mut end = change.range.unwrap().end(); + end.sub(diff_size) + }; - if end > new_content.text_len() { - end = new_content.text_len(); - } + Affected { + affected_range: TextRange::new(start, end.min(content_size)), + affected_indices, + prev_index, + next_index, + full_affected_range: TextRange::new(start_incl, end_incl.min(content_size)), + } + } - for (_, (_, r)) in &affected { - // adjust the range to the new content - let adjusted_start = if r.start() >= change.range.unwrap().end() { - r.start() + change.diff_size() - } else { - r.start() - }; - let adjusted_end = if r.end() >= change.range.unwrap().end() { - if change.is_addition() { - r.end() + change.diff_size() - } else { - r.end() - change.diff_size() - } + fn move_ranges(&mut self, offset: TextSize, diff_size: TextSize, is_addition: bool) { + self.positions + .iter_mut() + .skip_while(|(_, r)| offset > r.start()) + .for_each(|(_, range)| { + let new_range = if is_addition { + range.add(diff_size) } else { - r.end() + range.sub(diff_size) }; - if adjusted_start < start { - start = adjusted_start; - } - if adjusted_end > end && adjusted_end <= new_content.text_len() { - end = adjusted_end; - } - } + *range = new_range; + }); + } + /// Applies a single change to the document and returns the affected statements + fn apply_change(&mut self, change: &ChangeParams) -> Vec { + tracing::info!("applying change: {:?}", change); + + // if range is none, we have a full change + if change.range.is_none() { + return self.apply_full_change(&change.text); + } + + // i spent a relatively large amount of time thinking about how to handle range changes + // properly. there are quite a few edge cases to consider. I eventually skipped most of + // them, because the complexity is not worth the return for now. we might want to revisit + // this later though. + + let mut changed: Vec = Vec::with_capacity(self.positions.len()); + + let change_range = change.range.unwrap(); + let new_content = change.apply_to_text(&self.content); + + // we first need to determine the affected range and all affected statements, as well as + // the index of the prev and the next statement, if any. The full affected range is the + // affected range expanded to the start of the previous statement and the end of the next + let Affected { + affected_range, + affected_indices, + prev_index, + next_index, + full_affected_range, + } = self.get_affected( + change_range, + new_content.text_len(), + change.diff_size(), + change.is_addition(), + ); + + // if within a statement, we can modify it if the change results in also a single statement + if affected_indices.len() == 1 { let changed_content = new_content .as_str() - .get(usize::from(start)..usize::from(end)) + .get(usize::from(affected_range.start())..usize::from(affected_range.end())) .unwrap(); - let ranges = pg_statement_splitter::split(changed_content).ranges; + let new_ranges = pg_statement_splitter::split(changed_content).ranges; - if affected.len() == 1 && ranges.len() == 1 { - // from one to one, so we do a modification - let stmt = &affected[0]; - let new_stmt = &ranges[0]; + if new_ranges.len() == 1 { + if change.is_whitespace() { + self.move_ranges( + affected_range.end(), + change.diff_size(), + change.is_addition(), + ); - let new_id = self.id_generator.next(); - self.statements[stmt.0] = (new_id, new_stmt.add(start)); - - let changed_stmt = ChangedStatement { - old: self.statement(&stmt.1), - new_ref: self.statement_ref(&self.statements[stmt.0]), - // change must be relative to statement - range: change.range.unwrap().sub(stmt.1 .1.start()), - text: change.text.clone(), - }; + self.content = new_content; - changed.push(StatementChange::Modified(changed_stmt)); - } else { - // delete and add new ones - for (_, (id, r)) in &affected { - changed.push(StatementChange::Deleted(self.statement_ref(&(*id, *r)))); + return changed; } - // remove affected statements - self.statements - .retain(|(id, _)| !affected.iter().any(|(affected_id, _)| id == affected_id)); - - // add new statements - for range in ranges { - match self - .statements - .binary_search_by(|(_, r)| r.start().cmp(&range.start())) - { - Ok(_) => {} - Err(pos) => { - let new_id = self.id_generator.next(); - self.statements.insert(pos, (new_id, range)); - changed.push(StatementChange::Added(Statement { - ref_: StatementRef { - path: self.path.clone(), - id: new_id, - }, - text: new_content[range].to_string(), - })); - } - } - } - } - } + let affected_idx = affected_indices[0]; + let new_range = new_ranges[0].add(affected_range.start()); + let (old_id, old_range) = self.positions[affected_idx]; - self.content = new_content; + // move all statements after the afffected range + self.move_ranges(old_range.end(), change.diff_size(), change.is_addition()); - self.debug_statements(); + let new_id = self.id_generator.next(); + self.positions[affected_idx] = (new_id, new_range); - changed - } -} + changed.push(StatementChange::Modified(ModifiedStatement { + old_stmt: Statement { + id: old_id, + path: self.path.clone(), + }, + old_stmt_text: self.content[old_range].to_string(), -fn apply_text_change(text: &str, range: Option, change_text: &str) -> String { - if range.is_none() { - return change_text.to_string(); - } + new_stmt: Statement { + id: new_id, + path: self.path.clone(), + }, + new_stmt_text: changed_content[new_ranges[0]].to_string(), + // change must be relative to the statement + change_text: change.text.clone(), + change_range, + })); - let range = range.unwrap(); - let start = usize::from(range.start()); - let end = usize::from(range.end()); + self.content = new_content; - let mut new_text = String::new(); - new_text.push_str(&text[..start]); - new_text.push_str(change_text); - if end < text.len() { - new_text.push_str(&text[end..]); - } + return changed; + } + } + + // in any other case, parse the full affected range + let changed_content = new_content + .as_str() + .get(usize::from(full_affected_range.start())..usize::from(full_affected_range.end())) + .unwrap(); + + let new_ranges = pg_statement_splitter::split(changed_content).ranges; + + // delete and add new ones + if let Some(next_index) = next_index { + changed.push(StatementChange::Deleted(Statement { + id: self.positions[next_index].0, + path: self.path.clone(), + })); + self.positions.remove(next_index); + } + for idx in affected_indices.iter().rev() { + changed.push(StatementChange::Deleted(Statement { + id: self.positions[*idx].0, + path: self.path.clone(), + })); + self.positions.remove(*idx); + } + if let Some(prev_index) = prev_index { + changed.push(StatementChange::Deleted(Statement { + id: self.positions[prev_index].0, + path: self.path.clone(), + })); + self.positions.remove(prev_index); + } + + new_ranges.iter().for_each(|range| { + let actual_range = range.add(full_affected_range.start()); + let new_id = self.insert_statement(actual_range); + changed.push(StatementChange::Added(AddedStatement { + stmt: Statement { + id: new_id, + path: self.path.clone(), + }, + text: new_content[actual_range].to_string(), + })); + }); + + // move all statements after the afffected range + self.move_ranges( + full_affected_range.end(), + change.diff_size(), + change.is_addition(), + ); - new_text + self.content = new_content; + + changed + } } impl ChangeParams { pub fn is_whitespace(&self) -> bool { - self.text.chars().all(char::is_whitespace) + self.text.chars().count() > 0 && self.text.chars().all(char::is_whitespace) } pub fn diff_size(&self) -> TextSize { @@ -327,13 +385,30 @@ impl ChangeParams { #[cfg(test)] mod tests { - use text_size::{TextRange, TextSize}; + use super::*; + use text_size::TextRange; - use crate::workspace::{server::document::Statement, ChangeFileParams, ChangeParams}; + use crate::workspace::{ChangeFileParams, ChangeParams}; - use super::{super::StatementRef, Document, StatementChange}; use pg_fs::PgLspPath; + impl Document { + pub fn get_text(&self, idx: usize) -> String { + self.content[self.positions[idx].1.start().into()..self.positions[idx].1.end().into()] + .to_string() + } + } + + fn assert_document_integrity(d: &Document) { + let ranges = pg_statement_splitter::split(&d.content).ranges; + + assert!(ranges.len() == d.positions.len()); + + assert!(ranges + .iter() + .all(|r| { d.positions.iter().any(|(_, stmt_range)| stmt_range == r) })); + } + #[test] fn within_statements() { let path = PgLspPath::new("test.sql"); @@ -341,7 +416,7 @@ mod tests { let mut d = Document::new(PgLspPath::new("test.sql"), input.to_string(), 0); - assert_eq!(d.statements.len(), 2); + assert_eq!(d.positions.len(), 2); let change = ChangeFileParams { path: path.clone(), @@ -354,14 +429,110 @@ mod tests { let changed = d.apply_file_change(&change); - assert_eq!(changed.len(), 1); - assert!( - matches!(&changed[0], StatementChange::Added(Statement { ref_: _, text }) if text == "select 1;") + assert_eq!(changed.len(), 5); + assert_eq!( + changed + .iter() + .filter(|c| matches!(c, StatementChange::Deleted(_))) + .count(), + 2 + ); + assert_eq!( + changed + .iter() + .filter(|c| matches!(c, StatementChange::Added(_))) + .count(), + 3 ); assert_document_integrity(&d); } + #[test] + fn julians_sample() { + let path = PgLspPath::new("test.sql"); + let input = "select\n *\nfrom\n test;\n\nselect\n\nalter table test\n\ndrop column id;"; + let mut d = Document::new(path.clone(), input.to_string(), 0); + + assert_eq!(d.positions.len(), 4); + + let change1 = ChangeFileParams { + path: path.clone(), + version: 1, + changes: vec![ChangeParams { + text: " ".to_string(), + range: Some(TextRange::new(31.into(), 31.into())), + }], + }; + + let changed1 = d.apply_file_change(&change1); + assert_eq!( + changed1.len(), + 0, + "should not emit change if its only whitespace" + ); + assert_eq!( + d.content, + "select\n *\nfrom\n test;\n\nselect \n\nalter table test\n\ndrop column id;" + ); + assert_document_integrity(&d); + + // problem: this creates a new statement + let change2 = ChangeFileParams { + path: path.clone(), + version: 2, + changes: vec![ChangeParams { + text: ";".to_string(), + range: Some(TextRange::new(32.into(), 32.into())), + }], + }; + + let changed2 = d.apply_file_change(&change2); + assert_eq!(changed2.len(), 4); + assert_eq!( + changed2 + .iter() + .filter(|c| matches!(c, StatementChange::Deleted(_))) + .count(), + 2 + ); + assert_eq!( + changed2 + .iter() + .filter(|c| matches!(c, StatementChange::Added(_))) + .count(), + 2 + ); + assert_document_integrity(&d); + + let change3 = ChangeFileParams { + path: path.clone(), + version: 3, + changes: vec![ChangeParams { + text: "".to_string(), + range: Some(TextRange::new(32.into(), 33.into())), + }], + }; + + let changed3 = d.apply_file_change(&change3); + assert_eq!(changed3.len(), 1); + assert!(matches!(&changed3[0], StatementChange::Modified(_))); + assert_eq!( + d.content, + "select\n *\nfrom\n test;\n\nselect \n\nalter table test\n\ndrop column id;" + ); + match &changed3[0] { + StatementChange::Modified(changed) => { + assert_eq!(changed.old_stmt_text, "select ;"); + assert_eq!(changed.new_stmt_text, "select"); + assert_eq!(changed.change_text, ""); + assert_eq!(changed.change_range, TextRange::new(32.into(), 33.into())); + } + _ => panic!("expected modified statement"), + } + assert_document_integrity(&d); + } + #[test] fn across_statements() { let path = PgLspPath::new("test.sql"); @@ -369,7 +540,7 @@ mod tests { let mut d = Document::new(PgLspPath::new("test.sql"), input.to_string(), 0); - assert_eq!(d.statements.len(), 2); + assert_eq!(d.positions.len(), 2); let change = ChangeFileParams { path: path.clone(), @@ -385,40 +556,30 @@ mod tests { assert_eq!(changed.len(), 4); assert!(matches!( changed[0], - StatementChange::Deleted(StatementRef { id: 0, .. }) + StatementChange::Deleted(Statement { id: 1, .. }) )); assert!(matches!( changed[1], - StatementChange::Deleted(StatementRef { id: 1, .. }) + StatementChange::Deleted(Statement { id: 0, .. }) )); assert!( - matches!(&changed[2], StatementChange::Added(Statement { ref_: _, text }) if text == "select id,test from users;") + matches!(&changed[2], StatementChange::Added(AddedStatement { stmt: _, text }) if text == "select id,test from users;") ); assert!( - matches!(&changed[3], StatementChange::Added(Statement { ref_: _, text }) if text == "select 1;") + matches!(&changed[3], StatementChange::Added(AddedStatement { stmt: _, text }) if text == "select 1;") ); assert_document_integrity(&d); } - fn assert_document_integrity(d: &Document) { - let ranges = pg_statement_splitter::split(&d.content).ranges; - - assert!(ranges.len() == d.statements.len()); - - assert!(ranges - .iter() - .all(|r| { d.statements.iter().any(|(_, stmt_range)| stmt_range == r) })); - } - #[test] - fn append_to_statement() { + fn append_whitespace_to_statement() { let path = PgLspPath::new("test.sql"); let input = "select id"; let mut d = Document::new(PgLspPath::new("test.sql"), input.to_string(), 0); - assert_eq!(d.statements.len(), 1); + assert_eq!(d.positions.len(), 1); let change = ChangeFileParams { path: path.clone(), @@ -431,8 +592,7 @@ mod tests { let changed = d.apply_file_change(&change); - assert_eq!(changed.len(), 1); - matches!(changed[0], StatementChange::Modified(_)); + assert_eq!(changed.len(), 0); assert_document_integrity(&d); } @@ -444,7 +604,7 @@ mod tests { let mut d = Document::new(PgLspPath::new("test.sql"), input.to_string(), 0); - assert_eq!(d.statements.len(), 2); + assert_eq!(d.positions.len(), 2); let change = ChangeFileParams { path: path.clone(), @@ -461,22 +621,22 @@ mod tests { assert_eq!( changed[0], - StatementChange::Deleted(StatementRef { + StatementChange::Deleted(Statement { path: path.clone(), - id: 0 + id: 1 }) ); assert_eq!( changed[1], - StatementChange::Deleted(StatementRef { + StatementChange::Deleted(Statement { path: path.clone(), - id: 1 + id: 0 }) ); assert_eq!( changed[2], - StatementChange::Added(Statement { - ref_: StatementRef { + StatementChange::Added(AddedStatement { + stmt: Statement { path: path.clone(), id: 2 }, @@ -485,8 +645,8 @@ mod tests { ); assert_eq!( changed[3], - StatementChange::Added(Statement { - ref_: StatementRef { + StatementChange::Added(AddedStatement { + stmt: Statement { path: path.clone(), id: 3 }, @@ -495,18 +655,6 @@ mod tests { ); assert_eq!("select id,test from users\nselect 1;", d.content); - assert_eq!(d.statements.len(), 2); - - for r in &pg_statement_splitter::split(&d.content).ranges { - assert!( - d.statements.iter().any(|x| r == &x.1), - "should have stmt with range {:#?}", - r - ); - } - - assert_eq!(d.statements[0].1, TextRange::new(0.into(), 25.into())); - assert_eq!(d.statements[1].1, TextRange::new(26.into(), 35.into())); assert_document_integrity(&d); } @@ -518,24 +666,14 @@ mod tests { let mut d = Document::new(path.clone(), input.to_string(), 1); - assert_eq!(d.statements.len(), 2); - - let stmt_1_range = d.statements[0]; - let stmt_2_range = d.statements[1]; - - let update_text = " contacts;"; - - let update_range = TextRange::new(14.into(), 14.into()); - - let update_text_len = u32::try_from(update_text.chars().count()).unwrap(); - let update_addition = update_text_len - u32::from(update_range.len()); + assert_eq!(d.positions.len(), 2); let change = ChangeFileParams { path: path.clone(), version: 2, changes: vec![ChangeParams { - text: update_text.to_string(), - range: Some(update_range), + text: " contacts;".to_string(), + range: Some(TextRange::new(14.into(), 14.into())), }], }; @@ -549,20 +687,6 @@ mod tests { "select id from contacts;\nselect * from contacts;", d.content ); - assert_eq!(d.statements.len(), 2); - assert_eq!(d.statements[0].1.start(), stmt_1_range.1.start()); - assert_eq!( - u32::from(d.statements[0].1.end()), - u32::from(stmt_1_range.1.end()) + update_addition - ); - assert_eq!( - u32::from(d.statements[1].1.start()), - u32::from(stmt_2_range.1.start()) + update_addition - ); - assert_eq!( - u32::from(d.statements[1].1.end()), - u32::from(stmt_2_range.1.end()) + update_addition - ); assert_document_integrity(&d); } @@ -584,20 +708,14 @@ mod tests { doc.apply_file_change(&change); + assert_eq!(doc.get_text(0), "select 1;".to_string()); + assert_eq!(doc.get_text(1), "select 2;".to_string()); assert_eq!( - doc.statement(&doc.statements[0]).text, - "select 1;".to_string() - ); - assert_eq!( - doc.statement(&doc.statements[1]).text, - "select 2;".to_string() - ); - assert_eq!( - doc.statements[0].1, + doc.positions[0].1, TextRange::new(TextSize::new(0), TextSize::new(9)) ); assert_eq!( - doc.statements[1].1, + doc.positions[1].1, TextRange::new(TextSize::new(10), TextSize::new(19)) ); @@ -613,21 +731,15 @@ mod tests { doc.apply_file_change(&change_2); assert_eq!(doc.content, "select ;\nselect 2;"); - assert_eq!(doc.statements.len(), 2); - assert_eq!( - doc.statement(&doc.statements[0]).text, - "select ;".to_string() - ); - assert_eq!( - doc.statement(&doc.statements[1]).text, - "select 2;".to_string() - ); + assert_eq!(doc.positions.len(), 2); + assert_eq!(doc.get_text(0), "select ;".to_string()); + assert_eq!(doc.get_text(1), "select 2;".to_string()); assert_eq!( - doc.statements[0].1, + doc.positions[0].1, TextRange::new(TextSize::new(0), TextSize::new(8)) ); assert_eq!( - doc.statements[1].1, + doc.positions[1].1, TextRange::new(TextSize::new(9), TextSize::new(18)) ); @@ -643,13 +755,13 @@ mod tests { doc.apply_file_change(&change_3); assert_eq!(doc.content, "select !;\nselect 2;"); - assert_eq!(doc.statements.len(), 2); + assert_eq!(doc.positions.len(), 2); assert_eq!( - doc.statements[0].1, + doc.positions[0].1, TextRange::new(TextSize::new(0), TextSize::new(9)) ); assert_eq!( - doc.statements[1].1, + doc.positions[1].1, TextRange::new(TextSize::new(10), TextSize::new(19)) ); @@ -665,13 +777,13 @@ mod tests { doc.apply_file_change(&change_4); assert_eq!(doc.content, "select ;\nselect 2;"); - assert_eq!(doc.statements.len(), 2); + assert_eq!(doc.positions.len(), 2); assert_eq!( - doc.statements[0].1, + doc.positions[0].1, TextRange::new(TextSize::new(0), TextSize::new(8)) ); assert_eq!( - doc.statements[1].1, + doc.positions[1].1, TextRange::new(TextSize::new(9), TextSize::new(18)) ); @@ -687,13 +799,13 @@ mod tests { doc.apply_file_change(&change_5); assert_eq!(doc.content, "select 1;\nselect 2;"); - assert_eq!(doc.statements.len(), 2); + assert_eq!(doc.positions.len(), 2); assert_eq!( - doc.statements[0].1, + doc.positions[0].1, TextRange::new(TextSize::new(0), TextSize::new(9)) ); assert_eq!( - doc.statements[1].1, + doc.positions[1].1, TextRange::new(TextSize::new(10), TextSize::new(19)) ); @@ -707,10 +819,10 @@ mod tests { let mut doc = Document::new(path.clone(), input.to_string(), 0); - assert_eq!(doc.statements.len(), 2); + assert_eq!(doc.positions.len(), 2); - let stmt_1_range = doc.statements[0]; - let stmt_2_range = doc.statements[1]; + let stmt_1_range = doc.positions[0]; + let stmt_2_range = doc.positions[1]; let update_text = ",test"; @@ -734,18 +846,18 @@ mod tests { "select id,test from users;\nselect * from contacts;", doc.content ); - assert_eq!(doc.statements.len(), 2); - assert_eq!(doc.statements[0].1.start(), stmt_1_range.1.start()); + assert_eq!(doc.positions.len(), 2); + assert_eq!(doc.positions[0].1.start(), stmt_1_range.1.start()); assert_eq!( - u32::from(doc.statements[0].1.end()), + u32::from(doc.positions[0].1.end()), u32::from(stmt_1_range.1.end()) + update_addition ); assert_eq!( - u32::from(doc.statements[1].1.start()), + u32::from(doc.positions[1].1.start()), u32::from(stmt_2_range.1.start()) + update_addition ); assert_eq!( - u32::from(doc.statements[1].1.end()), + u32::from(doc.positions[1].1.end()), u32::from(stmt_2_range.1.end()) + update_addition ); diff --git a/crates/pg_workspace_new/src/workspace/server/document.rs b/crates/pg_workspace_new/src/workspace/server/document.rs index 4422daad..7c8dba06 100644 --- a/crates/pg_workspace_new/src/workspace/server/document.rs +++ b/crates/pg_workspace_new/src/workspace/server/document.rs @@ -1,32 +1,25 @@ use pg_fs::PgLspPath; -use text_size::{TextRange, TextSize}; +use text_size::TextRange; /// Global unique identifier for a statement #[derive(Debug, Hash, Eq, PartialEq, Clone)] -pub(crate) struct StatementRef { +pub(crate) struct Statement { /// Path of the document pub(crate) path: PgLspPath, /// Unique id within the document pub(crate) id: StatementId, } -/// Represenation of a statement -#[derive(Debug, PartialEq, Eq)] -pub(crate) struct Statement { - pub(crate) ref_: StatementRef, - pub(crate) text: String, -} - pub type StatementId = usize; -type StatementPosition = (StatementId, TextRange); +type StatementPos = (StatementId, TextRange); pub(crate) struct Document { pub(crate) path: PgLspPath, pub(crate) content: String, pub(crate) version: i32, /// List of statements sorted by range.start() - pub(super) statements: Vec, + pub(super) positions: Vec, pub(super) id_generator: IdGenerator, } @@ -35,7 +28,7 @@ impl Document { pub(crate) fn new(path: PgLspPath, content: String, version: i32) -> Self { let mut id_generator = IdGenerator::new(); - let statements: Vec = pg_statement_splitter::split(&content) + let ranges: Vec = pg_statement_splitter::split(&content) .ranges .iter() .map(|r| (id_generator.next(), *r)) @@ -43,7 +36,7 @@ impl Document { Self { path, - statements, + positions: ranges, content, version, @@ -51,103 +44,48 @@ impl Document { } } - pub fn debug_statements(&self) { - for (id, range) in self.statements.iter() { - tracing::info!( - "Document::debug_statements: statement: id: {}, range: {:?}, text: {:?}", - id, - range, - &self.content[*range] - ); - } - } - - #[allow(dead_code)] - pub fn get_statements(&self) -> &[StatementPosition] { - &self.statements - } - - pub fn statement_refs(&self) -> Vec { - self.statements - .iter() - .map(|inner_ref| self.statement_ref(inner_ref)) - .collect() - } - - pub fn statement_refs_with_ranges(&self) -> Vec<(StatementRef, TextRange)> { - self.statements - .iter() - .map(|inner_ref| (self.statement_ref(inner_ref), inner_ref.1)) - .collect() - } - - #[allow(dead_code)] - /// Returns the statement ref at the given offset - pub fn statement_ref_at_offset(&self, offset: &TextSize) -> Option { - self.statements.iter().find_map(|r| { - if r.1.contains(*offset) { - Some(self.statement_ref(r)) - } else { - None - } + pub fn iter_statements(&self) -> impl Iterator + '_ { + self.positions.iter().map(move |(id, _)| Statement { + id: *id, + path: self.path.clone(), }) } - #[allow(dead_code)] - /// Returns the statement refs at the given range - pub fn statement_refs_at_range(&self, range: &TextRange) -> Vec { - self.statements - .iter() - .filter(|(_, r)| { - range.contains_range(r.to_owned().to_owned()) || r.contains_range(range.to_owned()) - }) - .map(|x| self.statement_ref(x)) - .collect() - } - - #[allow(dead_code)] - /// Returns the statement at the given offset - pub fn statement_at_offset(&self, offset: &TextSize) -> Option { - self.statements.iter().find_map(|r| { - if r.1.contains(*offset) { - Some(self.statement(r)) - } else { - None - } + pub fn iter_statements_with_text(&self) -> impl Iterator + '_ { + self.positions.iter().map(move |(id, range)| { + let statement = Statement { + id: *id, + path: self.path.clone(), + }; + let text = &self.content[range.start().into()..range.end().into()]; + (statement, text) }) } - #[allow(dead_code)] - /// Returns the statements at the given range - pub fn statements_at_range(&self, range: &TextRange) -> Vec { - self.statements - .iter() - .filter(|(_, r)| { - range.contains_range(r.to_owned().to_owned()) || r.contains_range(range.to_owned()) - }) - .map(|x| self.statement(x)) - .collect() - } - - pub(super) fn statement_ref(&self, inner_ref: &StatementPosition) -> StatementRef { - StatementRef { - id: inner_ref.0, - path: self.path.clone(), - } - } - - pub(super) fn statement_range(&self, sref: &StatementRef) -> Option { - self.statements - .iter() - .find(|s| s.0 == sref.id) - .map(|it| it.1) + pub fn iter_statements_with_range(&self) -> impl Iterator + '_ { + self.positions.iter().map(move |(id, range)| { + let statement = Statement { + id: *id, + path: self.path.clone(), + }; + (statement, range) + }) } - pub(super) fn statement(&self, inner_ref: &StatementPosition) -> Statement { - Statement { - ref_: self.statement_ref(inner_ref), - text: self.content[inner_ref.1].to_string(), - } + pub fn iter_statements_with_text_and_range( + &self, + ) -> impl Iterator + '_ { + self.positions.iter().map(move |(id, range)| { + let statement = Statement { + id: *id, + path: self.path.clone(), + }; + ( + statement, + range, + &self.content[range.start().into()..range.end().into()], + ) + }) } } diff --git a/crates/pg_workspace_new/src/workspace/server/pg_query.rs b/crates/pg_workspace_new/src/workspace/server/pg_query.rs index cbdf8bf0..e9ca77eb 100644 --- a/crates/pg_workspace_new/src/workspace/server/pg_query.rs +++ b/crates/pg_workspace_new/src/workspace/server/pg_query.rs @@ -4,15 +4,11 @@ use dashmap::DashMap; use pg_diagnostics::serde::Diagnostic as SDiagnostic; use pg_query_ext::diagnostics::*; -use super::{ - change::ChangedStatement, - document::{Statement, StatementRef}, - store::Store, -}; +use super::{change::ModifiedStatement, document::Statement}; pub struct PgQueryStore { - ast_db: DashMap>, - diagnostics: DashMap, + ast_db: DashMap>, + diagnostics: DashMap, } impl PgQueryStore { @@ -22,37 +18,33 @@ impl PgQueryStore { diagnostics: DashMap::new(), } } -} -impl Store for PgQueryStore { - fn load(&self, statement: &StatementRef) -> Option> { + pub fn get_ast(&self, statement: &Statement) -> Option> { self.ast_db.get(statement).map(|x| x.clone()) } - fn add_statement(&self, statement: &Statement) { - let r = pg_query_ext::parse(statement.text.as_str()); + pub fn add_statement(&self, statement: &Statement, content: &str) { + let r = pg_query_ext::parse(content); if let Ok(ast) = r { - self.ast_db.insert(statement.ref_.clone(), Arc::new(ast)); + self.ast_db.insert(statement.clone(), Arc::new(ast)); } else { tracing::info!("adding diagnostics"); - self.diagnostics.insert( - statement.ref_.clone(), - SyntaxDiagnostic::from(r.unwrap_err()), - ); + self.diagnostics + .insert(statement.clone(), SyntaxDiagnostic::from(r.unwrap_err())); } } - fn remove_statement(&self, statement: &StatementRef) { + pub fn remove_statement(&self, statement: &Statement) { self.ast_db.remove(statement); self.diagnostics.remove(statement); } - fn modify_statement(&self, change: &ChangedStatement) { - self.remove_statement(&change.old.ref_); - self.add_statement(&change.new_statement()); + pub fn modify_statement(&self, change: &ModifiedStatement) { + self.remove_statement(&change.old_stmt); + self.add_statement(&change.new_stmt, &change.new_stmt_text); } - fn diagnostics(&self, stmt: &StatementRef) -> Vec { + pub fn get_diagnostics(&self, stmt: &Statement) -> Vec { self.diagnostics .get(stmt) .map_or_else(Vec::new, |err| vec![SDiagnostic::new(err.value().clone())]) diff --git a/crates/pg_workspace_new/src/workspace/server/store.rs b/crates/pg_workspace_new/src/workspace/server/store.rs deleted file mode 100644 index 0891a974..00000000 --- a/crates/pg_workspace_new/src/workspace/server/store.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::sync::Arc; - -use super::{ - change::ChangedStatement, - document::{Statement, StatementRef}, -}; - -pub(crate) trait Store { - fn diagnostics(&self, _stmt: &StatementRef) -> Vec { - Vec::new() - } - - #[allow(dead_code)] - fn load(&self, _stmt: &StatementRef) -> Option> { - None - } - - fn add_statement(&self, _stmt: &Statement) {} - - fn remove_statement(&self, _stmt: &StatementRef) {} - - fn modify_statement(&self, _change: &ChangedStatement) {} -} diff --git a/crates/pg_workspace_new/src/workspace/server/tree_sitter.rs b/crates/pg_workspace_new/src/workspace/server/tree_sitter.rs index e0e8f120..ad4eb0d6 100644 --- a/crates/pg_workspace_new/src/workspace/server/tree_sitter.rs +++ b/crates/pg_workspace_new/src/workspace/server/tree_sitter.rs @@ -3,14 +3,10 @@ use std::sync::{Arc, RwLock}; use dashmap::DashMap; use tree_sitter::InputEdit; -use super::{ - change::ChangedStatement, - document::{Statement, StatementRef}, - store::Store, -}; +use super::{change::ModifiedStatement, document::Statement}; pub struct TreeSitterStore { - db: DashMap>, + db: DashMap>, parser: RwLock, } @@ -27,30 +23,28 @@ impl TreeSitterStore { parser: RwLock::new(parser), } } -} -impl Store for TreeSitterStore { - fn load(&self, statement: &StatementRef) -> Option> { + pub fn get_parse_tree(&self, statement: &Statement) -> Option> { self.db.get(statement).map(|x| x.clone()) } - fn add_statement(&self, statement: &Statement) { + pub fn add_statement(&self, statement: &Statement, content: &str) { let mut guard = self.parser.write().expect("Error reading parser"); // todo handle error - let tree = guard.parse(&statement.text, None).unwrap(); + let tree = guard.parse(content, None).unwrap(); drop(guard); - self.db.insert(statement.ref_.clone(), Arc::new(tree)); + self.db.insert(statement.clone(), Arc::new(tree)); } - fn remove_statement(&self, statement: &StatementRef) { + pub fn remove_statement(&self, statement: &Statement) { self.db.remove(statement); } - fn modify_statement(&self, change: &ChangedStatement) { - let old = self.db.remove(&change.old.ref_); + pub fn modify_statement(&self, change: &ModifiedStatement) { + let old = self.db.remove(&change.old_stmt); if old.is_none() { - self.add_statement(&change.new_statement()); + self.add_statement(&change.new_stmt, &change.change_text); return; } @@ -59,22 +53,19 @@ impl Store for TreeSitterStore { let mut tree = old.unwrap().1.as_ref().clone(); let edit = edit_from_change( - change.old.text.as_str(), - usize::from(change.range.start()), - usize::from(change.range.end()), - change.text.as_str(), + change.old_stmt_text.as_str(), + usize::from(change.change_range.start()), + usize::from(change.change_range.end()), + change.change_text.as_str(), ); tree.edit(&edit); - let new_stmt = change.new_statement(); - let new_text = new_stmt.text.clone(); - let mut guard = self.parser.write().expect("Error reading parser"); // todo handle error self.db.insert( - new_stmt.ref_, - Arc::new(guard.parse(new_text, Some(&tree)).unwrap()), + change.new_stmt.clone(), + Arc::new(guard.parse(&change.new_stmt_text, Some(&tree)).unwrap()), ); drop(guard); }