Compare commits

...

60 Commits

Author SHA1 Message Date
Nik Revenco 309153bc1f
Merge 9fe397bd79 into 4281228da3 2025-07-24 17:35:39 +00:00
Valtteri Koskivuori 4281228da3
fix(queries): Fix filesystem permissions for snakemake (#14061) 2025-07-24 13:09:40 -04:00
Nik Revenco 9fe397bd79 fix: merge conflict 2025-06-07 12:28:00 +01:00
Nik Revenco 77eb2f95e8 Merge branch 'master' into powerful-file-explorer 2025-06-07 12:27:05 +01:00
Nik Revenco 3e5ead4200 feat: move cursor of `move` file explorer operation before the extension 2025-04-04 09:41:53 +01:00
Nik Revenco 3aea7bba74 refactor: create Editor::set_result 2025-04-04 09:15:34 +01:00
Nik Revenco 09021fdf30 refactor: extract file explorer into a separate module 2025-04-04 09:04:43 +01:00
Nik Revenco a69ca67e88 refactor: remove unneeded fields on Editor 2025-04-04 08:42:36 +01:00
Nikita Revenco 7b89fb4699 refactor: remove extra passage of arguments (unnecessary) 2025-03-04 13:22:40 +00:00
Nikita Revenco 386c4220fd feat: create parent directories of file if they do not exist 2025-03-04 12:49:14 +00:00
Nikita Revenco 27ad3f281c feat: use arrow -> for Copy and Move instead of colon 2025-03-04 12:20:58 +00:00
Nikita Revenco e7d7f93d63 feat: clear status message when receiving no message or error from performing file operation 2025-03-04 12:19:23 +00:00
Nikita Revenco 42d74bb9fa feat: tell user which path they are operating on 2025-03-04 12:19:02 +00:00
Nikita Revenco 4133e14a5e feat: notify LSPs when moving path in File Explorer 2025-02-28 14:33:28 +00:00
Nikita Revenco 674afbfd89 refactor: inversion of control 2025-02-22 13:31:35 +00:00
Nikita Revenco 59a1d244aa refactor: rename vars 2025-02-22 13:12:21 +00:00
Nikita Revenco 7a52c3f0bf fix: delete directories 2025-02-20 17:24:59 +00:00
Nikita Revenco d55b8f3242 feat: add file explorer keymappings to docs 2025-02-18 17:07:09 +00:00
Nikita Revenco 488e9552fd refactor: use type aliases instead of fully writing out the type 2025-02-18 17:02:41 +00:00
Nikita Revenco fae93aa308 refactor: move statement elsewhere 2025-02-18 17:02:10 +00:00
Nikita Revenco a6e110937b refactor: do not explicitlys specify the types everywhere 2025-02-18 16:57:45 +00:00
Nikita Revenco 984ad4bca9 refactor: pass EditorData to callbacks, do not "compute" the root 2025-02-18 16:49:00 +00:00
Nikita Revenco 67ca955baa refactor: extract into a function 2025-02-18 15:53:56 +00:00
Nikita Revenco f193705ca7 feat: add main separator when showing current file's directory 2025-02-18 15:38:56 +00:00
Nikita Revenco 7fdf2ba92a refactor: simplify function 2025-02-18 15:20:34 +00:00
Nikita Revenco eb35b604b5 fix: remove previous pickers when refreshing the current one 2025-02-18 15:17:01 +00:00
Nikita Revenco ed570d9f45 fix: use MAIN_SEPARATOR instead of just unix separator 2025-02-18 15:02:38 +00:00
Nikita Revenco a9612dad1d refactor: extract into a type alias 2025-02-18 14:52:49 +00:00
Nikita Revenco 6dbb09f1fa style: format mod.rs 2025-02-18 14:45:13 +00:00
Nikita Revenco e6e80e2185 fix: remove unneeded panics 2025-02-18 14:27:11 +00:00
Nikita Revenco a97ebc2ed0 style: format 2025-02-18 14:17:31 +00:00
Nikita Revenco 4fabd7927d refactor: remove unneeded macro 2025-02-18 14:08:16 +00:00
Nikita Revenco 24bd14863b feat: restore cursor when performing file operations 2025-02-18 14:04:10 +00:00
Nikita Revenco 9227267aa3 chore: remove TODO comment 2025-02-18 13:52:09 +00:00
Nikita Revenco 8eac1c2721 feat: refresh picker when directory operations are performed 2025-02-18 13:11:37 +00:00
Nikita Revenco fac6c7c1a6 chore: appease clippy 2025-02-18 12:38:27 +00:00
Nikita Revenco b6bbd4f18a fix: delete path, not confirmation e.g. `y` 2025-02-18 12:38:11 +00:00
Nikita Revenco 6a558be7b3 feat: better initial prompts when using file picker commands 2025-02-18 12:36:54 +00:00
Nikita Revenco 0e6e3e8aeb refactor: rename variable 2025-02-18 12:24:45 +00:00
Nikita Revenco cd6584f0ab chore: appease clippy 2025-02-18 12:20:37 +00:00
Nikita Revenco baddacfe20 docs: add file explorer keymap info 2025-02-18 12:19:20 +00:00
Nikita Revenco 382803c803 feat: add confirmation prompt when overwriting 2025-02-18 12:15:35 +00:00
Nikita Revenco e177c48208 refactor: use Option<Result> to indicate if a status message should not be changed 2025-02-18 11:42:03 +00:00
Nikita Revenco a099ae1dbe style: formatting 2025-02-18 11:33:01 +00:00
Nikita Revenco eafd8ace18 style: formatting 2025-02-18 11:28:30 +00:00
Nikita Revenco eecabdbeb5 feat: pass context to all callbacks in file operations 2025-02-18 11:16:15 +00:00
Nikita Revenco 7fd7b7274a feat: implement copy path of selected item 2025-02-18 11:11:02 +00:00
Nikita Revenco b3d0f16276 feat: use display method on paths 2025-02-18 10:48:38 +00:00
Nikita Revenco 835cda11f1 refactor: variable renaming 2025-02-18 10:27:58 +00:00
Nikita Revenco a96841dac1 feat: implement copying 2025-02-17 23:54:37 +00:00
Nikita Revenco 469115e5ee feat: implement delete files 2025-02-17 23:46:04 +00:00
Nikita Revenco f59c5966f6 feat: implement creating new files and directory 2025-02-17 23:37:58 +00:00
Nikita Revenco 2eef82e4df feat: implement basic callback functions for prompt operarions 2025-02-17 23:08:17 +00:00
Nikita Revenco 7dc631de9a chore: allow macro to destructure 2025-02-17 22:24:13 +00:00
Nikita Revenco 43f40d318f chore: add TODO comments 2025-02-17 22:08:31 +00:00
Nikita Revenco d5fb7b2999 refactor: improve the declare_key_handlers macro 2025-02-17 21:54:45 +00:00
Nikita Revenco 5d29a175f3 refactor: utility macro to declare multiple handlers with ease 2025-02-17 21:41:23 +00:00
Nikita Revenco 9a28d4fa42 feat: create operations for create, delete, copy, rename in file explorer 2025-02-17 21:22:51 +00:00
Nikita Revenco f4e5c26112 feat: figure out how to pass custom callback function set by keymap to picker 2025-02-17 21:09:04 +00:00
Nikita Revenco 87b5bd58bc feat: add API to register additional hooks with Pickrs 2025-02-17 20:53:14 +00:00
12 changed files with 480 additions and 53 deletions

View File

@ -286,6 +286,8 @@ This layer is a kludge of mappings, mostly pickers.
| ----- | ----------- | ------- |
| `f` | Open file picker at LSP workspace root | `file_picker` |
| `F` | Open file picker at current working directory | `file_picker_in_current_directory` |
| `e` | Open file explorer at LSP workspace root | `file_explorer` |
| `E` | Open file explorer at the opened file's directory | `file_explorer_in_current_buffer_directory`|
| `b` | Open buffer picker | `buffer_picker` |
| `j` | Open jumplist picker | `jumplist_picker` |
| `g` | Open changed file picker | `changed_file_picker` |
@ -466,6 +468,18 @@ See the documentation page on [pickers](./pickers.md) for more info.
| `Ctrl-t` | Toggle preview |
| `Escape`, `Ctrl-c` | Close picker |
### File Explorer
There are additional keys accessible when using the File Explorer (`Space-e` and `Space-E`).
| Key | Description |
| ----- | ------------- |
| `Alt-m` | Move selected file or directory |
| `Alt-n` | Create a new file or directory |
| `Alt-d` | Delete the selected file or directory |
| `Alt-c` | Copy the selected file |
| `Alt-y` | Yank the path to the selected file or directory |
## Prompt
Keys to use within prompt, Remapping currently not supported.

View File

@ -3099,7 +3099,7 @@ fn file_explorer(cx: &mut Context) {
return;
}
if let Ok(picker) = ui::file_explorer(root, cx.editor) {
if let Ok(picker) = ui::file_explorer(None, root, cx.editor) {
cx.push_layer(Box::new(overlaid(picker)));
}
}
@ -3126,7 +3126,7 @@ fn file_explorer_in_current_buffer_directory(cx: &mut Context) {
}
};
if let Ok(picker) = ui::file_explorer(path, cx.editor) {
if let Ok(picker) = ui::file_explorer(None, path, cx.editor) {
cx.push_layer(Box::new(overlaid(picker)));
}
}
@ -3139,7 +3139,7 @@ fn file_explorer_in_current_directory(cx: &mut Context) {
return;
}
if let Ok(picker) = ui::file_explorer(cwd, cx.editor) {
if let Ok(picker) = ui::file_explorer(None, cwd, cx.editor) {
cx.push_layer(Box::new(overlaid(picker)));
}
}

