Skip to content

feat(completions): basic scoring algorithm for tables #150

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
116 changes: 116 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ line_index = { path = "./lib/line_index", version = "0.0.0" }
tree_sitter_sql = { path = "./lib/tree_sitter_sql", version = "0.0.0" }
tree-sitter = "0.20.10"
tracing = "0.1.40"
tower-lsp = "0.20.0"
sqlx = { version = "0.8.2", features = [ "runtime-async-std", "tls-rustls", "postgres", "json" ] }

# postgres specific crates
Expand Down
15 changes: 15 additions & 0 deletions crates/pg_completions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Auto-Completions

## What does this crate do?

The `pg_completions` identifies and ranks autocompletion items that can be displayed in your code editor.
Its main export is the `complete` function. The function takes a PostgreSQL statement, a cursor position, and a datastructure representing the underlying databases schema. It returns a list of completion items.

Postgres's statement-parsing-engine, `libpg_query`, which is used in other parts of this LSP, is only capable of parsing _complete and valid_ statements. Since autocompletion should work for incomplete statements, we rely heavily on tree-sitter – an incremental parsing library.

### Working with TreeSitter

In the `pg_test_utils` crate, there's a binary that parses an SQL file and prints out the matching tree-sitter tree.
This makes writing tree-sitter queries for this crate easy.

To print a tree, run `cargo run --bin tree_print -- -f <your_sql_file>`.
57 changes: 39 additions & 18 deletions crates/pg_completions/src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,50 @@
use crate::{CompletionItem, CompletionResult};
use crate::{item::CompletionItem, CompletionResult};

pub struct CompletionBuilder<'a> {
pub items: Vec<CompletionItem<'a>>,
pub(crate) struct CompletionBuilder {
items: Vec<CompletionItem>,
}

pub struct CompletionConfig {}
impl CompletionBuilder {
pub fn new() -> Self {
CompletionBuilder { items: vec![] }
}

impl<'a> From<&'a CompletionConfig> for CompletionBuilder<'a> {
fn from(_config: &CompletionConfig) -> Self {
Self { items: Vec::new() }
pub fn add_item(&mut self, item: CompletionItem) {
self.items.push(item);
}
}

impl<'a> CompletionBuilder<'a> {
pub fn finish(mut self) -> CompletionResult<'a> {
self.items.sort_by(|a, b| {
b.preselect
.cmp(&a.preselect)
.then_with(|| b.score.cmp(&a.score))
.then_with(|| a.data.label().cmp(b.data.label()))
});
pub fn finish(mut self) -> CompletionResult {
self.items
.sort_by(|a, b| b.score.cmp(&a.score).then_with(|| a.label.cmp(&b.label)));

self.items.dedup_by(|a, b| a.data.label() == b.data.label());
self.items.dedup_by(|a, b| a.label == b.label);
self.items.truncate(crate::LIMIT);
let Self { items, .. } = self;

let should_preselect_first_item = self.should_preselect_first_item();

let items: Vec<CompletionItem> = self
.items
.into_iter()
.enumerate()
.map(|(idx, mut item)| {
if idx == 0 {
item.preselected = Some(should_preselect_first_item);
}
item.into()
})
.collect();

CompletionResult { items }
}

fn should_preselect_first_item(&mut self) -> bool {
let mut items_iter = self.items.iter();
let first = items_iter.next();
let second = items_iter.next();

first.is_some_and(|f| match second {
Some(s) => (f.score - s.score) > 10,
None => true,
})
}
}
Loading
Loading