aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatouš Dzivjak2024-01-17 17:24:38 +0000
committerGitHub2024-01-17 17:24:38 +0000
commitc60ba4ba04de56f37f4f4b19051bc4a1e68b3484 (patch)
tree724199349f239b9c646bc26639c60cbe18eb9b1b
parent6754acd83ff0f6edf837611679c5f4424e14492e (diff)
feat(lsp): implement show document request (#8865)
* feat(lsp): implement show document request Implement [window.showDocument](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#window_showDocument) LSP server-sent request. This PR builds on top of helix-editor#5820, moves the external-URL opening functionality into shared crate-level function that returns a callback that is now used by both the `open_file` command as well as the window.showDocument handler if the URL is marked as external. * add return * use vertical split * refactor --------- Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
-rw-r--r--helix-lsp/src/lib.rs5
-rw-r--r--helix-term/src/application.rs70
-rw-r--r--helix-term/src/commands.rs23
-rw-r--r--helix-term/src/lib.rs23
4 files changed, 102 insertions, 19 deletions
diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs
index 34278cd5..83625897 100644
--- a/helix-lsp/src/lib.rs
+++ b/helix-lsp/src/lib.rs
@@ -549,6 +549,7 @@ pub enum MethodCall {
WorkspaceConfiguration(lsp::ConfigurationParams),
RegisterCapability(lsp::RegistrationParams),
UnregisterCapability(lsp::UnregistrationParams),
+ ShowDocument(lsp::ShowDocumentParams),
}
impl MethodCall {
@@ -576,6 +577,10 @@ impl MethodCall {
let params: lsp::UnregistrationParams = params.parse()?;
Self::UnregisterCapability(params)
}
+ lsp::request::ShowDocument::METHOD => {
+ let params: lsp::ShowDocumentParams = params.parse()?;
+ Self::ShowDocument(params)
+ }
_ => {
return Err(Error::Unhandled);
}
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index 01c120d0..1b0a06dd 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -3,6 +3,7 @@ use futures_util::Stream;
use helix_core::{path::get_relative_path, pos_at_coords, syntax, Selection};
use helix_lsp::{
lsp::{self, notification::Notification},
+ util::lsp_range_to_range,
LspProgressMap,
};
use helix_view::{
@@ -1100,6 +1101,13 @@ impl Application {
}
Ok(serde_json::Value::Null)
}
+ Ok(MethodCall::ShowDocument(params)) => {
+ let language_server = language_server!();
+ let offset_encoding = language_server.offset_encoding();
+
+ let result = self.handle_show_document(params, offset_encoding);
+ Ok(json!(result))
+ }
};
tokio::spawn(language_server!().reply(id, reply));
@@ -1108,6 +1116,68 @@ impl Application {
}
}
+ fn handle_show_document(
+ &mut self,
+ params: lsp::ShowDocumentParams,
+ offset_encoding: helix_lsp::OffsetEncoding,
+ ) -> lsp::ShowDocumentResult {
+ if let lsp::ShowDocumentParams {
+ external: Some(true),
+ uri,
+ ..
+ } = params
+ {
+ self.jobs.callback(crate::open_external_url_callback(uri));
+ return lsp::ShowDocumentResult { success: true };
+ };
+
+ let lsp::ShowDocumentParams {
+ uri,
+ selection,
+ take_focus,
+ ..
+ } = params;
+
+ let path = match uri.to_file_path() {
+ Ok(path) => path,
+ Err(err) => {
+ log::error!("unsupported file URI: {}: {:?}", uri, err);
+ return lsp::ShowDocumentResult { success: false };
+ }
+ };
+
+ let action = match take_focus {
+ Some(true) => helix_view::editor::Action::Replace,
+ _ => helix_view::editor::Action::VerticalSplit,
+ };
+
+ let doc_id = match self.editor.open(&path, action) {
+ Ok(id) => id,
+ Err(err) => {
+ log::error!("failed to open path: {:?}: {:?}", uri, err);
+ return lsp::ShowDocumentResult { success: false };
+ }
+ };
+
+ let doc = doc_mut!(self.editor, &doc_id);
+ if let Some(range) = selection {
+ // TODO: convert inside server
+ if let Some(new_range) = lsp_range_to_range(doc.text(), range, offset_encoding) {
+ let view = view_mut!(self.editor);
+
+ // we flip the range so that the cursor sits on the start of the symbol
+ // (for example start of the function).
+ doc.set_selection(view.id, Selection::single(new_range.head, new_range.anchor));
+ if action.align_view(view, doc.id()) {
+ align_view(doc, view, Align::Center);
+ }
+ } else {
+ log::warn!("lsp position out of bounds - {:?}", range);
+ };
+ };
+ lsp::ShowDocumentResult { success: true }
+ }
+
async fn claim_term(&mut self) -> std::io::Result<()> {
let terminal_config = self.config.load().editor.clone().into();
self.terminal.claim(terminal_config)
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index c9593380..e436e1cf 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -1227,7 +1227,7 @@ fn open_url(cx: &mut Context, url: Url, action: Action) {
.unwrap_or_default();
if url.scheme() != "file" {
- return open_external_url(cx, url);
+ return cx.jobs.callback(crate::open_external_url_callback(url));
}
let content_type = std::fs::File::open(url.path()).and_then(|file| {
@@ -1240,7 +1240,9 @@ fn open_url(cx: &mut Context, url: Url, action: Action) {
// we attempt to open binary files - files that can't be open in helix - using external
// program as well, e.g. pdf files or images
match content_type {
- Ok(content_inspector::ContentType::BINARY) => open_external_url(cx, url),
+ Ok(content_inspector::ContentType::BINARY) => {
+ cx.jobs.callback(crate::open_external_url_callback(url))
+ }
Ok(_) | Err(_) => {
let path = &rel_path.join(url.path());
if path.is_dir() {
@@ -1253,23 +1255,6 @@ fn open_url(cx: &mut Context, url: Url, action: Action) {
}
}
-/// Opens URL in external program.
-fn open_external_url(cx: &mut Context, url: Url) {
- let commands = open::commands(url.as_str());
- cx.jobs.callback(async {
- for cmd in commands {
- let mut command = tokio::process::Command::new(cmd.get_program());
- command.args(cmd.get_args());
- if command.output().await.is_ok() {
- return Ok(job::Callback::Editor(Box::new(|_| {})));
- }
- }
- Ok(job::Callback::Editor(Box::new(move |editor| {
- editor.set_error("Opening URL in external program failed")
- })))
- });
-}
-
fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
where
F: Fn(RopeSlice, Range, usize) -> Range,
diff --git a/helix-term/src/lib.rs b/helix-term/src/lib.rs
index a94c5e49..a1d60329 100644
--- a/helix-term/src/lib.rs
+++ b/helix-term/src/lib.rs
@@ -12,7 +12,11 @@ pub mod keymap;
pub mod ui;
use std::path::Path;
+use futures_util::Future;
use ignore::DirEntry;
+use url::Url;
+
+pub use keymap::macros::*;
#[cfg(not(windows))]
fn true_color() -> bool {
@@ -46,3 +50,22 @@ fn filter_picker_entry(entry: &DirEntry, root: &Path, dedup_symlinks: bool) -> b
true
}
+
+/// Opens URL in external program.
+fn open_external_url_callback(
+ url: Url,
+) -> impl Future<Output = Result<job::Callback, anyhow::Error>> + Send + 'static {
+ let commands = open::commands(url.as_str());
+ async {
+ for cmd in commands {
+ let mut command = tokio::process::Command::new(cmd.get_program());
+ command.args(cmd.get_args());
+ if command.output().await.is_ok() {
+ return Ok(job::Callback::Editor(Box::new(|_| {})));
+ }
+ }
+ Ok(job::Callback::Editor(Box::new(move |editor| {
+ editor.set_error("Opening URL in external program failed")
+ })))
+ }
+}