Schuyler Mortimer 5 months ago
parent 348c0ebeb4
commit 877c332439

@ -665,6 +665,11 @@ impl Client {
..Default::default() ..Default::default()
}), }),
window: Some(lsp::WindowClientCapabilities { 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), work_done_progress: Some(true),
..Default::default() ..Default::default()
}), }),

@ -567,6 +567,7 @@ pub enum MethodCall {
RegisterCapability(lsp::RegistrationParams), RegisterCapability(lsp::RegistrationParams),
UnregisterCapability(lsp::UnregistrationParams), UnregisterCapability(lsp::UnregistrationParams),
ShowDocument(lsp::ShowDocumentParams), ShowDocument(lsp::ShowDocumentParams),
ShowMessageRequest(lsp::ShowMessageRequestParams),
} }
impl MethodCall { impl MethodCall {
@ -598,6 +599,9 @@ impl MethodCall {
let params: lsp::ShowDocumentParams = params.parse()?; let params: lsp::ShowDocumentParams = params.parse()?;
Self::ShowDocument(params) Self::ShowDocument(params)
} }
lsp::request::ShowMessageRequest::METHOD => {
Self::ShowMessageRequest(params.parse::<lsp::ShowMessageRequestParams>()?)
}
_ => { _ => {
return Err(Error::Unhandled); return Err(Error::Unhandled);
} }

@ -1,8 +1,8 @@
use arc_swap::{access::Map, ArcSwap}; use arc_swap::{access::Map, ArcSwap};
use futures_util::Stream; use futures_util::Stream;
use helix_core::{diagnostic::Severity, pos_at_coords, syntax, Selection}; use helix_core::{diagnostic::Severity, pos_at_coords, syntax, Rope, Selection};
use helix_lsp::{ use helix_lsp::{
lsp::{self, notification::Notification}, lsp::{self, notification::Notification, MessageType},
util::lsp_range_to_range, util::lsp_range_to_range,
LanguageServerId, LspProgressMap, LanguageServerId, LspProgressMap,
}; };
@ -26,7 +26,7 @@ use crate::{
handlers, handlers,
job::Jobs, job::Jobs,
keymap::Keymaps, keymap::Keymaps,
ui::{self, overlay::overlaid}, ui::{self, overlay::overlaid, FileLocation, Picker, Popup},
}; };
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
@ -960,11 +960,11 @@ impl Application {
"Language Server: Method {} not found in request {}", "Language Server: Method {} not found in request {}",
method, id method, id
); );
Err(helix_lsp::jsonrpc::Error { Some(Err(helix_lsp::jsonrpc::Error {
code: helix_lsp::jsonrpc::ErrorCode::MethodNotFound, code: helix_lsp::jsonrpc::ErrorCode::MethodNotFound,
message: format!("Method not found: {}", method), message: format!("Method not found: {}", method),
data: None, data: None,
}) }))
} }
Err(err) => { Err(err) => {
log::error!( log::error!(
@ -973,11 +973,11 @@ impl Application {
id, id,
err err
); );
Err(helix_lsp::jsonrpc::Error { Some(Err(helix_lsp::jsonrpc::Error {
code: helix_lsp::jsonrpc::ErrorCode::ParseError, code: helix_lsp::jsonrpc::ErrorCode::ParseError,
message: format!("Malformed method call: {}", method), message: format!("Malformed method call: {}", method),
data: None, data: None,
}) }))
} }
Ok(MethodCall::WorkDoneProgressCreate(params)) => { Ok(MethodCall::WorkDoneProgressCreate(params)) => {
self.lsp_progress.create(server_id, params.token); self.lsp_progress.create(server_id, params.token);
@ -991,7 +991,7 @@ impl Application {
spinner.start(); spinner.start();
} }
Ok(serde_json::Value::Null) Some(Ok(serde_json::Value::Null))
} }
Ok(MethodCall::ApplyWorkspaceEdit(params)) => { Ok(MethodCall::ApplyWorkspaceEdit(params)) => {
let language_server = language_server!(); let language_server = language_server!();
@ -1001,25 +1001,73 @@ impl Application {
.editor .editor
.apply_workspace_edit(offset_encoding, &params.edit); .apply_workspace_edit(offset_encoding, &params.edit);
Ok(json!(lsp::ApplyWorkspaceEditResponse { Some(Ok(json!(lsp::ApplyWorkspaceEditResponse {
applied: res.is_ok(), applied: res.is_ok(),
failure_reason: res.as_ref().err().map(|err| err.kind.to_string()), failure_reason: res.as_ref().err().map(|err| err.kind.to_string()),
failed_change: res failed_change: res
.as_ref() .as_ref()
.err() .err()
.map(|err| err.failed_change_idx as u32), .map(|err| err.failed_change_idx as u32),
})) })))
} else { } else {
Err(helix_lsp::jsonrpc::Error { Some(Err(helix_lsp::jsonrpc::Error {
code: helix_lsp::jsonrpc::ErrorCode::InvalidRequest, code: helix_lsp::jsonrpc::ErrorCode::InvalidRequest,
message: "Server must be initialized to request workspace edits" message: "Server must be initialized to request workspace edits"
.to_string(), .to_string(),
data: None, data: None,
}) }))
} }
} }
Ok(MethodCall::WorkspaceFolders) => { Ok(MethodCall::WorkspaceFolders) => {
Ok(json!(&*language_server!().workspace_folders().await)) Some(Ok(json!(&*language_server!().workspace_folders().await)))
}
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 =
Picker::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)),
);
})
.with_preview(
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)) => { Ok(MethodCall::WorkspaceConfiguration(params)) => {
let language_server = language_server!(); let language_server = language_server!();
@ -1039,7 +1087,7 @@ impl Application {
Some(config) Some(config)
}) })
.collect(); .collect();
Ok(json!(result)) Some(Ok(json!(result)))
} }
Ok(MethodCall::RegisterCapability(params)) => { Ok(MethodCall::RegisterCapability(params)) => {
if let Some(client) = self.editor.language_servers.get_by_id(server_id) { if let Some(client) = self.editor.language_servers.get_by_id(server_id) {
@ -1077,7 +1125,7 @@ impl Application {
} }
} }
Ok(serde_json::Value::Null) Some(Ok(serde_json::Value::Null))
} }
Ok(MethodCall::UnregisterCapability(params)) => { Ok(MethodCall::UnregisterCapability(params)) => {
for unreg in params.unregisterations { for unreg in params.unregisterations {
@ -1093,18 +1141,26 @@ impl Application {
} }
} }
} }
Ok(serde_json::Value::Null) Some(Ok(serde_json::Value::Null))
} }
Ok(MethodCall::ShowDocument(params)) => { Ok(MethodCall::ShowDocument(params)) => {
let language_server = language_server!(); let language_server = language_server!();
let offset_encoding = language_server.offset_encoding(); let offset_encoding = language_server.offset_encoding();
let result = self.handle_show_document(params, offset_encoding); let result = self.handle_show_document(params, offset_encoding);
Ok(json!(result)) Some(Ok(json!(result)))
} }
}; };
if let Some(reply) = reply {
tokio::spawn(language_server!().reply(id, reply)); let lanaguage_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(lanaguage_server.reply(id, reply));
}
} }
Call::Invalid { id } => log::error!("LSP invalid method call id={:?}", id), Call::Invalid { id } => log::error!("LSP invalid method call id={:?}", id),
} }

