diff --git a/Cargo.lock b/Cargo.lock index c635265c..fd4c5689 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,7 +172,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn", + "syn 2.0.106", "which", ] @@ -203,6 +203,12 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + [[package]] name = "bytes" version = "1.10.1" @@ -284,10 +290,10 @@ version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -424,7 +430,7 @@ dependencies = [ "glob", "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -435,7 +441,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -488,7 +494,7 @@ checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -671,6 +677,12 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -1249,7 +1261,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -1290,6 +1302,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "papergrid" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b0f8def1f117e13c895f3eda65a7b5650688da29d6ad04635f61bc7b92eebd" +dependencies = [ + "bytecount", + "fnv", + "unicode-width 0.2.1", +] + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -1380,7 +1403,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.106", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] @@ -1408,7 +1453,7 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ - "heck", + "heck 0.5.0", "itertools 0.14.0", "log", "multimap", @@ -1418,7 +1463,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn", + "syn 2.0.106", "tempfile", ] @@ -1432,7 +1477,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -1683,7 +1728,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -1715,7 +1760,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -1889,6 +1934,7 @@ dependencies = [ "smol_str", "squawk-linter", "squawk-syntax", + "tabled", ] [[package]] @@ -1992,6 +2038,17 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.106" @@ -2017,7 +2074,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -2041,6 +2098,29 @@ dependencies = [ "libc", ] +[[package]] +name = "tabled" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6709222f3973137427ce50559cd564dc187a95b9cfe01613d2f4e93610e510a" +dependencies = [ + "papergrid", + "tabled_derive", +] + +[[package]] +name = "tabled_derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "931be476627d4c54070a1f3a9739ccbfec9b36b39815106a20cce2243bbcefe1" +dependencies = [ + "heck 0.4.1", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "tempfile" version = "3.21.0" @@ -2114,7 +2194,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -2125,7 +2205,7 @@ checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -2388,7 +2468,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.106", "wasm-bindgen-shared", ] @@ -2423,7 +2503,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2458,7 +2538,7 @@ checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -2818,7 +2898,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", "synstructure", ] @@ -2839,7 +2919,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", "synstructure", ] @@ -2873,5 +2953,5 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] diff --git a/Cargo.toml b/Cargo.toml index a7beeb8e..6444cd23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ xshell = "0.2.7" proc-macro2 = "1.0.95" snapbox = { version = "0.6.0", features = ["diff", "term-svg", "cmd"] } smallvec = "1.13.2" +tabled = "0.17.0" # local # we have to make the versions explicit otherwise `cargo publish` won't work diff --git a/crates/squawk_ide/Cargo.toml b/crates/squawk_ide/Cargo.toml index d2089594..2a6c74f9 100644 --- a/crates/squawk_ide/Cargo.toml +++ b/crates/squawk_ide/Cargo.toml @@ -27,6 +27,7 @@ smallvec.workspace = true [dev-dependencies] insta.workspace = true +tabled.workspace = true [lints] workspace = true diff --git a/crates/squawk_ide/src/code_actions.rs b/crates/squawk_ide/src/code_actions.rs index 8b1d511b..c3cf52f3 100644 --- a/crates/squawk_ide/src/code_actions.rs +++ b/crates/squawk_ide/src/code_actions.rs @@ -457,7 +457,7 @@ fn rewrite_cast_to_double_colon( let replacement = format!("{}::{}", expr_text, type_text); actions.push(CodeAction { - title: "Rewrite cast function".to_owned(), + title: "Rewrite as cast operator `::`".to_owned(), edits: vec![Edit::replace(cast_expr.syntax().text_range(), replacement)], kind: ActionKind::RefactorRewrite, }); @@ -484,7 +484,7 @@ fn rewrite_double_colon_to_cast( let replacement = format!("cast({} as {})", expr_text, type_text); actions.push(CodeAction { - title: "Rewrite as cast operator".to_owned(), + title: "Rewrite as cast function `cast()`".to_owned(), edits: vec![Edit::replace(cast_expr.syntax().text_range(), replacement)], kind: ActionKind::RefactorRewrite, }); diff --git a/crates/squawk_ide/src/completion.rs b/crates/squawk_ide/src/completion.rs new file mode 100644 index 00000000..2580537c --- /dev/null +++ b/crates/squawk_ide/src/completion.rs @@ -0,0 +1,143 @@ +use rowan::TextSize; +use squawk_syntax::ast::{self, AstNode}; + +use crate::tokens::is_string_or_comment; + +pub fn completion(file: &ast::SourceFile, offset: TextSize) -> Vec { + let Some(token) = file.syntax().token_at_offset(offset).right_biased() else { + // empty file + return top_level_completions(); + }; + + // We don't support completions inside comments since we don't have doc + // comments a la JSDoc. + // And we don't have string literal types so we bail out early for strings too. + if is_string_or_comment(token.kind()) { + return vec![]; + } + + top_level_completions() +} + +fn top_level_completions() -> Vec { + ["select", "table"] + .map(|x| CompletionItem::keyword(x.to_owned())) + .to_vec() +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CompletionItemKind { + Keyword, + Table, + Column, + Function, + Schema, + Type, + Snippet, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CompletionInsertTextFormat { + PlainText, + Snippet, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CompletionItem { + pub label: String, + pub kind: CompletionItemKind, + pub detail: Option, + pub insert_text: Option, + pub insert_text_format: Option, +} + +impl CompletionItem { + fn keyword(text: String) -> CompletionItem { + CompletionItem { + label: text, + kind: CompletionItemKind::Keyword, + detail: None, + insert_text: None, + insert_text_format: None, + } + } +} + +#[cfg(test)] +mod tests { + use super::completion; + use crate::test_utils::fixture; + use insta::assert_snapshot; + use squawk_syntax::ast; + use tabled::builder::Builder; + use tabled::settings::Style; + + fn completions(sql: &str) -> String { + let (offset, sql) = fixture(sql); + let parse = ast::SourceFile::parse(&sql); + let file = parse.tree(); + let items = completion(&file, offset); + assert!( + !items.is_empty(), + "No completions found. If this was intended, use `completions_not_found` instead." + ); + format_items(items) + } + + fn completions_not_found(sql: &str) { + let (offset, sql) = fixture(sql); + let parse = ast::SourceFile::parse(&sql); + let file = parse.tree(); + let items = completion(&file, offset); + assert_eq!( + items, + vec![], + "Completions found. If this was unintended, use `completions` instead." + ) + } + + fn format_items(items: Vec) -> String { + let mut rows: Vec> = items + .into_iter() + .map(|item| { + vec![ + item.label, + format!("{:?}", item.kind), + item.detail.unwrap_or_default(), + ] + }) + .collect(); + + rows.sort(); + + let mut builder = Builder::default(); + builder.push_record(["label", "kind", "detail"]); + for row in rows { + builder.push_record(row); + } + + let mut table = builder.build(); + table.with(Style::psql()); + table.to_string() + } + + #[test] + fn completion_at_start() { + assert_snapshot!(completions("$0"), @r" + label | kind | detail + --------+---------+-------- + select | Keyword | + table | Keyword | + "); + } + + #[test] + fn completion_in_string() { + completions_not_found("select '$0';"); + } + + #[test] + fn completion_in_comment() { + completions_not_found("-- $0 "); + } +} diff --git a/crates/squawk_ide/src/expand_selection.rs b/crates/squawk_ide/src/expand_selection.rs index c153074c..0b565ea7 100644 --- a/crates/squawk_ide/src/expand_selection.rs +++ b/crates/squawk_ide/src/expand_selection.rs @@ -33,6 +33,8 @@ use squawk_syntax::{ ast::{self, AstToken}, }; +use crate::tokens::is_string_or_comment; + const DELIMITED_LIST_KINDS: &[SyntaxKind] = &[ SyntaxKind::ALTER_OPTION_LIST, SyntaxKind::ARG_LIST, @@ -78,15 +80,6 @@ pub fn extend_selection(root: &SyntaxNode, range: TextRange) -> TextRange { } fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option { - let string_kinds = [ - SyntaxKind::COMMENT, - SyntaxKind::STRING, - SyntaxKind::BYTE_STRING, - SyntaxKind::BIT_STRING, - SyntaxKind::DOLLAR_QUOTED_STRING, - SyntaxKind::ESC_STRING, - ]; - if range.is_empty() { let offset = range.start(); let mut leaves = root.token_at_offset(offset); @@ -98,7 +91,7 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option return None, rowan::TokenAtOffset::Single(l) => { - if string_kinds.contains(&l.kind()) { + if is_string_or_comment(l.kind()) { extend_single_word_in_comment_or_string(&l, offset) .unwrap_or_else(|| l.text_range()) } else { diff --git a/crates/squawk_ide/src/lib.rs b/crates/squawk_ide/src/lib.rs index 56519b8b..19ed9650 100644 --- a/crates/squawk_ide/src/lib.rs +++ b/crates/squawk_ide/src/lib.rs @@ -2,6 +2,7 @@ mod binder; mod classify; pub mod code_actions; pub mod column_name; +pub mod completion; pub mod document_symbols; pub mod expand_selection; pub mod find_references; @@ -16,3 +17,4 @@ mod scope; mod symbols; #[cfg(test)] pub mod test_utils; +mod tokens; diff --git a/crates/squawk_ide/src/tokens.rs b/crates/squawk_ide/src/tokens.rs new file mode 100644 index 00000000..7edd3568 --- /dev/null +++ b/crates/squawk_ide/src/tokens.rs @@ -0,0 +1,13 @@ +use squawk_syntax::SyntaxKind; + +pub(crate) fn is_string_or_comment(kind: SyntaxKind) -> bool { + matches!( + kind, + SyntaxKind::COMMENT + | SyntaxKind::STRING + | SyntaxKind::BYTE_STRING + | SyntaxKind::BIT_STRING + | SyntaxKind::DOLLAR_QUOTED_STRING + | SyntaxKind::ESC_STRING + ) +} diff --git a/crates/squawk_parser/src/grammar.rs b/crates/squawk_parser/src/grammar.rs index ffdee05a..2369022e 100644 --- a/crates/squawk_parser/src/grammar.rs +++ b/crates/squawk_parser/src/grammar.rs @@ -4878,6 +4878,7 @@ const TARGET_FOLLOW: TokenSet = TokenSet::new(&[ R_BRACK, RETURNING_KW, SEMICOLON, + CREATE_KW, EOF, ]) .union(COMPOUND_SELECT_FIRST); diff --git a/crates/squawk_parser/tests/data/err/select.sql b/crates/squawk_parser/tests/data/err/select.sql index 3680659e..a5ec1574 100644 --- a/crates/squawk_parser/tests/data/err/select.sql +++ b/crates/squawk_parser/tests/data/err/select.sql @@ -64,5 +64,10 @@ select case when then x end; -- case missing else expr select case when 1 then 2 else end; +-- select without semi and trailing create table +select + +create table users (); + -- trailing comma at EOF select 1, diff --git a/crates/squawk_parser/tests/snapshots/tests__select_err.snap b/crates/squawk_parser/tests/snapshots/tests__select_err.snap index ce53c218..724bca13 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_err.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_err.snap @@ -685,6 +685,27 @@ SOURCE_FILE END_KW "end" SEMICOLON ";" WHITESPACE "\n\n" + COMMENT "-- select without semi and trailing create table" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE "\n\n" + CREATE_TABLE + CREATE_KW "create" + WHITESPACE " " + TABLE_KW "table" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME + IDENT "users" + WHITESPACE " " + TABLE_ARG_LIST + L_PAREN "(" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n\n" COMMENT "-- trailing comma at EOF" WHITESPACE "\n" SELECT @@ -811,7 +832,13 @@ error[syntax-error]: expected an expression, found END_KW ╭▸ 65 │ select case when 1 then 2 else end; ╰╴ ━ +error[syntax-error]: expected SEMICOLON + ╭▸ +68 │ select + │ ┏━━━━━━━┛ +69 │ ┃ + ╰╴┗━┛ error[syntax-error]: unexpected trailing comma ╭▸ -68 │ select 1, +73 │ select 1, ╰╴ ━ diff --git a/crates/squawk_server/src/lib.rs b/crates/squawk_server/src/lib.rs index 42dad503..cbf08643 100644 --- a/crates/squawk_server/src/lib.rs +++ b/crates/squawk_server/src/lib.rs @@ -4,25 +4,27 @@ use log::info; use lsp_server::{Connection, Message, Notification, Response}; use lsp_types::{ CodeAction, CodeActionKind, CodeActionOptions, CodeActionOrCommand, CodeActionParams, - CodeActionProviderCapability, CodeActionResponse, Command, Diagnostic, - DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, - DocumentSymbol, DocumentSymbolParams, GotoDefinitionParams, GotoDefinitionResponse, Hover, - HoverContents, HoverParams, HoverProviderCapability, InitializeParams, InlayHint, - InlayHintKind, InlayHintLabel, InlayHintLabelPart, InlayHintParams, LanguageString, Location, - MarkedString, OneOf, PublishDiagnosticsParams, ReferenceParams, SelectionRangeParams, - SelectionRangeProviderCapability, ServerCapabilities, SymbolKind, TextDocumentSyncCapability, - TextDocumentSyncKind, Url, WorkDoneProgressOptions, WorkspaceEdit, + CodeActionProviderCapability, CodeActionResponse, Command, CompletionOptions, CompletionParams, + CompletionResponse, Diagnostic, DidChangeTextDocumentParams, DidCloseTextDocumentParams, + DidOpenTextDocumentParams, DocumentSymbol, DocumentSymbolParams, GotoDefinitionParams, + GotoDefinitionResponse, Hover, HoverContents, HoverParams, HoverProviderCapability, + InitializeParams, InlayHint, InlayHintKind, InlayHintLabel, InlayHintLabelPart, + InlayHintParams, LanguageString, Location, MarkedString, OneOf, PublishDiagnosticsParams, + ReferenceParams, SelectionRangeParams, SelectionRangeProviderCapability, ServerCapabilities, + SymbolKind, TextDocumentSyncCapability, TextDocumentSyncKind, Url, WorkDoneProgressOptions, + WorkspaceEdit, notification::{ DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument, Notification as _, PublishDiagnostics, }, request::{ - CodeActionRequest, DocumentSymbolRequest, GotoDefinition, HoverRequest, InlayHintRequest, - References, Request, SelectionRangeRequest, + CodeActionRequest, Completion, DocumentSymbolRequest, GotoDefinition, HoverRequest, + InlayHintRequest, References, Request, SelectionRangeRequest, }, }; use rowan::TextRange; use squawk_ide::code_actions::code_actions; +use squawk_ide::completion::completion; use squawk_ide::document_symbols::{DocumentSymbolKind, document_symbols}; use squawk_ide::find_references::find_references; use squawk_ide::goto_definition::goto_definition; @@ -69,6 +71,15 @@ pub fn run() -> Result<()> { hover_provider: Some(HoverProviderCapability::Simple(true)), inlay_hint_provider: Some(OneOf::Left(true)), document_symbol_provider: Some(OneOf::Left(true)), + completion_provider: Some(CompletionOptions { + resolve_provider: Some(false), + trigger_characters: None, + all_commit_characters: None, + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + completion_item: None, + }), ..Default::default() }) .unwrap(); @@ -124,6 +135,9 @@ fn main_loop(connection: Connection, params: serde_json::Value) -> Result<()> { DocumentSymbolRequest::METHOD => { handle_document_symbol(&connection, req, &documents)?; } + Completion::METHOD => { + handle_completion(&connection, req, &documents)?; + } "squawk/syntaxTree" => { handle_syntax_tree(&connection, req, &documents)?; } @@ -482,6 +496,47 @@ fn handle_references( Ok(()) } +fn handle_completion( + connection: &Connection, + req: lsp_server::Request, + documents: &HashMap, +) -> Result<()> { + let params: CompletionParams = serde_json::from_value(req.params)?; + let uri = params.text_document_position.text_document.uri; + let position = params.text_document_position.position; + + let content = documents.get(&uri).map_or("", |doc| &doc.content); + let parse = SourceFile::parse(content); + let file = parse.tree(); + let line_index = LineIndex::new(content); + + let Some(offset) = lsp_utils::offset(&line_index, position) else { + let resp = Response { + id: req.id, + result: Some(serde_json::to_value(CompletionResponse::Array(vec![])).unwrap()), + error: None, + }; + connection.sender.send(Message::Response(resp))?; + return Ok(()); + }; + + let completion_items = completion(&file, offset) + .into_iter() + .map(lsp_utils::completion_item) + .collect(); + + let result = CompletionResponse::Array(completion_items); + + let resp = Response { + id: req.id, + result: Some(serde_json::to_value(&result).unwrap()), + error: None, + }; + + connection.sender.send(Message::Response(resp))?; + Ok(()) +} + fn handle_code_action( connection: &Connection, req: lsp_server::Request, diff --git a/crates/squawk_server/src/lsp_utils.rs b/crates/squawk_server/src/lsp_utils.rs index a65e48cb..28e0fdfe 100644 --- a/crates/squawk_server/src/lsp_utils.rs +++ b/crates/squawk_server/src/lsp_utils.rs @@ -75,6 +75,36 @@ pub(crate) fn code_action( } } +pub(crate) fn completion_item( + item: squawk_ide::completion::CompletionItem, +) -> lsp_types::CompletionItem { + use squawk_ide::completion::{CompletionInsertTextFormat, CompletionItemKind}; + + let kind = match item.kind { + CompletionItemKind::Keyword => lsp_types::CompletionItemKind::KEYWORD, + CompletionItemKind::Table => lsp_types::CompletionItemKind::STRUCT, + CompletionItemKind::Column => lsp_types::CompletionItemKind::FIELD, + CompletionItemKind::Function => lsp_types::CompletionItemKind::FUNCTION, + CompletionItemKind::Schema => lsp_types::CompletionItemKind::MODULE, + CompletionItemKind::Type => lsp_types::CompletionItemKind::CLASS, + CompletionItemKind::Snippet => lsp_types::CompletionItemKind::SNIPPET, + }; + + let insert_text_format = item.insert_text_format.map(|x| match x { + CompletionInsertTextFormat::PlainText => lsp_types::InsertTextFormat::PLAIN_TEXT, + CompletionInsertTextFormat::Snippet => lsp_types::InsertTextFormat::SNIPPET, + }); + + lsp_types::CompletionItem { + label: item.label, + kind: Some(kind), + detail: item.detail, + insert_text: item.insert_text, + insert_text_format, + ..Default::default() + } +} + pub(crate) fn range(line_index: &LineIndex, range: TextRange) -> lsp_types::Range { let start = line_index.line_col(range.start()); let end = line_index.line_col(range.end());