on master help sign in
wip: Async async. Delay response handling with a callback.
commit2a3910c1d9f2b05fe5bc0610e6a83e6dabe13b71 authorBlaž Hrastnik <blaz@mxxn.io> dateFri 26 Mar 2021, 16:02 (6 years ago) parentad3325d
8 files changed+220−104
Cargo.lock+2 helix-lsp/src/client.rs+28−25 helix-lsp/src/lib.rs+2 helix-term/Cargo.toml+3 helix-term/src/application.rs+29 helix-term/src/commands.rs+152−79 helix-term/src/compositor.rs+3 helix-term/src/ui/editor.rs+1
Cargo.lock+2
@@ -549,6 +549,8 @@ dependencies = [
549549 "num_cpus",
550550 "once_cell",
551551 "pulldown-cmark",
552+ "serde",
553+ "serde_json",
552554 "smol",
553555 "smol-timeout",
554556 "toml",
helix-lsp/src/client.rs+28−25
@@ -1,13 +1,12 @@
11use crate::{
22 transport::{Payload, Transport},
3− Call, Error,
3+ Call, Error, Result,
44};
55
6−type Result<T> = core::result::Result<T, Error>;
7−
86use helix_core::{ChangeSet, Rope};
97
108// use std::collections::HashMap;
9+use std::future::Future;
1110use std::sync::atomic::{AtomicU64, Ordering};
1211
1312use jsonrpc_core as jsonrpc;
@@ -96,6 +95,21 @@ impl Client {
9695 where
9796 R::Params: serde::Serialize,
9897 R::Result: core::fmt::Debug, // TODO: temporary
98+ {
99+ // a future that resolves into the response
100+ let future = self.call::<R>(params).await?;
101+ let json = future.await?;
102+ let response = serde_json::from_value(json)?;
103+ Ok(response)
104+ }
105+
106+ /// Execute a RPC request on the language server.
107+ pub async fn call<R: lsp::request::Request>(
108+ &self,
109+ params: R::Params,
110+ ) -> Result<impl Future<Output = Result<Value>>>
111+ where
112+ R::Params: serde::Serialize,
99113 {
100114 let params = serde_json::to_value(params)?;
101115
@@ -119,15 +133,15 @@ impl Client {
119133 use smol_timeout::TimeoutExt;
120134 use std::time::Duration;
121135
122− let response = match rx.recv().timeout(Duration::from_secs(2)).await {
123− Some(response) => response,
124− None => return Err(Error::Timeout),
125− }
126− .map_err(|e| Error::Other(e.into()))??;
127−
128− let response = serde_json::from_value(response)?;
136+ let future = async move {
137+ rx.recv()
138+ .timeout(Duration::from_secs(2))
139+ .await
140+ .ok_or(Error::Timeout)? // return Timeout
141+ .map_err(|e| Error::Other(e.into()))?
142+ };
129143
130− Ok(response)
144+ Ok(future)
131145 }
132146
133147 /// Send a RPC notification to the language server.
@@ -447,7 +461,8 @@ impl Client {
447461 &self,
448462 text_document: lsp::TextDocumentIdentifier,
449463 position: lsp::Position,
450− ) -> Result<Vec<lsp::CompletionItem>> {
464+ ) -> Result<impl Future<Output = Result<Value>>> {
465+ // ) -> Result<Vec<lsp::CompletionItem>> {
451466 let params = lsp::CompletionParams {
452467 text_document_position: lsp::TextDocumentPositionParams {
453468 text_document,
@@ -464,19 +479,7 @@ impl Client {
464479 // lsp::CompletionContext { trigger_kind: , trigger_character: Some(), }
465480 };
466481
467− let response = self.request::<lsp::request::Completion>(params).await?;
468−
469− let items = match response {
470− Some(lsp::CompletionResponse::Array(items)) => items,
471− // TODO: do something with is_incomplete
472− Some(lsp::CompletionResponse::List(lsp::CompletionList {
473− is_incomplete: _is_incomplete,
474− items,
475− })) => items,
476− None => Vec::new(),
477− };
478−
479− Ok(items)
482+ self.call::<lsp::request::Completion>(params).await
480483 }
481484
482485 pub async fn text_document_signature_help(
helix-lsp/src/lib.rs+2
@@ -8,6 +8,8 @@ pub use lsp_types as lsp;
88pub use client::Client;
99pub use lsp::{Position, Url};
1010
11+pub type Result<T> = core::result::Result<T, Error>;
12+
1113use helix_core::syntax::LanguageConfiguration;
1214
1315use thiserror::Error;
helix-term/Cargo.toml+3
@@ -44,3 +44,6 @@ pulldown-cmark = { version = "0.8", default-features = false }
4444
4545# config
4646toml = "0.5"
47+
48+serde_json = "1.0"
49+serde = { version = "1.0", features = ["derive"] }
helix-term/src/application.rs+29
@@ -24,11 +24,23 @@ use crossterm::{
2424
2525use tui::layout::Rect;
2626
27+// use futures_util::future::BoxFuture;
28+use futures_util::stream::FuturesUnordered;
29+use std::pin::Pin;
30+
31+type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
32+pub type LspCallback =
33+ BoxFuture<Result<Box<dyn FnOnce(&mut Editor, &mut Compositor) + Send>, anyhow::Error>>;
34+
35+pub type LspCallbacks = FuturesUnordered<LspCallback>;
36+pub type LspCallbackWrapper = Box<dyn FnOnce(&mut Editor, &mut Compositor) + Send>;
37+
2738pub struct Application {
2839 compositor: Compositor,
2940 editor: Editor,
3041
3142 executor: &'static smol::Executor<'static>,
43+ callbacks: LspCallbacks,
3244}
3345
3446impl Application {
@@ -50,6 +62,7 @@ impl Application {
5062 editor,
5163
5264 executor,
65+ callbacks: FuturesUnordered::new(),
5366 };
5467
5568 Ok(app)
@@ -59,10 +72,12 @@ impl Application {
5972 let executor = &self.executor;
6073 let editor = &mut self.editor;
6174 let compositor = &mut self.compositor;
75+ let callbacks = &mut self.callbacks;
6276
6377 let mut cx = crate::compositor::Context {
6478 editor,
6579 executor,
80+ callbacks,
6681 scroll: None,
6782 };
6883
@@ -87,14 +102,28 @@ impl Application {
87102 call = self.editor.language_servers.incoming.next().fuse() => {
88103 self.handle_language_server_message(call).await
89104 }
105+ callback = self.callbacks.next().fuse() => {
106+ self.handle_language_server_callback(callback)
107+ }
90108 }
91109 }
92110 }
111+ pub fn handle_language_server_callback(
112+ &mut self,
113+ callback: Option<Result<LspCallbackWrapper, anyhow::Error>>,
114+ ) {
115+ if let Some(Ok(callback)) = callback {
116+ // TODO: handle Err()
117+ callback(&mut self.editor, &mut self.compositor);
118+ self.render();
119+ }
120+ }
93121
94122 pub fn handle_terminal_events(&mut self, event: Option<Result<Event, crossterm::ErrorKind>>) {
95123 let mut cx = crate::compositor::Context {
96124 editor: &mut self.editor,
97125 executor: &self.executor,
126+ callbacks: &mut self.callbacks,
98127 scroll: None,
99128 };
100129 // Handle key events
helix-term/src/commands.rs+152−79
@@ -10,7 +10,7 @@ use helix_core::{
1010use once_cell::sync::Lazy;
1111
1212use crate::{
13− compositor::{Callback, Compositor},
13+ compositor::{Callback, Component, Compositor},
1414 ui::{self, Picker, Popup, Prompt, PromptEvent},
1515};
1616
@@ -26,14 +26,20 @@ use crossterm::event::{KeyCode, KeyEvent};
2626
2727use helix_lsp::lsp;
2828
29+use crate::application::{LspCallbackWrapper, LspCallbacks};
30+
2931pub struct Context<'a> {
3032 pub count: usize,
3133 pub editor: &'a mut Editor,
3234
3335 pub callback: Option<crate::compositor::Callback>,
3436 pub on_next_key_callback: Option<Box<dyn FnOnce(&mut Context, KeyEvent)>>,
37+ pub callbacks: &'a mut LspCallbacks,
3538}
3639
40+use futures_util::FutureExt;
41+use std::future::Future;
42+
3743impl<'a> Context<'a> {
3844 #[inline]
3945 pub fn view(&mut self) -> &mut View {
@@ -47,7 +53,7 @@ impl<'a> Context<'a> {
4753 }
4854
4955 /// 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>) {
5157 self.callback = Some(Box::new(
5258 |compositor: &mut Compositor, editor: &mut Editor| {
5359 let size = compositor.size();
@@ -65,6 +71,27 @@ impl<'a> Context<'a> {
6571 ) {
6672 self.on_next_key_callback = Some(Box::new(on_next_key_callback));
6773 }
74+
75+ #[inline]
76+ pub fn callback<T, F>(
77+ &mut self,
78+ call: impl Future<Output = helix_lsp::Result<serde_json::Value>> + 'static + Send,
79+ callback: F,
80+ ) where
81+ T: for<'de> serde::Deserialize<'de> + Send + 'static,
82+ F: FnOnce(&mut Editor, &mut Compositor, T) + Send + 'static,
83+ {
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)
90+ });
91+ Ok(call)
92+ });
93+ self.callbacks.push(callback);
94+ }
6895}
6996
7097/// 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) {
15641591}
15651592
15661593pub 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
1597+ //
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
1600+ //
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))
1606+ // })
1607+ // async {
1608+ // let (response, callback) = response.await?;
1609+ // callback(response)
1610+ // }
1611+
15671612 let doc = cx.doc();
15681613
15691614 let language_server = match doc.language_server() {
@@ -1576,91 +1621,119 @@ pub fn completion(cx: &mut Context) {
15761621
15771622 // TODO: handle fails
15781623
1579− let res = smol::block_on(language_server.completion(doc.identifier(), pos)).unwrap_or_default();
1580−
1581− // TODO: if no completion, show some message or something
1582− if !res.is_empty() {
1583− // let snapshot = doc.state.clone();
1584− let mut menu = ui::Menu::new(
1585− res,
1586− |item| {
1587− // format_fn
1588− item.label.as_str().into()
1589−
1590− // TODO: use item.filter_text for filtering
1591− },
1592− move |editor: &mut Editor, item, event| {
1593− match event {
1594− PromptEvent::Abort => {
1595− // revert state
1596− // let id = editor.view().doc;
1597− // let doc = &mut editor.documents[id];
1598− // doc.state = snapshot.clone();
1599− }
1600− PromptEvent::Validate => {
1601− let id = editor.view().doc;
1602− let doc = &mut editor.documents[id];
1603−
1604− // revert state to what it was before the last update
1605− // doc.state = snapshot.clone();
1606−
1607− // extract as fn(doc, item):
1608−
1609− // TODO: need to apply without composing state...
1610− // TODO: need to update lsp on accept/cancel by diffing the snapshot with
1611− // the final state?
1612− // -> on update simply update the snapshot, then on accept redo the call,
1613− // finally updating doc.changes + notifying lsp.
1614− //
1615− // or we could simply use doc.undo + apply when changing between options
1616−
1617− // always present here
1618− let item = item.unwrap();
1619−
1620− use helix_lsp::{lsp, util};
1621− // determine what to insert: text_edit | insert_text | label
1622− let edit = if let Some(edit) = &item.text_edit {
1623− match edit {
1624− lsp::CompletionTextEdit::Edit(edit) => edit.clone(),
1625− lsp::CompletionTextEdit::InsertAndReplace(item) => {
1626− unimplemented!("completion: insert_and_replace {:?}", item)
1624+ let res = smol::block_on(language_server.completion(doc.identifier(), pos)).unwrap();
1625+
1626+ cx.callback(
1627+ res,
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,
1636+ items,
1637+ })) => items,
1638+ None => Vec::new(),
1639+ };
1640+
1641+ // TODO: if no completion, show some message or something
1642+ if !items.is_empty() {
1643+ // let snapshot = doc.state.clone();
1644+ let mut menu = ui::Menu::new(
1645+ items,
1646+ |item| {
1647+ // format_fn
1648+ item.label.as_str().into()
1649+
1650+ // TODO: use item.filter_text for filtering
1651+ },
1652+ move |editor: &mut Editor, item, event| {
1653+ match event {
1654+ PromptEvent::Abort => {
1655+ // revert state
1656+ // let id = editor.view().doc;
1657+ // let doc = &mut editor.documents[id];
1658+ // doc.state = snapshot.clone();
1659+ }
1660+ PromptEvent::Validate => {
1661+ let id = editor.view().doc;
1662+ let doc = &mut editor.documents[id];
1663+
1664+ // revert state to what it was before the last update
1665+ // doc.state = snapshot.clone();
1666+
1667+ // extract as fn(doc, item):
1668+
1669+ // TODO: need to apply without composing state...
1670+ // TODO: need to update lsp on accept/cancel by diffing the snapshot with
1671+ // the final state?
1672+ // -> on update simply update the snapshot, then on accept redo the call,
1673+ // finally updating doc.changes + notifying lsp.
1674+ //
1675+ // or we could simply use doc.undo + apply when changing between options
1676+
1677+ // always present here
1678+ let item = item.unwrap();
1679+
1680+ use helix_lsp::{lsp, util};
1681+ // determine what to insert: text_edit | insert_text | label
1682+ let edit = if let Some(edit) = &item.text_edit {
1683+ match edit {
1684+ lsp::CompletionTextEdit::Edit(edit) => edit.clone(),
1685+ lsp::CompletionTextEdit::InsertAndReplace(item) => {
1686+ unimplemented!(
1687+ "completion: insert_and_replace {:?}",
1688+ item
1689+ )
1690+ }
1691+ }
1692+ } else {
1693+ item.insert_text.as_ref().unwrap_or(&item.label);
1694+ unimplemented!();
1695+ // lsp::TextEdit::new(); TODO: calculate a TextEdit from insert_text
1696+ // and we insert at position.
1697+ };
1698+
1699+ // TODO: merge edit with additional_text_edits
1700+ if let Some(additional_edits) = &item.additional_text_edits {
1701+ if !additional_edits.is_empty() {
1702+ unimplemented!(
1703+ "completion: additional_text_edits: {:?}",
1704+ additional_edits
1705+ );
1706+ }
16271707 }
1708+
1709+ let transaction =
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?
16281713 }
1629− } else {
1630− item.insert_text.as_ref().unwrap_or(&item.label);
1631− unimplemented!();
1632− // lsp::TextEdit::new(); TODO: calculate a TextEdit from insert_text
1633− // and we insert at position.
1714+ _ => (),
16341715 };
1716+ },
1717+ );
16351718
1636− // TODO: merge edit with additional_text_edits
1637− if let Some(additional_edits) = &item.additional_text_edits {
1638− if !additional_edits.is_empty() {
1639− unimplemented!(
1640− "completion: additional_text_edits: {:?}",
1641− additional_edits
1642− );
1643− }
1644− }
1719+ let popup = Popup::new(Box::new(menu));
1720+ let mut component: Box<dyn Component> = Box::new(popup);
16451721
1646− // TODO: <-- if state has changed by further input, transaction will panic on len
1647− let transaction =
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?
1651− }
1652− _ => (),
1653− };
1654− },
1655− );
1722+ // Server error: content modified
16561723
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);
1729+ }
1730+ },
1731+ );
16591732
1660− // TODO!: when iterating over items, show the docs in popup
1733+ // // TODO!: when iterating over items, show the docs in popup
16611734
1662− // language server client needs to be accessible via a registry of some sort
1663− }
1735+ // // language server client needs to be accessible via a registry of some sort
1736+ //}
16641737}
16651738
16661739pub fn hover(cx: &mut Context) {
helix-term/src/compositor.rs+3
@@ -25,10 +25,13 @@ pub enum EventResult {
2525
2626use helix_view::{Editor, View};
2727
28+use crate::application::LspCallbacks;
29+
2830pub struct Context<'a> {
2931 pub editor: &'a mut Editor,
3032 pub executor: &'static smol::Executor<'static>,
3133 pub scroll: Option<usize>,
34+ pub callbacks: &'a mut LspCallbacks,
3235}
3336
3437pub trait Component {
helix-term/src/ui/editor.rs+1
@@ -439,6 +439,7 @@ impl Component for EditorView {
439439 editor: &mut cx.editor,
440440 count: 1,
441441 callback: None,
442+ callbacks: cx.callbacks,
442443 on_next_key_callback: None,
443444 };
444445