@ -2425,7 +2425,10 @@ fn global_search(cx: &mut Context) {
let picker = Picker::with_stream( let picker = Picker::with_stream(
picker, picker,
injector, injector,
move |cx, FileResult { path, line_num }, action| { move |cx, file_result, action| {
let Some(FileResult { path, line_num }) = file_result else {
return;
};
let doc = match cx.editor.open(path, action) { let doc = match cx.editor.open(path, action) {
Ok(id) => doc_mut!(cx.editor, &id), Ok(id) => doc_mut!(cx.editor, &id),
Err(e) => { Err(e) => {
@ -2941,7 +2944,8 @@ fn buffer_picker(cx: &mut Context) {
// mru // mru
items.sort_unstable_by_key(|item| std::cmp::Reverse(item.focused_at)); items.sort_unstable_by_key(|item| std::cmp::Reverse(item.focused_at));
let picker = Picker::new(items, (), |cx, meta, action| { let picker = Picker::new(items, (), |cx, buffer_meta, action| {
let Some(meta) = buffer_meta else { return };
cx.editor.switch(meta.id, action); cx.editor.switch(meta.id, action);
}) })
.with_preview(|editor, meta| { .with_preview(|editor, meta| {
@ -3030,7 +3034,8 @@ fn jumplist_picker(cx: &mut Context) {
}) })
.collect(), .collect(),
(), (),
|cx, meta, action| { |cx, jump_meta, action| {
let Some(meta) = jump_meta else { return };
cx.editor.switch(meta.id, action); cx.editor.switch(meta.id, action);
let config = cx.editor.config(); let config = cx.editor.config();
let (view, doc) = (view_mut!(cx.editor), doc_mut!(cx.editor, &meta.id)); let (view, doc) = (view_mut!(cx.editor), doc_mut!(cx.editor, &meta.id));
@ -3108,7 +3113,8 @@ fn changed_file_picker(cx: &mut Context) {
style_deleted: deleted, style_deleted: deleted,
style_renamed: renamed, style_renamed: renamed,
}, },
|cx, meta: &FileChange, action| { |cx, file_meta: Option<&FileChange>, action| {
let Some(meta) = file_meta else { return };
let path_to_open = meta.path(); let path_to_open = meta.path();
if let Err(e) = cx.editor.open(path_to_open, action) { if let Err(e) = cx.editor.open(path_to_open, action) {
let err = if let Some(err) = e.source() { let err = if let Some(err) = e.source() {
@ -3184,7 +3190,10 @@ 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 { let mut ctx = Context {
register, register,
count, count,

@ -74,7 +74,8 @@ fn thread_picker(
let thread_states = debugger.thread_states.clone(); let thread_states = debugger.thread_states.clone();
let picker = Picker::new(threads, thread_states, move |cx, thread, _action| { let picker = Picker::new(threads, thread_states, move |cx, thread, _action| {
callback_fn(cx.editor, thread) let Some(t) = thread else { return };
callback_fn(cx.editor, t)
}) })
.with_preview(move |editor, thread| { .with_preview(move |editor, thread| {
let frames = editor.debugger.as_ref()?.stack_frames.get(&thread.id)?; let frames = editor.debugger.as_ref()?.stack_frames.get(&thread.id)?;
@ -271,7 +272,10 @@ pub fn dap_launch(cx: &mut Context) {
cx.push_layer(Box::new(overlaid(Picker::new( cx.push_layer(Box::new(overlaid(Picker::new(
templates, templates,
(), (),
|cx, template, _action| { |cx, debug_template, _action| {
let Some(template) = debug_template else {
return;
};
if template.completion.is_empty() { if template.completion.is_empty() {
if let Err(err) = dap_start_impl(cx, Some(&template.name), None, None) { if let Err(err) = dap_start_impl(cx, Some(&template.name), None, None) {
cx.editor.set_error(err.to_string()); cx.editor.set_error(err.to_string());
@ -736,7 +740,8 @@ pub fn dap_switch_stack_frame(cx: &mut Context) {
let frames = debugger.stack_frames[&thread_id].clone(); let frames = debugger.stack_frames[&thread_id].clone();
let picker = Picker::new(frames, (), move |cx, frame, _action| { let picker = Picker::new(frames, (), move |cx, stack_frame, _action| {
let Some(frame) = stack_frame else { return };
let debugger = debugger!(cx.editor); let debugger = debugger!(cx.editor);
// TODO: this should be simpler to find // TODO: this should be simpler to find
let pos = debugger.stack_frames[&thread_id] let pos = debugger.stack_frames[&thread_id]

@ -245,7 +245,8 @@ type SymbolPicker = Picker<SymbolInformationItem>;
fn sym_picker(symbols: Vec<SymbolInformationItem>, current_path: Option<lsp::Url>) -> SymbolPicker { fn sym_picker(symbols: Vec<SymbolInformationItem>, current_path: Option<lsp::Url>) -> SymbolPicker {
// TODO: drop current_path comparison and instead use workspace: bool flag? // TODO: drop current_path comparison and instead use workspace: bool flag?
Picker::new(symbols, current_path, move |cx, item, action| { Picker::new(symbols, current_path, move |cx, symbol_item, action| {
let Some(item) = symbol_item else { return };
jump_to_location( jump_to_location(
cx.editor, cx.editor,
&item.symbol.location, &item.symbol.location,
@ -296,13 +297,15 @@ fn diag_picker(
Picker::new( Picker::new(
flat_diag, flat_diag,
(styles, format), (styles, format),
move |cx, move |cx, picker_diagnostic, action| {
PickerDiagnostic { let Some(PickerDiagnostic {
path, path,
diag, diag,
offset_encoding, offset_encoding,
}, }) = picker_diagnostic
action| { else {
return;
};
jump_to_position(cx.editor, path, diag.range, *offset_encoding, action) jump_to_position(cx.editor, path, diag.range, *offset_encoding, action)
}, },
) )
@ -509,6 +512,13 @@ impl ui::menu::Item for CodeActionOrCommandItem {
} }
} }
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. /// Determines the category of the `CodeAction` using the `CodeAction::kind` field.
/// Returns a number that represent these categories. /// Returns a number that represent these categories.
/// Categories with a lower number should be displayed first. /// Categories with a lower number should be displayed first.
@ -818,7 +828,8 @@ fn goto_impl(
[] => unreachable!("`locations` should be non-empty for `goto_impl`"), [] => unreachable!("`locations` should be non-empty for `goto_impl`"),
_locations => { _locations => {
let picker = Picker::new(locations, cwdir, move |cx, location, action| { let picker = Picker::new(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)
}) })
.with_preview(move |_editor, location| Some(location_to_file_location(location))); .with_preview(move |_editor, location| Some(location_to_file_location(location)));
compositor.push(Box::new(overlaid(picker))); compositor.push(Box::new(overlaid(picker)));

@ -1413,7 +1413,8 @@ fn lsp_workspace_command(
commands, commands,
(), (),
move |cx, LsIdCommand(ls_id, command), _action| { move |cx, LsIdCommand(ls_id, command), _action| {
execute_lsp_command(cx.editor, *ls_id, command.clone()); let Some(c) = command else { return };
execute_lsp_command(cx.editor, *ls_id, c.clone());
}, },
); );
compositor.push(Box::new(overlaid(picker))) compositor.push(Box::new(overlaid(picker)))

@ -217,16 +217,21 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> Picker
}); });
log::debug!("file_picker init {:?}", Instant::now().duration_since(now)); log::debug!("file_picker init {:?}", Instant::now().duration_since(now));
let picker = Picker::new(Vec::new(), root, move |cx, path: &PathBuf, action| { let picker = Picker::new(
if let Err(e) = cx.editor.open(path, action) { Vec::new(),
let err = if let Some(err) = e.source() { root,
format!("{}", err) move |cx, path_buff: Option<&PathBuf>, action| {
} else { let Some(path) = path_buff else { return };
format!("unable to open \"{}\"", path.display()) if let Err(e) = cx.editor.open(path, action) {
}; let err = if let Some(err) = e.source() {
cx.editor.set_error(err); format!("{}", err)
} } else {
}) format!("unable to open \"{}\"", path.display())
};
cx.editor.set_error(err);
}
},
)
.with_preview(|_editor, path| Some((path.clone().into(), None))); .with_preview(|_editor, path| Some((path.clone().into(), None)));
let injector = picker.injector(); let injector = picker.injector();
let timeout = std::time::Instant::now() + std::time::Duration::from_millis(30); let timeout = std::time::Instant::now() + std::time::Duration::from_millis(30);

@ -35,7 +35,7 @@ use std::{
use crate::ui::{Prompt, PromptEvent}; use crate::ui::{Prompt, PromptEvent};
use helix_core::{ use helix_core::{
char_idx_at_visual_offset, fuzzy::MATCHER, movement::Direction, char_idx_at_visual_offset, fuzzy::MATCHER, movement::Direction,
text_annotations::TextAnnotations, unicode::segmentation::UnicodeSegmentation, Position, text_annotations::TextAnnotations, unicode::segmentation::UnicodeSegmentation, Position, Rope,
Syntax, Syntax,
}; };
use helix_view::{ use helix_view::{
@ -57,6 +57,7 @@ pub const MAX_FILE_SIZE_FOR_PREVIEW: u64 = 10 * 1024 * 1024;
pub enum PathOrId { pub enum PathOrId {
Id(DocumentId), Id(DocumentId),
Path(PathBuf), Path(PathBuf),
Text(Rope),
} }
impl PathOrId { impl PathOrId {
@ -65,10 +66,17 @@ impl PathOrId {
match self { match self {
Path(path) => Path(helix_stdx::path::canonicalize(path)), Path(path) => Path(helix_stdx::path::canonicalize(path)),
Id(id) => Id(id), Id(id) => Id(id),
Text(rope) => Text(rope),
} }
} }
} }
impl From<Rope> for PathOrId {
fn from(text: Rope) -> Self {
Self::Text(text)
}
}
impl From<PathBuf> for PathOrId { impl From<PathBuf> for PathOrId {
fn from(v: PathBuf) -> Self { fn from(v: PathBuf) -> Self {
Self::Path(v) Self::Path(v)
@ -98,6 +106,7 @@ pub enum CachedPreview {
pub enum Preview<'picker, 'editor> { pub enum Preview<'picker, 'editor> {
Cached(&'picker CachedPreview), Cached(&'picker CachedPreview),
EditorDocument(&'editor Document), EditorDocument(&'editor Document),
Text(Box<Document>),
} }
impl Preview<'_, '_> { impl Preview<'_, '_> {
@ -105,6 +114,7 @@ impl Preview<'_, '_> {
match self { match self {
Preview::EditorDocument(doc) => Some(doc), Preview::EditorDocument(doc) => Some(doc),
Preview::Cached(CachedPreview::Document(doc)) => Some(doc), Preview::Cached(CachedPreview::Document(doc)) => Some(doc),
Preview::Text(doc) => Some(doc.as_ref()),
_ => None, _ => None,
} }
} }
@ -112,6 +122,7 @@ impl Preview<'_, '_> {
/// Alternate text to show for the preview. /// Alternate text to show for the preview.
fn placeholder(&self) -> &str { fn placeholder(&self) -> &str {
match *self { match *self {
Self::Text(_) => "<Text>",
Self::EditorDocument(_) => "<Invalid file location>", Self::EditorDocument(_) => "<Invalid file location>",
Self::Cached(preview) => match preview { Self::Cached(preview) => match preview {
CachedPreview::Document(_) => "<Invalid file location>", CachedPreview::Document(_) => "<Invalid file location>",
@ -222,7 +233,7 @@ impl<T: Item + 'static> Picker<T> {
pub fn new( pub fn new(
options: Vec<T>, options: Vec<T>,
editor_data: T::Data, editor_data: T::Data,
callback_fn: impl Fn(&mut Context, &T, Action) + 'static, callback_fn: impl Fn(&mut Context, Option<&T>, Action) + 'static,
) -> Self { ) -> Self {
let matcher = Nucleo::new( let matcher = Nucleo::new(
Config::DEFAULT, Config::DEFAULT,
@ -247,7 +258,7 @@ impl<T: Item + 'static> Picker<T> {
pub fn with_stream( pub fn with_stream(
matcher: Nucleo<T>, matcher: Nucleo<T>,
injector: Injector<T>, injector: Injector<T>,
callback_fn: impl Fn(&mut Context, &T, Action) + 'static, callback_fn: impl Fn(&mut Context, Option<&T>, Action) + 'static,
) -> Self { ) -> Self {
Self::with(matcher, injector.editor_data, injector.shutown, callback_fn) Self::with(matcher, injector.editor_data, injector.shutown, callback_fn)
} }
@ -256,7 +267,7 @@ impl<T: Item + 'static> Picker<T> {
matcher: Nucleo<T>, matcher: Nucleo<T>,
editor_data: Arc<T::Data>, editor_data: Arc<T::Data>,
shutdown: Arc<AtomicBool>, shutdown: Arc<AtomicBool>,
callback_fn: impl Fn(&mut Context, &T, Action) + 'static, callback_fn: impl Fn(&mut Context, Option<&T>, Action) + 'static,
) -> Self { ) -> Self {
let prompt = Prompt::new( let prompt = Prompt::new(
"".into(), "".into(),
@ -440,6 +451,10 @@ impl<T: Item + 'static> Picker<T> {
let doc = editor.documents.get(&id).unwrap(); let doc = editor.documents.get(&id).unwrap();
Preview::EditorDocument(doc) Preview::EditorDocument(doc)
} }
PathOrId::Text(rope) => {
let doc = Document::from(rope, None, editor.config.clone());
Preview::Text(Box::new(doc))
}
} }
} }
@ -455,6 +470,7 @@ impl<T: Item + 'static> Picker<T> {
Some(CachedPreview::Document(ref mut doc)) => doc, Some(CachedPreview::Document(ref mut doc)) => doc,
_ => return EventResult::Consumed(None), _ => return EventResult::Consumed(None),
}, },
PathOrId::Text(_) => return EventResult::Consumed(None),
}; };
let mut callback: Option<compositor::Callback> = None; let mut callback: Option<compositor::Callback> = None;
@ -502,6 +518,7 @@ impl<T: Item + 'static> Picker<T> {
} }
_ => return, _ => return,
}, },
PathOrId::Text(_rope) => return,
}; };
doc.syntax = Some(syntax); doc.syntax = Some(syntax);
}; };
@ -885,26 +902,18 @@ impl<T: Item + 'static + Send + Sync> Component for Picker<T> {
} }
key!(Esc) | ctrl!('c') => return close_fn(self), key!(Esc) | ctrl!('c') => return close_fn(self),
alt!(Enter) => { alt!(Enter) => {
if let Some(option) = self.selection() { (self.callback_fn)(ctx, self.selection(), Action::Load);
(self.callback_fn)(ctx, option, Action::Load);
}
} }
key!(Enter) => { key!(Enter) => {
if let Some(option) = self.selection() { (self.callback_fn)(ctx, self.selection(), Action::Replace);
(self.callback_fn)(ctx, option, Action::Replace);
}
return close_fn(self); return close_fn(self);
} }
ctrl!('s') => { ctrl!('s') => {
if let Some(option) = self.selection() { (self.callback_fn)(ctx, self.selection(), Action::HorizontalSplit);
(self.callback_fn)(ctx, option, Action::HorizontalSplit);
}
return close_fn(self); return close_fn(self);
} }
ctrl!('v') => { ctrl!('v') => {
if let Some(option) = self.selection() { (self.callback_fn)(ctx, self.selection(), Action::VerticalSplit);
(self.callback_fn)(ctx, option, Action::VerticalSplit);
}
return close_fn(self); return close_fn(self);
} }
ctrl!('t') => { ctrl!('t') => {
@ -945,7 +954,7 @@ impl<T: Item> Drop for Picker<T> {
} }
} }
type PickerCallback<T> = Box<dyn Fn(&mut Context, &T, Action)>; type PickerCallback<T> = Box<dyn Fn(&mut Context, Option<&T>, Action)>;
/// Returns a new list of options to replace the contents of the picker /// Returns a new list of options to replace the contents of the picker
/// when called with the current picker query, /// when called with the current picker query,

Loading…
Cancel
Save