Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement window/showMessageRequest #5492

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
5 changes: 5 additions & 0 deletions helix-lsp/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,11 @@ impl Client {
..Default::default()
}),
window: Some(lsp::WindowClientCapabilities {
show_message: Some(lsp::ShowMessageRequestClientCapabilities {
message_action_item: Some(lsp::MessageActionItemCapabilities {
additional_properties_support: Some(true),
}),
}),
work_done_progress: Some(true),
..Default::default()
}),
Expand Down
4 changes: 4 additions & 0 deletions helix-lsp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@ pub enum MethodCall {
WorkspaceFolders,
WorkspaceConfiguration(lsp::ConfigurationParams),
RegisterCapability(lsp::RegistrationParams),
ShowMessageRequest(lsp::ShowMessageRequestParams),
}

impl MethodCall {
Expand All @@ -550,6 +551,9 @@ impl MethodCall {
let params: lsp::RegistrationParams = params.parse()?;
Self::RegisterCapability(params)
}
lsp::request::ShowMessageRequest::METHOD => {
Self::ShowMessageRequest(params.parse::<lsp::ShowMessageRequestParams>()?)
}
_ => {
return Err(Error::Unhandled);
}
Expand Down
97 changes: 76 additions & 21 deletions helix-term/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ use futures_util::Stream;
use helix_core::{
diagnostic::{DiagnosticTag, NumberOrString},
path::get_relative_path,
pos_at_coords, syntax, Selection,
pos_at_coords, syntax, Rope, Selection,
};
use helix_lsp::{
lsp::{self, MessageType},
util::lsp_pos_to_pos,
LspProgressMap,
};
use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
use helix_view::{
align_view,
document::DocumentSavedEventResult,
Expand All @@ -25,7 +29,7 @@ use crate::{
config::Config,
job::Jobs,
keymap::Keymaps,
ui::{self, overlay::overlayed},
ui::{self, overlay::overlayed, FileLocation, FilePicker, Popup},
};

use log::{debug, error, warn};
Expand Down Expand Up @@ -965,11 +969,11 @@ impl Application {
"Language Server: Method {} not found in request {}",
method, id
);
Err(helix_lsp::jsonrpc::Error {
Some(Err(helix_lsp::jsonrpc::Error {
code: helix_lsp::jsonrpc::ErrorCode::MethodNotFound,
message: format!("Method not found: {}", method),
data: None,
})
}))
}
Err(err) => {
log::error!(
Expand All @@ -978,11 +982,11 @@ impl Application {
id,
err
);
Err(helix_lsp::jsonrpc::Error {
Some(Err(helix_lsp::jsonrpc::Error {
code: helix_lsp::jsonrpc::ErrorCode::ParseError,
message: format!("Malformed method call: {}", method),
data: None,
})
}))
}
Ok(MethodCall::WorkDoneProgressCreate(params)) => {
self.lsp_progress.create(server_id, params.token);
Expand All @@ -996,7 +1000,7 @@ impl Application {
spinner.start();
}

Ok(serde_json::Value::Null)
Some(Ok(serde_json::Value::Null))
}
Ok(MethodCall::ApplyWorkspaceEdit(params)) => {
let res = apply_workspace_edit(
Expand All @@ -1005,20 +1009,69 @@ impl Application {
&params.edit,
);

Ok(json!(lsp::ApplyWorkspaceEditResponse {
Some(Ok(json!(lsp::ApplyWorkspaceEditResponse {
applied: res.is_ok(),
failure_reason: res.as_ref().err().map(|err| err.kind.to_string()),
failed_change: res
.as_ref()
.err()
.map(|err| err.failed_change_idx as u32),
}))
})))
}
Ok(MethodCall::WorkspaceFolders) => {
let language_server =
self.editor.language_servers.get_by_id(server_id).unwrap();

Ok(json!(language_server.workspace_folders()))
Some(Ok(json!(language_server.workspace_folders())))
}
Ok(MethodCall::ShowMessageRequest(params)) => {
if let Some(actions) = params.actions {
let call_id = id.clone();
let message_type = match params.typ {
MessageType::ERROR => "ERROR",
MessageType::WARNING => "WARNING",
MessageType::INFO => "INFO",
_ => "LOG",
};
let message = format!("{}: {}", message_type, &params.message);
let rope = Rope::from(message);
let picker = FilePicker::new(
actions,
(),
move |ctx, message_action, _event| {
let server_from_id =
ctx.editor.language_servers.get_by_id(server_id);
let language_server = match server_from_id {
Some(language_server) => language_server,
None => {
warn!(
"can't find language server with id `{server_id}`"
);
return;
}
};
let response = match message_action {
Some(item) => json!(item),
None => serde_json::Value::Null,
};

tokio::spawn(
language_server.reply(call_id.clone(), Ok(response)),
);
},
move |_editor, _value| {
let file_location: FileLocation = (rope.clone().into(), None);
Some(file_location)
},
);
let popup_id = "show-message-request";
let popup = Popup::new(popup_id, picker);
self.compositor.replace_or_push(popup_id, popup);
// do not send a reply just yet
None
} else {
Some(Ok(serde_json::Value::Null))
}
}
Ok(MethodCall::WorkspaceConfiguration(params)) => {
let result: Vec<_> = params
Expand Down Expand Up @@ -1046,7 +1099,7 @@ impl Application {
Some(config)
})
.collect();
Ok(json!(result))
Some(Ok(json!(result)))
}
Ok(MethodCall::RegisterCapability(_params)) => {
log::warn!("Ignoring a client/registerCapability request because dynamic capability registration is not enabled. Please report this upstream to the language server");
Expand All @@ -1057,19 +1110,21 @@ impl Application {
// exit. So we work around this by ignoring the request and sending back an OK
// response.

Ok(serde_json::Value::Null)
Some(Ok(serde_json::Value::Null))
}
};

let language_server = match self.editor.language_servers.get_by_id(server_id) {
Some(language_server) => language_server,
None => {
warn!("can't find language server with id `{}`", server_id);
return;
}
};
if let Some(reply) = reply {
let language_server = match self.editor.language_servers.get_by_id(server_id) {
Some(language_server) => language_server,
None => {
warn!("can't find language server with id `{}`", server_id);
return;
}
};

tokio::spawn(language_server.reply(id, reply));
tokio::spawn(language_server.reply(id, reply));
}
}
Call::Invalid { id } => log::error!("LSP invalid method call id={:?}", id),
}
Expand Down
12 changes: 8 additions & 4 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2116,7 +2116,8 @@ fn global_search(cx: &mut Context) {
let picker = FilePicker::new(
all_matches,
current_path,
move |cx, FileResult { path, line_num }, action| {
move |cx, file_result, action| {
let Some(FileResult { path, line_num }) = file_result else { return };
match cx.editor.open(path, action) {
Ok(_) => {}
Err(e) => {
Expand Down Expand Up @@ -2496,7 +2497,8 @@ fn buffer_picker(cx: &mut Context) {
.map(|doc| new_meta(doc))
.collect(),
(),
|cx, meta, action| {
|cx, buffer_meta, action| {
let Some(meta) = buffer_meta else { return };
cx.editor.switch(meta.id, action);
},
|editor, meta| {
Expand Down Expand Up @@ -2578,7 +2580,8 @@ fn jumplist_picker(cx: &mut Context) {
})
.collect(),
(),
|cx, meta, action| {
|cx, jump_meta, action| {
let Some(meta) = jump_meta else { return };
cx.editor.switch(meta.id, action);
let config = cx.editor.config();
let (view, doc) = current!(cx.editor);
Expand Down Expand Up @@ -2639,7 +2642,8 @@ pub fn command_palette(cx: &mut Context) {
}
}));

let picker = Picker::new(commands, keymap, move |cx, command, _action| {
let picker = Picker::new(commands, keymap, move |cx, mappable_command, _action| {
let Some(command) = mappable_command else { return };
let mut ctx = Context {
register: None,
count: std::num::NonZeroUsize::new(1),
Expand Down
11 changes: 8 additions & 3 deletions helix-term/src/commands/dap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ fn thread_picker(
let picker = FilePicker::new(
threads,
thread_states,
move |cx, thread, _action| callback_fn(cx.editor, thread),
move |cx, thread, _action| {
let Some(t) = thread else { return };
callback_fn(cx.editor, t)
},
move |editor, thread| {
let frames = editor.debugger.as_ref()?.stack_frames.get(&thread.id)?;
let frame = frames.get(0)?;
Expand Down Expand Up @@ -273,7 +276,8 @@ pub fn dap_launch(cx: &mut Context) {
cx.push_layer(Box::new(overlayed(Picker::new(
templates,
(),
|cx, template, _action| {
|cx, debug_template, _action| {
let Some(template) = debug_template else { return };
let completions = template.completion.clone();
let name = template.name.clone();
let callback = Box::pin(async move {
Expand Down Expand Up @@ -731,7 +735,8 @@ pub fn dap_switch_stack_frame(cx: &mut Context) {
let picker = FilePicker::new(
frames,
(),
move |cx, frame, _action| {
move |cx, stack_frame, _action| {
let Some(frame) = stack_frame else { return };
let debugger = debugger!(cx.editor);
// TODO: this should be simpler to find
let pos = debugger.stack_frames[&thread_id]
Expand Down
16 changes: 13 additions & 3 deletions helix-term/src/commands/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,8 @@ fn sym_picker(
FilePicker::new(
symbols,
current_path.clone(),
move |cx, symbol, action| {
move |cx, symbol_information, action| {
let Some(symbol) = symbol_information else { return };
let (view, doc) = current!(cx.editor);
push_jump(view, doc);

Expand Down Expand Up @@ -293,7 +294,8 @@ fn diag_picker(
FilePicker::new(
flat_diag,
(styles, format),
move |cx, PickerDiagnostic { url, diag }, action| {
move |cx, picker_diagnostic, action| {
let Some(PickerDiagnostic { url, diag }) = picker_diagnostic else { return };
if current_path.as_ref() == Some(url) {
let (view, doc) = current!(cx.editor);
push_jump(view, doc);
Expand Down Expand Up @@ -484,6 +486,13 @@ impl ui::menu::Item for lsp::CodeActionOrCommand {
}
}

impl ui::menu::Item for lsp::MessageActionItem {
type Data = ();
fn format(&self, _data: &Self::Data) -> Row {
self.title.as_str().into()
}
}

/// Determines the category of the `CodeAction` using the `CodeAction::kind` field.
/// Returns a number that represent these categories.
/// Categories with a lower number should be displayed first.
Expand Down Expand Up @@ -951,7 +960,8 @@ fn goto_impl(
locations,
cwdir,
move |cx, location, action| {
jump_to_location(cx.editor, location, offset_encoding, action)
let Some(l) = location else { return };
jump_to_location(cx.editor, l, offset_encoding, action)
},
move |_editor, location| Some(location_to_file_location(location)),
);
Expand Down
3 changes: 2 additions & 1 deletion helix-term/src/commands/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1333,7 +1333,8 @@ fn lsp_workspace_command(
let call: job::Callback = Callback::EditorCompositor(Box::new(
move |_editor: &mut Editor, compositor: &mut Compositor| {
let picker = ui::Picker::new(commands, (), |cx, command, _action| {
execute_lsp_command(cx.editor, command.clone());
let Some(c) = command else { return };
execute_lsp_command(cx.editor, c.clone());
});
compositor.push(Box::new(overlayed(picker)))
},
Expand Down
3 changes: 2 additions & 1 deletion helix-term/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,8 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi
FilePicker::new(
files,
root,
move |cx, path: &PathBuf, action| {
move |cx, path_buff, action| {
let Some(path) = path_buff else { return };
if let Err(e) = cx.editor.open(path, action) {
let err = if let Some(err) = e.source() {
format!("{}", err)
Expand Down
Loading