diff --git a/Cargo.lock b/Cargo.lock index de62a98e..abf6e630 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -191,6 +191,17 @@ version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815" +[[package]] +name = "futures-executor" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-macro" version = "0.3.14" @@ -293,6 +304,7 @@ name = "helix-lsp" version = "0.1.0" dependencies = [ "anyhow", + "futures-executor", "futures-util", "glob", "helix-core", diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index 09351f97..0fae9cb2 100644 --- a/helix-lsp/Cargo.toml +++ b/helix-lsp/Cargo.toml @@ -14,6 +14,7 @@ once_cell = "1.4" lsp-types = { version = "0.89", features = ["proposed"] } tokio = { version = "1", features = ["full"] } tokio-stream = "0.1.5" +futures-executor = { version = "0.3" } url = "2" pathdiff = "0.2" shellexpand = "2.0" diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index c54560de..8de4b95a 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -152,7 +152,7 @@ impl Client { timeout(Duration::from_secs(2), rx.recv()) .await - .map_err(|e| Error::Timeout)? // return Timeout + .map_err(|_| Error::Timeout)? // return Timeout .unwrap() // TODO: None if channel closed } } @@ -500,11 +500,11 @@ impl Client { self.call::(params) } - pub async fn text_document_signature_help( + pub fn text_document_signature_help( &self, text_document: lsp::TextDocumentIdentifier, position: lsp::Position, - ) -> anyhow::Result> { + ) -> impl Future> { let params = lsp::SignatureHelpParams { text_document_position_params: lsp::TextDocumentPositionParams { text_document, @@ -517,18 +517,14 @@ impl Client { // lsp::SignatureHelpContext }; - let response = self - .request::(params) - .await?; - - Ok(response) + self.call::(params) } - pub async fn text_document_hover( + pub fn text_document_hover( &self, text_document: lsp::TextDocumentIdentifier, position: lsp::Position, - ) -> anyhow::Result> { + ) -> impl Future> { let params = lsp::HoverParams { text_document_position_params: lsp::TextDocumentPositionParams { text_document, @@ -540,9 +536,7 @@ impl Client { // lsp::SignatureHelpContext }; - let response = self.request::(params).await?; - - Ok(response) + self.call::(params) } // formatting @@ -607,7 +601,7 @@ impl Client { Ok(response.unwrap_or_default()) } - async fn goto_request< + fn goto_request< T: lsp::request::Request< Params = lsp::GotoDefinitionParams, Result = Option, @@ -616,7 +610,7 @@ impl Client { &self, text_document: lsp::TextDocumentIdentifier, position: lsp::Position, - ) -> anyhow::Result> { + ) -> impl Future> { let params = lsp::GotoDefinitionParams { text_document_position_params: lsp::TextDocumentPositionParams { text_document, @@ -630,56 +624,38 @@ impl Client { }, }; - let response = self.request::(params).await?; - - let items = match response { - Some(lsp::GotoDefinitionResponse::Scalar(location)) => vec![location], - Some(lsp::GotoDefinitionResponse::Array(locations)) => locations, - Some(lsp::GotoDefinitionResponse::Link(locations)) => locations - .into_iter() - .map(|location_link| lsp::Location { - uri: location_link.target_uri, - range: location_link.target_range, - }) - .collect(), - None => Vec::new(), - }; - - Ok(items) + self.call::(params) } - pub async fn goto_definition( + pub fn goto_definition( &self, text_document: lsp::TextDocumentIdentifier, position: lsp::Position, - ) -> anyhow::Result> { + ) -> impl Future> { self.goto_request::(text_document, position) - .await } - pub async fn goto_type_definition( + pub fn goto_type_definition( &self, text_document: lsp::TextDocumentIdentifier, position: lsp::Position, - ) -> anyhow::Result> { + ) -> impl Future> { self.goto_request::(text_document, position) - .await } - pub async fn goto_implementation( + pub fn goto_implementation( &self, text_document: lsp::TextDocumentIdentifier, position: lsp::Position, - ) -> anyhow::Result> { + ) -> impl Future> { self.goto_request::(text_document, position) - .await } - pub async fn goto_reference( + pub fn goto_reference( &self, text_document: lsp::TextDocumentIdentifier, position: lsp::Position, - ) -> anyhow::Result> { + ) -> impl Future> { let params = lsp::ReferenceParams { text_document_position: lsp::TextDocumentPositionParams { text_document, @@ -696,8 +672,6 @@ impl Client { }, }; - let response = self.request::(params).await?; - - Ok(response.unwrap_or_default()) + self.call::(params) } } diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index fd7e6fd3..6adaa3f8 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -203,8 +203,7 @@ impl Registry { Client::start(&config.command, &config.args).ok()?; // TODO: run this async without blocking - let rt = tokio::runtime::Handle::current(); - rt.block_on(client.initialize()).unwrap(); + futures_executor::block_on(client.initialize()).unwrap(); s_incoming.push(UnboundedReceiverStream::new(incoming)); diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 1e53d010..aebdb465 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -108,16 +108,6 @@ impl<'a> Context<'a> { /// state (usually by creating and applying a transaction). pub type Command = fn(cx: &mut Context); -#[inline] -fn block_on(future: impl Future) -> T { - use tokio::runtime::Runtime; - // let rt = Runtime::new().unwrap(); - let rt = tokio::runtime::Handle::current(); - // let local = LocalSet::new(); - // local.block_on(&rt, future) - rt.block_on(future) -} - pub fn move_char_left(cx: &mut Context) { let count = cx.count; let (view, doc) = cx.current(); @@ -260,13 +250,13 @@ pub fn move_next_word_end(cx: &mut Context) { } pub fn move_file_start(cx: &mut Context) { - push_jump(cx); + push_jump(cx.editor); let (view, doc) = cx.current(); doc.set_selection(view.id, Selection::point(0)); } pub fn move_file_end(cx: &mut Context) { - push_jump(cx); + push_jump(cx.editor); let (view, doc) = cx.current(); let text = doc.text(); let last_line = text.line_to_char(text.len_lines().saturating_sub(2)); @@ -880,7 +870,7 @@ pub fn command_mode(cx: &mut Context) { // TODO: non-blocking via save() command let id = editor.view().doc; let doc = &mut editor.documents[id]; - block_on(doc.save()); + tokio::spawn(doc.save()); } _ => (), @@ -1079,8 +1069,8 @@ pub fn normal_mode(cx: &mut Context) { } // Store a jump on the jumplist. -fn push_jump(cx: &mut Context) { - let (view, doc) = cx.current(); +fn push_jump(editor: &mut Editor) { + let (view, doc) = editor.current(); let jump = { (doc.id(), doc.selection(view.id).clone()) }; view.jumps.push(jump); } @@ -1089,7 +1079,7 @@ pub fn goto_mode(cx: &mut Context) { let count = cx.count; if count > 1 { - push_jump(cx); + push_jump(cx.editor); // TODO: can't go to line 1 since we can't distinguish between g and 1g, g gets converted // to 1g @@ -1127,10 +1117,15 @@ pub fn exit_select_mode(cx: &mut Context) { cx.doc().mode = Mode::Normal; } -fn _goto(cx: &mut Context, locations: Vec, offset_encoding: OffsetEncoding) { +fn _goto( + editor: &mut Editor, + compositor: &mut Compositor, + locations: Vec, + offset_encoding: OffsetEncoding, +) { use helix_view::editor::Action; - push_jump(cx); + push_jump(editor); fn jump_to( editor: &mut Editor, @@ -1152,7 +1147,7 @@ fn _goto(cx: &mut Context, locations: Vec, offset_encoding: Offse match locations.as_slice() { [location] => { - jump_to(cx.editor, location, offset_encoding, Action::Replace); + jump_to(editor, location, offset_encoding, Action::Replace); } [] => (), // maybe show user message that no definition was found? _locations => { @@ -1167,7 +1162,7 @@ fn _goto(cx: &mut Context, locations: Vec, offset_encoding: Offse jump_to(editor, location, offset_encoding, action) }, ); - cx.push_layer(Box::new(picker)); + compositor.push(Box::new(picker)); } } } @@ -1184,8 +1179,29 @@ pub fn goto_definition(cx: &mut Context) { let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding); // TODO: handle fails - let res = block_on(language_server.goto_definition(doc.identifier(), pos)).unwrap_or_default(); - _goto(cx, res, offset_encoding); + let future = language_server.goto_definition(doc.identifier(), pos); + + cx.callback( + future, + move |editor: &mut Editor, + compositor: &mut Compositor, + response: Option| { + let items = match response { + Some(lsp::GotoDefinitionResponse::Scalar(location)) => vec![location], + Some(lsp::GotoDefinitionResponse::Array(locations)) => locations, + Some(lsp::GotoDefinitionResponse::Link(locations)) => locations + .into_iter() + .map(|location_link| lsp::Location { + uri: location_link.target_uri, + range: location_link.target_range, + }) + .collect(), + None => Vec::new(), + }; + + _goto(editor, compositor, items, offset_encoding); + }, + ); } pub fn goto_type_definition(cx: &mut Context) { @@ -1200,9 +1216,29 @@ pub fn goto_type_definition(cx: &mut Context) { let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding); // TODO: handle fails - let res = - block_on(language_server.goto_type_definition(doc.identifier(), pos)).unwrap_or_default(); - _goto(cx, res, offset_encoding); + let future = language_server.goto_type_definition(doc.identifier(), pos); + + cx.callback( + future, + move |editor: &mut Editor, + compositor: &mut Compositor, + response: Option| { + let items = match response { + Some(lsp::GotoDefinitionResponse::Scalar(location)) => vec![location], + Some(lsp::GotoDefinitionResponse::Array(locations)) => locations, + Some(lsp::GotoDefinitionResponse::Link(locations)) => locations + .into_iter() + .map(|location_link| lsp::Location { + uri: location_link.target_uri, + range: location_link.target_range, + }) + .collect(), + None => Vec::new(), + }; + + _goto(editor, compositor, items, offset_encoding); + }, + ); } pub fn goto_implementation(cx: &mut Context) { @@ -1217,9 +1253,29 @@ pub fn goto_implementation(cx: &mut Context) { let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding); // TODO: handle fails - let res = - block_on(language_server.goto_implementation(doc.identifier(), pos)).unwrap_or_default(); - _goto(cx, res, offset_encoding); + let future = language_server.goto_implementation(doc.identifier(), pos); + + cx.callback( + future, + move |editor: &mut Editor, + compositor: &mut Compositor, + response: Option| { + let items = match response { + Some(lsp::GotoDefinitionResponse::Scalar(location)) => vec![location], + Some(lsp::GotoDefinitionResponse::Array(locations)) => locations, + Some(lsp::GotoDefinitionResponse::Link(locations)) => locations + .into_iter() + .map(|location_link| lsp::Location { + uri: location_link.target_uri, + range: location_link.target_range, + }) + .collect(), + None => Vec::new(), + }; + + _goto(editor, compositor, items, offset_encoding); + }, + ); } pub fn goto_reference(cx: &mut Context) { @@ -1234,8 +1290,21 @@ pub fn goto_reference(cx: &mut Context) { let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding); // TODO: handle fails - let res = block_on(language_server.goto_reference(doc.identifier(), pos)).unwrap_or_default(); - _goto(cx, res, offset_encoding); + let future = language_server.goto_reference(doc.identifier(), pos); + + cx.callback( + future, + move |editor: &mut Editor, + compositor: &mut Compositor, + items: Option>| { + _goto( + editor, + compositor, + items.unwrap_or_default(), + offset_encoding, + ); + }, + ); } pub fn signature_help(cx: &mut Context) { @@ -1253,23 +1322,28 @@ pub fn signature_help(cx: &mut Context) { ); // TODO: handle fails + let future = language_server.text_document_signature_help(doc.identifier(), pos); - let res = 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 - } + cx.callback( + future, + move |editor: &mut Editor, + compositor: &mut Compositor, + response: Option| { + if let Some(signature_help) = response { + 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. @@ -1643,20 +1717,22 @@ pub fn format_selections(cx: &mut Context) { }; // TODO: handle fails // TODO: concurrent map - let edits = block_on(language_server.text_document_range_formatting( - doc.identifier(), - range, - lsp::FormattingOptions::default(), - )) - .unwrap_or_default(); - - let transaction = helix_lsp::util::generate_transaction_from_edits( - doc.text(), - edits, - language_server.offset_encoding(), - ); - - doc.apply(&transaction, view.id); + unimplemented!(); // neeed to block to get the formatting + + // let edits = block_on(language_server.text_document_range_formatting( + // doc.identifier(), + // range, + // lsp::FormattingOptions::default(), + // )) + // .unwrap_or_default(); + + // let transaction = helix_lsp::util::generate_transaction_from_edits( + // doc.text(), + // edits, + // language_server.offset_encoding(), + // ); + + // doc.apply(&transaction, view.id); } doc.append_changes_to_history(view.id); @@ -1734,7 +1810,7 @@ pub fn save(cx: &mut Context) { // TODO: handle save errors somehow? // TODO: don't block - block_on(cx.doc().save()); + tokio::spawn(cx.doc().save()); } pub fn completion(cx: &mut Context) { @@ -1847,30 +1923,34 @@ pub fn hover(cx: &mut Context) { ); // TODO: handle fails - let res = - block_on(language_server.text_document_hover(doc.identifier(), pos)).unwrap_or_default(); - - if let Some(hover) = res { - // hover.contents / .range <- used for visualizing - let contents = match hover.contents { - lsp::HoverContents::Scalar(contents) => { - // markedstring(string/languagestring to be highlighted) - // TODO - unimplemented!("{:?}", contents) - } - lsp::HoverContents::Array(contents) => { - unimplemented!("{:?}", contents) - } - // TODO: render markdown - lsp::HoverContents::Markup(contents) => contents.value, - }; + let future = language_server.text_document_hover(doc.identifier(), pos); - // skip if contents empty + cx.callback( + future, + move |editor: &mut Editor, compositor: &mut Compositor, response: Option| { + if let Some(hover) = response { + // hover.contents / .range <- used for visualizing + let contents = match hover.contents { + lsp::HoverContents::Scalar(contents) => { + // markedstring(string/languagestring to be highlighted) + // TODO + unimplemented!("{:?}", contents) + } + lsp::HoverContents::Array(contents) => { + unimplemented!("{:?}", contents) + } + // TODO: render markdown + lsp::HoverContents::Markup(contents) => contents.value, + }; - let contents = ui::Markdown::new(contents); - let mut popup = Popup::new(contents); - cx.push_layer(Box::new(popup)); - } + // skip if contents empty + + let contents = ui::Markdown::new(contents); + let mut popup = Popup::new(contents); + compositor.push(Box::new(popup)); + } + }, + ); } // view movements @@ -1971,7 +2051,7 @@ pub fn space_mode(cx: &mut Context) { 'w' => { // save current buffer let doc = cx.doc(); - block_on(doc.save()); + tokio::spawn(doc.save()); } 'c' => { // close current split