View File

@ -0,0 +1,422 @@
use std::error::Error as _;
use std::{
fs,
path::{Path, PathBuf},
};
use helix_core::hashmap;
use helix_view::{theme::Style, Editor};
use tui::text::Span;
use crate::{alt, compositor::Context, job::Callback};
use super::prompt::Movement;
use super::{
directory_content, overlay, picker::PickerKeyHandler, Picker, PickerColumn, Prompt, PromptEvent,
};
/// for each path: (path to item, is the path a directory?)
type ExplorerItem = (PathBuf, bool);
/// (file explorer root, directory style)
type ExplorerData = (PathBuf, Style);
type FileExplorer = Picker<ExplorerItem, ExplorerData>;
type KeyHandler = PickerKeyHandler<ExplorerItem, ExplorerData>;
/// Create a prompt that asks for the user's confirmation before overwriting a path
fn confirm_before_overwriting<F>(
// Path that we are overwriting
overwriting: PathBuf,
// Overwrite this path with
overwrite_with: PathBuf,
cx: &mut Context,
picker_root: PathBuf,
overwrite: F,
) -> Option<Result<String, String>>
where
F: Fn(&mut Context, PathBuf, &Path) -> Option<Result<String, String>> + Send + 'static,
{
// No need for confirmation, as the path does not exist. We can freely write to it
if !overwriting.exists() {
return overwrite(cx, picker_root, &overwrite_with);
}
let callback = Box::pin(async move {
let call: Callback = Callback::EditorCompositor(Box::new(move |_editor, compositor| {
let prompt = Prompt::new(
format!(
"Path {} already exists. Ovewrite? (y/n):",
overwriting.display()
)
.into(),
None,
crate::ui::completers::none,
move |cx, input: &str, event: PromptEvent| {
if event != PromptEvent::Validate || input != "y" {
return;
};
if let Some(result) = overwrite(cx, picker_root.clone(), &overwrite_with) {
cx.editor.set_result(result);
};
},
);
compositor.push(Box::new(prompt));
}));
Ok(call)
});
cx.jobs.callback(callback);
None
}
fn create_file_operation_prompt<F>(
cx: &mut Context,
// Currently selected path of the picker
path: &Path,
// Text value of the prompt
prompt: fn(&Path) -> String,
// How to move the cursor
movement: Option<Movement>,
// What to fill user's input with
prefill: fn(&Path) -> String,
// Action to take when the operation runs
file_op: F,
) where
F: Fn(&mut Context, &PathBuf, String) -> Option<Result<String, String>> + Send + 'static,
{
let selected_path = path.to_path_buf();
let callback = Box::pin(async move {
let call: Callback = Callback::EditorCompositor(Box::new(move |editor, compositor| {
// to be able to move selected_path
let path = selected_path.clone();
let mut prompt = Prompt::new(
prompt(&path).into(),
None,
crate::ui::completers::none,
move |cx, input: &str, event: PromptEvent| {
if event != PromptEvent::Validate {
return;
};
if let Some(result) = file_op(cx, &path, input.to_owned()) {
cx.editor.set_result(result);
} else {
cx.editor.clear_status();
};
},
);
prompt.set_line(prefill(&selected_path), editor);
if let Some(movement) = movement {
log::error!("{movement:?}");
prompt.move_cursor(movement);
}
compositor.push(Box::new(prompt));
}));
Ok(call)
});
cx.jobs.callback(callback);
}
fn refresh_file_explorer(cursor: u32, cx: &mut Context, root: PathBuf) {
let callback = Box::pin(async move {
let call: Callback = Callback::EditorCompositor(Box::new(move |editor, compositor| {
// replace the old file explorer with the new one
compositor.pop();
if let Ok(picker) = file_explorer(Some(cursor), root, editor) {
compositor.push(Box::new(overlay::overlaid(picker)));
}
}));
Ok(call)
});
cx.jobs.callback(callback);
}
pub fn file_explorer(
cursor: Option<u32>,
root: PathBuf,
editor: &Editor,
) -> Result<FileExplorer, std::io::Error> {
let directory_style = editor.theme.get("ui.text.directory");
let directory_content = directory_content(&root)?;
let yank_path: KeyHandler = Box::new(|cx, (path, _), _, _| {
let register = cx
.editor
.selected_register
.unwrap_or(cx.editor.config().default_yank_register);
let path = helix_stdx::path::get_relative_path(path);
let path = path.to_string_lossy().to_string();
let message = format!("Yanked path {} to register {register}", path);
match cx.editor.registers.write(register, vec![path]) {
Ok(()) => cx.editor.set_status(message),
Err(err) => cx.editor.set_error(err.to_string()),
};
});
let create: KeyHandler = Box::new(|cx, (path, _), data, cursor| {
create_file_operation_prompt(
cx,
path,
|_| "Create: ".into(),
None,
|path| {
path.parent()
.map(|p| format!("{}{}", p.display(), std::path::MAIN_SEPARATOR))
.unwrap_or_default()
},
move |cx, _, to_create_string| {
let root = data.0.clone();
let to_create = helix_stdx::path::expand_tilde(PathBuf::from(&to_create_string));
confirm_before_overwriting(
to_create.to_path_buf(),
to_create.to_path_buf(),
cx,
root,
move |cx: &mut Context, root: PathBuf, to_create: &Path| {
if to_create_string.ends_with(std::path::MAIN_SEPARATOR) {
if let Err(err_create_dir) =
fs::create_dir_all(to_create).map_err(|err| {
format!(
"Unable to create directory {}: {err}",
to_create.display()
)
})
{
return Some(Err(err_create_dir));
}
refresh_file_explorer(cursor, cx, root);
return Some(Ok(format!("Created directory: {}", to_create.display())));
}
// allows to create a path like /path/to/somewhere.txt even if "to" does not exist. Creates intermediate directories
let Some(to_create_parent) = to_create.parent() else {
return Some(Err(format!(
"Failed to get parent directory of {}",
to_create.display()
)));
};
if let Err(err_create_parent) = fs::create_dir_all(to_create_parent) {
return Some(Err(format!(
"Could not create intermediate directories: {err_create_parent}"
)));
}
if let Err(err_create_file) = fs::File::create(to_create).map_err(|err| {
format!("Unable to create file {}: {err}", to_create.display())
}) {
return Some(Err(err_create_file));
};
refresh_file_explorer(cursor, cx, root);
Some(Ok(format!("Created file: {}", to_create.display())))
},
)
},
)
});
let move_: KeyHandler = Box::new(|cx, (path, _), data, cursor| {
create_file_operation_prompt(
cx,
path,
|path| format!("Move {} -> ", path.display()),
// move cursor before the extension
// Yazi does this and it leads to good user experience
// Most of the time when we would like to rename a file we
// don't want to change its file extension
path.extension()
.inspect(|a| log::error!("{a:?}"))
// +1 to account for the dot in the extension (`.`)
.map(|ext| Movement::BackwardChar(ext.len() + 1)),
|path| path.display().to_string(),
move |cx, move_from, move_to_string| {
let root = data.0.clone();
let move_to = helix_stdx::path::expand_tilde(PathBuf::from(&move_to_string));
confirm_before_overwriting(
move_to.to_path_buf(),
move_from.to_path_buf(),
cx,
root,
move |cx: &mut Context, root: PathBuf, move_from: &Path| {
let move_to =
helix_stdx::path::expand_tilde(PathBuf::from(&move_to_string));
if let Err(err) = cx.editor.move_path(move_from, &move_to).map_err(|err| {
format!(
"Unable to move {} {} -> {}: {err}",
if move_to_string.ends_with(std::path::MAIN_SEPARATOR) {
"directory"
} else {
"file"
},
move_from.display(),
move_to.display()
)
}) {
return Some(Err(err));
};
refresh_file_explorer(cursor, cx, root);
None
},
)
},
)
});
let delete: KeyHandler = Box::new(|cx, (path, _), data, cursor| {
create_file_operation_prompt(
cx,
path,
|path| format!("Delete {}? (y/n): ", path.display()),
None,
|_| "".to_string(),
move |cx, to_delete, confirmation| {
let root = data.0.clone();
if confirmation != "y" {
return None;
}
if !to_delete.exists() {
return Some(Err(format!("Path {} does not exist", to_delete.display())));
};
if to_delete.is_dir() {
if let Err(err) = fs::remove_dir_all(to_delete).map_err(|err| {
format!("Unable to delete directory {}: {err}", to_delete.display())
}) {
return Some(Err(err));
};
refresh_file_explorer(cursor, cx, root);
return Some(Ok(format!("Deleted directory: {}", to_delete.display())));
}
if let Err(err) = fs::remove_file(to_delete)
.map_err(|err| format!("Unable to delete file {}: {err}", to_delete.display()))
{
return Some(Err(err));
};
refresh_file_explorer(cursor, cx, root);
Some(Ok(format!("Deleted file: {}", to_delete.display())))
},
)
});
let copy: KeyHandler = Box::new(|cx, (path, _), data, cursor| {
create_file_operation_prompt(
cx,
path,
|path| format!("Copy {} -> ", path.display()),
None,
|path| {
path.parent()
.map(|p| format!("{}{}", p.display(), std::path::MAIN_SEPARATOR))
.unwrap_or_default()
},
move |cx, copy_from, copy_to_string| {
let root = data.0.clone();
let copy_to = helix_stdx::path::expand_tilde(PathBuf::from(&copy_to_string));
if copy_from.is_dir() || copy_to_string.ends_with(std::path::MAIN_SEPARATOR) {
// TODO: support copying directories (recursively)?. This isn't built-in to the standard library
return Some(Err(format!(
"Copying directories is not supported: {} is a directory",
copy_from.display()
)));
}
let copy_to_str = copy_to_string.to_string();
confirm_before_overwriting(
copy_to.to_path_buf(),
copy_from.to_path_buf(),
cx,
root,
move |cx: &mut Context, picker_root: PathBuf, copy_from: &Path| {
let copy_to = helix_stdx::path::expand_tilde(PathBuf::from(&copy_to_str));
if let Err(err) = std::fs::copy(copy_from, &copy_to).map_err(|err| {
format!(
"Unable to copy from file {} to {}: {err}",
copy_from.display(),
copy_to.display()
)
}) {
return Some(Err(err));
};
refresh_file_explorer(cursor, cx, picker_root);
Some(Ok(format!(
"Copied contents of file {} to {}",
copy_from.display(),
copy_to.display()
)))
},
)
},
)
});
let columns = [PickerColumn::new(
"path",
|(path, is_dir): &ExplorerItem, (root, directory_style): &ExplorerData| {
let name = path.strip_prefix(root).unwrap_or(path).to_string_lossy();
if *is_dir {
Span::styled(format!("{}/", name), *directory_style).into()
} else {
name.into()
}
},
)];
let picker = Picker::new(
columns,
0,
directory_content,
(root, directory_style),
move |cx, (path, is_dir): &ExplorerItem, action| {
if *is_dir {
let new_root = helix_stdx::path::normalize(path);
let callback = Box::pin(async move {
let call: Callback =
Callback::EditorCompositor(Box::new(move |editor, compositor| {
if let Ok(picker) = file_explorer(None, new_root, editor) {
compositor.push(Box::new(overlay::overlaid(picker)));
}
}));
Ok(call)
});
cx.jobs.callback(callback);
} else if let Err(e) = cx.editor.open(path, action) {
let err = if let Some(err) = e.source() {
format!("{}", err)
} else {
format!("unable to open \"{}\"", path.display())
};
cx.editor.set_error(err);
}
},
)
.with_cursor(cursor.unwrap_or_default())
.with_preview(|_editor, (path, _is_dir)| Some((path.as_path().into(), None)))
.with_key_handlers(hashmap! {
alt!('n') => create,
alt!('m') => move_,
alt!('d') => delete,
alt!('c') => copy,
alt!('y') => yank_path,
});
Ok(picker)
}

