@@ -10,7 +10,7 @@ use helix_core::{
10use once_cell::sync::Lazy;
10use once_cell::sync::Lazy;
12use crate::{
12use crate::{
13− compositor::{Callback, Compositor},
13+ compositor::{Callback, Component, Compositor},
14 ui::{self, Picker, Popup, Prompt, PromptEvent},
14 ui::{self, Picker, Popup, Prompt, PromptEvent},
@@ -26,14 +26,20 @@ use crossterm::event::{KeyCode, KeyEvent};
27use helix_lsp::lsp;
27use helix_lsp::lsp;
29+use crate::application::{LspCallbackWrapper, LspCallbacks};
29pub struct Context<'a> {
31pub struct Context<'a> {
30 pub count: usize,
32 pub count: usize,
31 pub editor: &'a mut Editor,
33 pub editor: &'a mut Editor,
33 pub callback: Option<crate::compositor::Callback>,
35 pub callback: Option<crate::compositor::Callback>,
34 pub on_next_key_callback: Option<Box<dyn FnOnce(&mut Context, KeyEvent)>>,
36 pub on_next_key_callback: Option<Box<dyn FnOnce(&mut Context, KeyEvent)>>,
37+ pub callbacks: &'a mut LspCallbacks,
40+use futures_util::FutureExt;
41+use std::future::Future;
37impl<'a> Context<'a> {
43impl<'a> Context<'a> {
39 pub fn view(&mut self) -> &mut View {
45 pub fn view(&mut self) -> &mut View {
@@ -47,7 +53,7 @@ impl<'a> Context<'a> {
49 /// Push a new component onto the compositor.
55 /// Push a new component onto the compositor.
50− pub fn push_layer(&mut self, mut component: Box<dyn crate::compositor::Component>) {
56+ pub fn push_layer(&mut self, mut component: Box<dyn Component>) {
51 self.callback = Some(Box::new(
57 self.callback = Some(Box::new(
52 |compositor: &mut Compositor, editor: &mut Editor| {
58 |compositor: &mut Compositor, editor: &mut Editor| {
53 let size = compositor.size();
59 let size = compositor.size();
@@ -65,6 +71,27 @@ impl<'a> Context<'a> {
66 self.on_next_key_callback = Some(Box::new(on_next_key_callback));
72 self.on_next_key_callback = Some(Box::new(on_next_key_callback));
76+ pub fn callback<T, F>(
78+ call: impl Future<Output = helix_lsp::Result<serde_json::Value>> + 'static + Send,
81+ T: for<'de> serde::Deserialize<'de> + Send + 'static,
82+ F: FnOnce(&mut Editor, &mut Compositor, T) + Send + 'static,
84+ let callback = Box::pin(async move {
85+ let json = call.await?;
86+ let response = serde_json::from_value(json)?;
87+ let call: LspCallbackWrapper =
88+ Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
89+ callback(editor, compositor, response)
93+ self.callbacks.push(callback);
70/// A command is a function that takes the current state and a count, and does a side-effect on the
97/// A command is a function that takes the current state and a count, and does a side-effect on the
@@ -1564,6 +1591,24 @@ pub fn save(cx: &mut Context) {
1566pub fn completion(cx: &mut Context) {
1593pub fn completion(cx: &mut Context) {
1594+ // trigger on trigger char, or if user calls it
1595+ // (or on word char typing??)
1596+ // after it's triggered, if response marked is_incomplete, update on every subsequent keypress
1598+ // lsp calls are done via a callback: it sends a request and doesn't block.
1599+ // when we get the response similarly to notification, trigger a call to the completion popup
1601+ // language_server.completion(params, |cx: &mut Context, _meta, response| {
1602+ // // called at response time
1603+ // // compositor, lookup completion layer
1604+ // // downcast dyn Component to Completion component
1605+ // // emit response to completion (completion.complete/handle(response))
1608+ // let (response, callback) = response.await?;
1609+ // callback(response)
1567 let doc = cx.doc();
1612 let doc = cx.doc();
1569 let language_server = match doc.language_server() {
1614 let language_server = match doc.language_server() {
@@ -1576,91 +1621,119 @@ pub fn completion(cx: &mut Context) {
1577 // TODO: handle fails
1622 // TODO: handle fails
1579− let res = smol::block_on(language_server.completion(doc.identifier(), pos)).unwrap_or_default();
1624+ let res = smol::block_on(language_server.completion(doc.identifier(), pos)).unwrap();
1628+ |editor: &mut Editor,
1629+ compositor: &mut Compositor,
1630+ response: Option<lsp::CompletionResponse>| {
1631+ let items = match response {
1632+ Some(lsp::CompletionResponse::Array(items)) => items,
1633+ // TODO: do something with is_incomplete
1634+ Some(lsp::CompletionResponse::List(lsp::CompletionList {
1635+ is_incomplete: _is_incomplete,
1638+ None => Vec::new(),
1581− // TODO: if no completion, show some message or something
1641+ // TODO: if no completion, show some message or something
1582− if !res.is_empty() {
1642+ if !items.is_empty() {
1583− // let snapshot = doc.state.clone();
1643+ // let snapshot = doc.state.clone();
1584− let mut menu = ui::Menu::new(
1644+ let mut menu = ui::Menu::new(
1586− |item| {
1646+ |item| {
1587− // format_fn
1647+ // format_fn
1588− item.label.as_str().into()
1648+ item.label.as_str().into()
1590− // TODO: use item.filter_text for filtering
1650+ // TODO: use item.filter_text for filtering
1592− move |editor: &mut Editor, item, event| {
1652+ move |editor: &mut Editor, item, event| {
1593− match event {
1653+ match event {
1594− PromptEvent::Abort => {
1654+ PromptEvent::Abort => {
1595− // revert state
1655+ // revert state
1596− // let id = editor.view().doc;
1656+ // let id = editor.view().doc;
1597− // let doc = &mut editor.documents[id];
1657+ // let doc = &mut editor.documents[id];
1598− // doc.state = snapshot.clone();
1658+ // doc.state = snapshot.clone();
1600− PromptEvent::Validate => {
1660+ PromptEvent::Validate => {
1601− let id = editor.view().doc;
1661+ let id = editor.view().doc;
1602− let doc = &mut editor.documents[id];
1662+ let doc = &mut editor.documents[id];
1604− // revert state to what it was before the last update
1664+ // revert state to what it was before the last update
1605− // doc.state = snapshot.clone();
1665+ // doc.state = snapshot.clone();
1607− // extract as fn(doc, item):
1667+ // extract as fn(doc, item):
1609− // TODO: need to apply without composing state...
1669+ // TODO: need to apply without composing state...
1610− // TODO: need to update lsp on accept/cancel by diffing the snapshot with
1670+ // TODO: need to update lsp on accept/cancel by diffing the snapshot with
1611− // the final state?
1671+ // the final state?
1612− // -> on update simply update the snapshot, then on accept redo the call,
1672+ // -> on update simply update the snapshot, then on accept redo the call,
1613− // finally updating doc.changes + notifying lsp.
1673+ // finally updating doc.changes + notifying lsp.
1615− // or we could simply use doc.undo + apply when changing between options
1675+ // or we could simply use doc.undo + apply when changing between options
1617− // always present here
1677+ // always present here
1618− let item = item.unwrap();
1678+ let item = item.unwrap();
1620− use helix_lsp::{lsp, util};
1680+ use helix_lsp::{lsp, util};
1621− // determine what to insert: text_edit | insert_text | label
1681+ // determine what to insert: text_edit | insert_text | label
1622− let edit = if let Some(edit) = &item.text_edit {
1682+ let edit = if let Some(edit) = &item.text_edit {
1623− match edit {
1683+ match edit {
1624− lsp::CompletionTextEdit::Edit(edit) => edit.clone(),
1684+ lsp::CompletionTextEdit::Edit(edit) => edit.clone(),
1625− lsp::CompletionTextEdit::InsertAndReplace(item) => {
1685+ lsp::CompletionTextEdit::InsertAndReplace(item) => {
1626− unimplemented!("completion: insert_and_replace {:?}", item)
1687+ "completion: insert_and_replace {:?}",
1693+ item.insert_text.as_ref().unwrap_or(&item.label);
1695+ // lsp::TextEdit::new(); TODO: calculate a TextEdit from insert_text
1696+ // and we insert at position.
1699+ // TODO: merge edit with additional_text_edits
1700+ if let Some(additional_edits) = &item.additional_text_edits {
1701+ if !additional_edits.is_empty() {
1703+ "completion: additional_text_edits: {:?}",
1710+ util::generate_transaction_from_edits(doc.text(), vec![edit]);
1711+ doc.apply(&transaction);
1712+ // TODO: doc.append_changes_to_history(); if not in insert mode?
1629− } else {
1714+ _ => (),
1630− item.insert_text.as_ref().unwrap_or(&item.label);
1632− // lsp::TextEdit::new(); TODO: calculate a TextEdit from insert_text
1633− // and we insert at position.
1636− // TODO: merge edit with additional_text_edits
1719+ let popup = Popup::new(Box::new(menu));
1637− if let Some(additional_edits) = &item.additional_text_edits {
1720+ let mut component: Box<dyn Component> = Box::new(popup);
1638− if !additional_edits.is_empty() {
1640− "completion: additional_text_edits: {:?}",
1646− // TODO: <-- if state has changed by further input, transaction will panic on len
1722+ // Server error: content modified
1648− util::generate_transaction_from_edits(doc.text(), vec![edit]);
1649− doc.apply(&transaction);
1650− // TODO: doc.append_changes_to_history(); if not in insert mode?
1657− let popup = Popup::new(Box::new(menu));
1658− cx.push_layer(Box::new(popup));
1724+ // TODO: this is shared with cx.push_layer
1725+ let size = compositor.size();
1726+ // trigger required_size on init
1727+ component.required_size((size.width, size.height));
1728+ compositor.push(component);
1660− // TODO!: when iterating over items, show the docs in popup
1733+ // // TODO!: when iterating over items, show the docs in popup
1662− // language server client needs to be accessible via a registry of some sort
1735+ // // language server client needs to be accessible via a registry of some sort
1666pub fn hover(cx: &mut Context) {
1739pub fn hover(cx: &mut Context) {