From 9a36d2c2a8c4c7c3932de59d1ec145faf3301f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Wed, 24 Mar 2021 18:17:00 +0900 Subject: [PATCH] wip: Hooks & trigger characters for completion/signature_help. --- helix-core/src/auto_pairs.rs | 26 ++++++--- helix-lsp/src/client.rs | 6 ++ helix-term/src/commands.rs | 105 ++++++++++++++++++++++++++++++++++- helix-term/src/keymap.rs | 2 + 4 files changed, 130 insertions(+), 9 deletions(-) diff --git a/helix-core/src/auto_pairs.rs b/helix-core/src/auto_pairs.rs index 7bb87e14..cd052489 100644 --- a/helix-core/src/auto_pairs.rs +++ b/helix-core/src/auto_pairs.rs @@ -28,13 +28,11 @@ const CLOSE_BEFORE: &str = ")]}'\":;> \n"; // includes space and newline pub fn hook(doc: &Rope, selection: &Selection, ch: char) -> Option { for &(open, close) in PAIRS { if open == ch { - let t = if open == close { - return None; - // handle_same() + if open == close { + return handle_same(doc, selection, open); } else { - handle_open(doc, selection, open, close, CLOSE_BEFORE) - }; - return Some(t); + return Some(handle_open(doc, selection, open, close, CLOSE_BEFORE)); + } } if close == ch { @@ -115,7 +113,21 @@ fn handle_close(doc: &Rope, selection: &Selection, _open: char, close: char) -> } // handle cases where open and close is the same, or in triples ("""docstring""") -fn handle_same() { +fn handle_same(doc: &Rope, selection: &Selection, token: char) -> Option { // if not cursor but selection, wrap // let next = next char + + // if next == bracket { + // // if start of syntax node, insert token twice (new pair because node is complete) + // // elseif colsedBracketAt + // // is_triple == allow triple && next 3 is equal + // // cursor jump over + // } + //} else if allow_triple && followed by triple { + //} + //} else if next != word char && prev != bracket && prev != word char { + // // condition checks for cases like I' where you don't want I'' (or I'm) + // insert pair ("") + //} + None } diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 672247a9..7c9de90c 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -85,6 +85,12 @@ impl Client { } } + pub fn capabilities(&self) -> &lsp::ServerCapabilities { + self.capabilities + .as_ref() + .expect("language server not yet initialized!") + } + /// Execute a RPC request on the language server. pub async fn request(&self, params: R::Params) -> Result where diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 24fb1709..dd7de06e 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1122,14 +1122,106 @@ pub fn goto_reference(cx: &mut Context) { goto(cx, res); } +pub fn signature_help(cx: &mut Context) { + let doc = cx.doc(); + + let language_server = match doc.language_server() { + Some(language_server) => language_server, + None => return, + }; + + // TODO: blocking here is not ideal + let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection().cursor()); + + // TODO: handle fails + + let res = smol::block_on(language_server.text_document_signature_help(doc.identifier(), pos)) + .unwrap_or_default(); + + if let Some(signature_help) = res { + log::info!("{:?}", signature_help); + // signatures + // active_signature + // active_parameter + // render as: + + // signature + // ---------- + // doc + + // with active param highlighted + } +} + // NOTE: Transactions in this module get appended to history when we switch back to normal mode. pub mod insert { use super::*; pub type Hook = fn(&Rope, &Selection, char) -> Option; + pub type PostHook = fn(&mut Context, char); use helix_core::auto_pairs; const HOOKS: &[Hook] = &[auto_pairs::hook]; + fn completion(cx: &mut Context, ch: char) { + // if ch matches completion char, trigger completion + let doc = cx.doc(); + let language_server = match doc.language_server() { + Some(language_server) => language_server, + None => return, + }; + + let capabilities = language_server.capabilities(); + + if let lsp::ServerCapabilities { + completion_provider: + Some(lsp::CompletionOptions { + trigger_characters: Some(triggers), + .. + }), + .. + } = capabilities + { + // TODO: what if trigger is multiple chars long + let is_trigger = triggers.iter().any(|trigger| trigger.contains(ch)); + + if is_trigger { + super::completion(cx); + } + } + } + + // TODO: the pre-hook handles ( so post hook never gets called + fn signature_help(cx: &mut Context, ch: char) { + // if ch matches signature_help char, trigger + let doc = cx.doc(); + let language_server = match doc.language_server() { + Some(language_server) => language_server, + None => return, + }; + + let capabilities = language_server.capabilities(); + + if let lsp::ServerCapabilities { + signature_help_provider: + Some(lsp::SignatureHelpOptions { + trigger_characters: Some(triggers), + // TODO: retrigger_characters + .. + }), + .. + } = capabilities + { + // TODO: what if trigger is multiple chars long + let is_trigger = triggers.iter().any(|trigger| trigger.contains(ch)); + + if is_trigger { + super::signature_help(cx); + } + } + } + + const POST_HOOKS: &[PostHook] = &[completion, signature_help]; + // TODO: insert means add text just before cursor, on exit we should be on the last letter. pub fn insert_char(cx: &mut Context, c: char) { let doc = cx.doc(); @@ -1142,10 +1234,17 @@ pub mod insert { } } - let c = Tendril::from_char(c); - let transaction = Transaction::insert(doc.text(), doc.selection(), c); + let t = Tendril::from_char(c); + let transaction = Transaction::insert(doc.text(), doc.selection(), t); doc.apply(&transaction); + + // TODO: need a post insert hook too for certain triggers (autocomplete, signature help, etc) + // this could also generically look at Transaction, but it's a bit annoying to look at + // Operation instead of Change. + for hook in POST_HOOKS { + hook(cx, c); + } } pub fn insert_tab(cx: &mut Context) { @@ -1162,6 +1261,7 @@ pub mod insert { let doc = cx.doc(); let text = doc.text().slice(..); let transaction = Transaction::change_by_selection(doc.text(), doc.selection(), |range| { + // TODO: offset range.head by 1? when calculating? let indent_level = helix_core::indent::suggested_indent_for_pos(doc.syntax(), text, range.head, true); let indent = doc.indent_unit().repeat(indent_level); @@ -1514,6 +1614,7 @@ pub fn completion(cx: &mut Context) { // // or we could simply use doc.undo + apply when changing between options + // always present here let item = item.unwrap(); use helix_lsp::{lsp, util}; diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 824d469d..aa1f8cce 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -196,7 +196,9 @@ pub fn default() -> Keymaps { key!('[') => commands::expand_selection, key!('/') => commands::search, + // ? for search_reverse key!('n') => commands::search_next, + // N for search_prev key!('*') => commands::search_selection, key!('u') => commands::undo,