View File

@ -1,6 +1,7 @@
mod completion;
mod document;
pub(crate) mod editor;
mod file_explorer;
mod info;
pub mod lsp;
mod markdown;
@ -19,6 +20,7 @@ use crate::filter_picker_entry;
use crate::job::{self, Callback};
pub use completion::Completion;
pub use editor::EditorView;
pub use file_explorer::file_explorer;
use helix_stdx::rope;
use helix_view::theme::Style;
pub use markdown::Markdown;
@ -300,56 +302,6 @@ pub fn file_picker(editor: &Editor, root: PathBuf) -> FilePicker {
picker
}
type FileExplorer = Picker<(PathBuf, bool), (PathBuf, Style)>;
pub fn file_explorer(root: PathBuf, editor: &Editor) -> Result<FileExplorer, std::io::Error> {
let directory_style = editor.theme.get("ui.text.directory");
let directory_content = directory_content(&root)?;
let columns = [PickerColumn::new(
"path",
|(path, is_dir): &(PathBuf, bool), (root, directory_style): &(PathBuf, Style)| {
let name = path.strip_prefix(root).unwrap_or(path).to_string_lossy();
if *is_dir {
Span::styled(format!("{}/", name), *directory_style).into()
} else {
name.into()
}
},
)];
let picker = Picker::new(
columns,
0,
directory_content,
(root, directory_style),
move |cx, (path, is_dir): &(PathBuf, bool), action| {
if *is_dir {
let new_root = helix_stdx::path::normalize(path);
let callback = Box::pin(async move {
let call: Callback =
Callback::EditorCompositor(Box::new(move |editor, compositor| {
if let Ok(picker) = file_explorer(new_root, editor) {
compositor.push(Box::new(overlay::overlaid(picker)));
}
}));
Ok(call)
});
cx.jobs.callback(callback);
} else if let Err(e) = cx.editor.open(path, action) {
let err = if let Some(err) = e.source() {
format!("{}", err)
} else {
format!("unable to open \"{}\"", path.display())
};
cx.editor.set_error(err);
}
},
)
.with_preview(|_editor, (path, _is_dir)| Some((path.as_path().into(), None)));
Ok(picker)
}
fn directory_content(path: &Path) -> Result<Vec<(PathBuf, bool)>, std::io::Error> {
let mut content: Vec<_> = std::fs::read_dir(path)?
.flatten()

View File

@ -47,6 +47,7 @@ use helix_core::{
use helix_view::{
editor::Action,
graphics::{CursorKind, Margin, Modifier, Rect},
input::KeyEvent,
theme::Style,
view::ViewPosition,
Document, DocumentId, Editor,
@ -258,6 +259,7 @@ pub struct Picker<T: 'static + Send + Sync, D: 'static> {
widths: Vec<Constraint>,
callback_fn: PickerCallback<T>,
custom_key_handlers: PickerKeyHandlers<T, D>,
pub truncate_start: bool,
/// Caches paths to documents
@ -385,6 +387,7 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
completion_height: 0,
widths,
preview_cache: HashMap::new(),
custom_key_handlers: HashMap::new(),
read_buffer: Vec::with_capacity(1024),
file_fn: None,
preview_highlight_handler: PreviewHighlightHandler::<T, D>::default().spawn(),
@ -392,6 +395,11 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
}
}
pub fn with_key_handlers(mut self, handlers: PickerKeyHandlers<T, D>) -> Self {
self.custom_key_handlers = handlers;
self
}
pub fn injector(&self) -> Injector<T, D> {
Injector {
dst: self.matcher.injector(),
@ -483,6 +491,11 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
.saturating_sub(1);
}
pub fn with_cursor(mut self, cursor: u32) -> Self {
self.cursor = cursor;
self
}
pub fn selection(&self) -> Option<&T> {
self.matcher
.snapshot()
@ -509,6 +522,17 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
self.show_preview = !self.show_preview;
}
fn custom_key_event_handler(&mut self, event: &KeyEvent, cx: &mut Context) -> EventResult {
if let (Some(callback), Some(selected)) =
(self.custom_key_handlers.get(event), self.selection())
{
callback(cx, selected, Arc::clone(&self.editor_data), self.cursor);
EventResult::Consumed(None)
} else {
EventResult::Ignored(None)
}
}
fn prompt_handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
if let EventResult::Consumed(_) = self.prompt.handle_event(event, cx) {
self.handle_prompt_change(matches!(event, Event::Paste(_)));
@ -1049,6 +1073,11 @@ impl<I: 'static + Send + Sync, D: 'static + Send + Sync> Component for Picker<I,
EventResult::Consumed(Some(callback))
};
// handle custom keybindings, if exist
if let EventResult::Consumed(_) = self.custom_key_event_handler(&key_event, ctx) {
return EventResult::Consumed(None);
}
match key_event {
shift!(Tab) | key!(Up) | ctrl!('p') => {
self.move_by(1, Direction::Backward);
@ -1168,3 +1197,5 @@ impl<T: 'static + Send + Sync, D> Drop for Picker<T, D> {
}
type PickerCallback<T> = Box<dyn Fn(&mut Context, &T, Action)>;
pub type PickerKeyHandler<T, D> = Box<dyn Fn(&mut Context, &T, Arc<D>, u32) + 'static>;
pub type PickerKeyHandlers<T, D> = HashMap<KeyEvent, PickerKeyHandler<T, D>>;

View File

@ -1358,6 +1358,14 @@ impl Editor {
self.status_msg = Some((error, Severity::Error));
}
#[inline]
pub fn set_result<T: Into<Cow<'static, str>>>(&mut self, result: Result<T, T>) {
match result {
Ok(ok) => self.set_status(ok),
Err(err) => self.set_error(err),
}
}
#[inline]
pub fn set_warning<T: Into<Cow<'static, str>>>(&mut self, warning: T) {
let warning = warning.into();

0
runtime/queries/snakemake/LICENSE 100755 → 100644
View File

View File

View File

View File

View File

View File