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>
This commit is contained in:
parent
6754acd83f
commit
c60ba4ba04
4 changed files with 102 additions and 19 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue