Merge remote-tracking branch 'nikitarevenco/powerful-file-explorer'
This commit is contained in:
commit
9796951648
6 changed files with 442 additions and 11 deletions
|
@ -285,6 +285,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` |
|
||||
|
@ -463,6 +465,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.
|
||||
|
|
|
@ -3017,7 +3017,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)));
|
||||
}
|
||||
}
|
||||
|
@ -3044,7 +3044,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)));
|
||||
}
|
||||
}
|
||||
|
@ -3057,7 +3057,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)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,11 +14,12 @@ mod statusline;
|
|||
mod text;
|
||||
mod text_decorations;
|
||||
|
||||
use crate::compositor::Compositor;
|
||||
use crate::filter_picker_entry;
|
||||
use crate::compositor::{Compositor, Context};
|
||||
use crate::job::{self, Callback};
|
||||
use crate::{alt, filter_picker_entry};
|
||||
pub use completion::Completion;
|
||||
pub use editor::EditorView;
|
||||
use helix_core::hashmap;
|
||||
use helix_stdx::rope;
|
||||
use helix_view::theme::Style;
|
||||
pub use markdown::Markdown;
|
||||
|
@ -32,9 +33,13 @@ pub use text::Text;
|
|||
use helix_view::Editor;
|
||||
use tui::text::Span;
|
||||
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::{error::Error, path::PathBuf};
|
||||
|
||||
use self::picker::PickerKeyHandler;
|
||||
|
||||
struct Utf8PathBuf {
|
||||
path: String,
|
||||
is_dir: bool,
|
||||
|
@ -279,15 +284,379 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi
|
|||
picker
|
||||
}
|
||||
|
||||
type FileExplorer = Picker<(PathBuf, bool), (PathBuf, Style)>;
|
||||
/// for each path: (path to item, is the path a directory?)
|
||||
type ExplorerItem = (PathBuf, bool);
|
||||
/// (file explorer root, directory style)
|
||||
type ExplorerData = (PathBuf, Style);
|
||||
|
||||
pub fn file_explorer(root: PathBuf, editor: &Editor) -> Result<FileExplorer, std::io::Error> {
|
||||
type FileExplorer = Picker<ExplorerItem, ExplorerData>;
|
||||
|
||||
type KeyHandler = PickerKeyHandler<ExplorerItem, ExplorerData>;
|
||||
|
||||
type OnConfirm = fn(
|
||||
cursor: u32,
|
||||
cx: &mut Context,
|
||||
picker_root: PathBuf,
|
||||
&str,
|
||||
&Path,
|
||||
) -> Option<Result<String, String>>;
|
||||
|
||||
fn create_confirmation_prompt(
|
||||
cursor: u32,
|
||||
input: String,
|
||||
cx: &mut Context,
|
||||
operation_input_str: String,
|
||||
operation_input: PathBuf,
|
||||
picker_root: PathBuf,
|
||||
on_confirm: OnConfirm,
|
||||
) {
|
||||
let callback = Box::pin(async move {
|
||||
let call: Callback = Callback::EditorCompositor(Box::new(move |_editor, compositor| {
|
||||
let prompt = Prompt::new(
|
||||
input.into(),
|
||||
None,
|
||||
crate::ui::completers::none,
|
||||
move |cx, input: &str, event: PromptEvent| {
|
||||
if event != PromptEvent::Validate || input != "y" {
|
||||
return;
|
||||
};
|
||||
|
||||
match on_confirm(
|
||||
cursor,
|
||||
cx,
|
||||
picker_root.clone(),
|
||||
&operation_input_str,
|
||||
&operation_input,
|
||||
) {
|
||||
Some(Ok(msg)) => cx.editor.set_status(msg),
|
||||
Some(Err(msg)) => cx.editor.set_error(msg),
|
||||
None => (),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
compositor.push(Box::new(prompt));
|
||||
}));
|
||||
Ok(call)
|
||||
});
|
||||
cx.jobs.callback(callback);
|
||||
}
|
||||
|
||||
type FileOperation = fn(PathBuf, u32, &mut Context, &Path, &str) -> Option<Result<String, String>>;
|
||||
|
||||
fn create_file_operation_prompt(
|
||||
cursor: u32,
|
||||
prompt: &'static str,
|
||||
cx: &mut Context,
|
||||
path: &Path,
|
||||
data: Arc<ExplorerData>,
|
||||
compute_initial_line: fn(&Path) -> String,
|
||||
file_op: FileOperation,
|
||||
) {
|
||||
cx.editor.file_explorer_selected_path = Some(path.to_path_buf());
|
||||
let callback = Box::pin(async move {
|
||||
let call: Callback = Callback::EditorCompositor(Box::new(move |editor, compositor| {
|
||||
let mut prompt = Prompt::new(
|
||||
prompt.into(),
|
||||
None,
|
||||
crate::ui::completers::none,
|
||||
move |cx, input: &str, event: PromptEvent| {
|
||||
if event != PromptEvent::Validate {
|
||||
return;
|
||||
};
|
||||
|
||||
let path = cx.editor.file_explorer_selected_path.clone();
|
||||
|
||||
if let Some(path) = path {
|
||||
match file_op(data.0.clone(), cursor, cx, &path, input) {
|
||||
Some(Ok(msg)) => cx.editor.set_status(msg),
|
||||
Some(Err(msg)) => cx.editor.set_error(msg),
|
||||
None => (),
|
||||
};
|
||||
} else {
|
||||
cx.editor
|
||||
.set_error("Unable to determine path of selected file")
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(path_editing) = &editor.file_explorer_selected_path {
|
||||
prompt.set_line_no_recalculate(compute_initial_line(path_editing));
|
||||
}
|
||||
|
||||
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(
|
||||
cursor,
|
||||
"create:",
|
||||
cx,
|
||||
path,
|
||||
data,
|
||||
|path| {
|
||||
path.parent()
|
||||
.map(|p| format!("{}{}", p.display(), std::path::MAIN_SEPARATOR))
|
||||
.unwrap_or_default()
|
||||
},
|
||||
|root, cursor, cx, _, to_create_str| {
|
||||
let to_create = helix_stdx::path::expand_tilde(PathBuf::from(to_create_str));
|
||||
|
||||
let do_create = |cursor: u32,
|
||||
cx: &mut Context,
|
||||
root: PathBuf,
|
||||
to_create_str: &str,
|
||||
to_create: &Path| {
|
||||
if to_create_str.ends_with(std::path::MAIN_SEPARATOR) {
|
||||
if let Err(err) = fs::create_dir_all(to_create).map_err(|err| {
|
||||
format!("Unable to create directory {}: {err}", to_create.display())
|
||||
}) {
|
||||
return Some(Err(err));
|
||||
}
|
||||
refresh_file_explorer(cursor, cx, root);
|
||||
|
||||
return Some(Ok(format!("Created directory: {}", to_create.display())));
|
||||
}
|
||||
|
||||
if let Err(err) = fs::File::create(to_create).map_err(|err| {
|
||||
format!("Unable to create file {}: {err}", to_create.display())
|
||||
}) {
|
||||
return Some(Err(err));
|
||||
};
|
||||
refresh_file_explorer(cursor, cx, root);
|
||||
|
||||
Some(Ok(format!("Created file: {}", to_create.display())))
|
||||
};
|
||||
|
||||
if to_create.exists() {
|
||||
create_confirmation_prompt(
|
||||
cursor,
|
||||
format!(
|
||||
"Path {} already exists. Overwrite? (y/n):",
|
||||
to_create.display()
|
||||
),
|
||||
cx,
|
||||
to_create_str.to_string(),
|
||||
to_create.to_path_buf(),
|
||||
root,
|
||||
do_create,
|
||||
);
|
||||
return None;
|
||||
};
|
||||
|
||||
do_create(cursor, cx, root, to_create_str, &to_create)
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
let move_: KeyHandler = Box::new(|cx, (path, _), data, cursor| {
|
||||
create_file_operation_prompt(
|
||||
cursor,
|
||||
"move:",
|
||||
cx,
|
||||
path,
|
||||
data,
|
||||
|path| path.display().to_string(),
|
||||
|root, cursor, cx, move_from, move_to_str| {
|
||||
let move_to = helix_stdx::path::expand_tilde(PathBuf::from(move_to_str));
|
||||
|
||||
let do_move = |cursor: u32,
|
||||
cx: &mut Context,
|
||||
root: PathBuf,
|
||||
move_to_str: &str,
|
||||
move_from: &Path| {
|
||||
let move_to = helix_stdx::path::expand_tilde(PathBuf::from(move_to_str));
|
||||
if let Err(err) = fs::rename(move_from, &move_to).map_err(|err| {
|
||||
format!(
|
||||
"Unable to move {} {} -> {}: {err}",
|
||||
if move_to_str.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
|
||||
};
|
||||
|
||||
if move_to.exists() {
|
||||
create_confirmation_prompt(
|
||||
cursor,
|
||||
format!(
|
||||
"Path {} already exists. Overwrite? (y/n):",
|
||||
move_to.display()
|
||||
),
|
||||
cx,
|
||||
move_to_str.to_string(),
|
||||
move_from.to_path_buf(),
|
||||
root,
|
||||
do_move,
|
||||
);
|
||||
return None;
|
||||
};
|
||||
|
||||
do_move(cursor, cx, root, move_to_str, move_from)
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
let delete: KeyHandler = Box::new(|cx, (path, _), data, cursor| {
|
||||
create_file_operation_prompt(
|
||||
cursor,
|
||||
"delete? (y/n):",
|
||||
cx,
|
||||
path,
|
||||
data,
|
||||
|_| "".to_string(),
|
||||
|root, cursor, cx, to_delete, confirmation| {
|
||||
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(
|
||||
cursor,
|
||||
"copy-to:",
|
||||
cx,
|
||||
path,
|
||||
data,
|
||||
|path| {
|
||||
path.parent()
|
||||
.map(|p| format!("{}{}", p.display(), std::path::MAIN_SEPARATOR))
|
||||
.unwrap_or_default()
|
||||
},
|
||||
|root, cursor, cx, copy_from, copy_to_str| {
|
||||
let copy_to = helix_stdx::path::expand_tilde(PathBuf::from(copy_to_str));
|
||||
|
||||
let do_copy = |cursor: u32,
|
||||
cx: &mut Context,
|
||||
root: PathBuf,
|
||||
copy_to_str: &str,
|
||||
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, ©_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, root);
|
||||
|
||||
Some(Ok(format!(
|
||||
"Copied contents of file {} to {}",
|
||||
copy_from.display(),
|
||||
copy_to.display()
|
||||
)))
|
||||
};
|
||||
|
||||
if copy_from.is_dir() || copy_to_str.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()
|
||||
)));
|
||||
}
|
||||
|
||||
if copy_to.exists() {
|
||||
create_confirmation_prompt(
|
||||
cursor,
|
||||
format!(
|
||||
"Path {} already exists. Overwrite? (y/n):",
|
||||
copy_to.display()
|
||||
),
|
||||
cx,
|
||||
copy_to_str.to_string(),
|
||||
copy_from.to_path_buf(),
|
||||
root,
|
||||
do_copy,
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
do_copy(cursor, cx, root, copy_to_str, copy_from)
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
let columns = [PickerColumn::new(
|
||||
"path",
|
||||
|(path, is_dir): &(PathBuf, bool), (root, directory_style): &(PathBuf, Style)| {
|
||||
|(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()
|
||||
|
@ -296,18 +665,19 @@ pub fn file_explorer(root: PathBuf, editor: &Editor) -> Result<FileExplorer, std
|
|||
}
|
||||
},
|
||||
)];
|
||||
|
||||
let picker = Picker::new(
|
||||
columns,
|
||||
0,
|
||||
directory_content,
|
||||
(root, directory_style),
|
||||
move |cx, (path, is_dir): &(PathBuf, bool), action| {
|
||||
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(new_root, editor) {
|
||||
if let Ok(picker) = file_explorer(None, new_root, editor) {
|
||||
compositor.push(Box::new(overlay::overlaid(picker)));
|
||||
}
|
||||
}));
|
||||
|
@ -324,7 +694,15 @@ pub fn file_explorer(root: PathBuf, editor: &Editor) -> Result<FileExplorer, std
|
|||
}
|
||||
},
|
||||
)
|
||||
.with_preview(|_editor, (path, _is_dir)| Some((path.as_path().into(), None)));
|
||||
.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)
|
||||
}
|
||||
|
|
|
@ -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_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(_)));
|
||||
|
@ -1046,6 +1070,10 @@ impl<I: 'static + Send + Sync, D: 'static + Send + Sync> Component for Picker<I,
|
|||
EventResult::Consumed(Some(callback))
|
||||
};
|
||||
|
||||
if let EventResult::Consumed(_) = self.custom_event_handler(&key_event, ctx) {
|
||||
return EventResult::Consumed(None);
|
||||
}
|
||||
|
||||
match key_event {
|
||||
shift!(Tab) | key!(Up) | ctrl!('p') => {
|
||||
self.move_by(1, Direction::Backward);
|
||||
|
@ -1157,3 +1185,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>>;
|
||||
|
|
|
@ -122,6 +122,13 @@ impl Prompt {
|
|||
self.recalculate_completion(editor);
|
||||
}
|
||||
|
||||
pub fn set_line_no_recalculate(&mut self, line: String) {
|
||||
debug_assert!(self.completion.is_empty());
|
||||
let cursor = line.len();
|
||||
self.line = line;
|
||||
self.cursor = cursor;
|
||||
}
|
||||
|
||||
pub fn with_language(
|
||||
mut self,
|
||||
language: &'static str,
|
||||
|
|
|
@ -1107,6 +1107,7 @@ pub struct Editor {
|
|||
|
||||
pub mouse_down_range: Option<Range>,
|
||||
pub cursor_cache: CursorCache,
|
||||
pub file_explorer_selected_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub type Motion = Box<dyn Fn(&mut Editor)>;
|
||||
|
@ -1229,6 +1230,7 @@ impl Editor {
|
|||
handlers,
|
||||
mouse_down_range: None,
|
||||
cursor_cache: CursorCache::default(),
|
||||
file_explorer_selected_path: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue