Add tab completion to Add sample file dir dialog

This commit is contained in:
Skye 2023-07-01 20:35:10 -04:00 committed by Scott Lamb
parent 930decc766
commit 3d40a39b93
3 changed files with 180 additions and 8 deletions

View File

@ -5,8 +5,8 @@
use base::strutil::{decode_size, encode_size};
use cursive::traits::{Nameable, Resizable};
use cursive::view::Scrollable;
use cursive::views;
use cursive::Cursive;
use cursive::{views, With};
use db::writer;
use failure::Error;
use log::{debug, trace};
@ -16,6 +16,8 @@ use std::path::Path;
use std::rc::Rc;
use std::sync::Arc;
use super::tab_complete::TabCompleteEditView;
struct Stream {
label: String,
used: i64,
@ -217,19 +219,47 @@ pub fn top_dialog(db: &Arc<db::Database>, siv: &mut Cursive) {
);
}
fn tab_completer(content: &str) -> Box<[String]> {
let (parent, final_segment) = content.split_at(content.rfind('/').map(|i| i + 1).unwrap_or(0));
Path::new(parent)
.read_dir()
.ok()
.into_iter()
.flatten()
.filter_map(|entry| {
let entry = entry.ok()?;
if entry.file_type().ok()?.is_dir()
&& entry.file_name().to_str()?.starts_with(final_segment)
{
Some(entry.path().as_os_str().to_str()?.to_owned() + "/")
} else {
None
}
})
.collect::<Box<[String]>>()
.with(|completions| {
// Sort ignoring initial dot
completions.sort_by(|a, b| {
a.strip_prefix('.')
.unwrap_or(a)
.cmp(b.strip_prefix('.').unwrap_or(b))
})
})
}
fn add_dir_dialog(db: &Arc<db::Database>, siv: &mut Cursive) {
siv.add_layer(
views::Dialog::around(
views::LinearLayout::vertical()
.child(views::TextView::new("path"))
.child(
views::EditView::new()
.on_submit({
let db = db.clone();
move |siv, path| add_dir(&db, siv, path.as_ref())
})
.with_name("path")
.fixed_width(60),
TabCompleteEditView::new(views::EditView::new().on_submit({
let db = db.clone();
move |siv, path| add_dir(&db, siv, path.as_ref())
}))
.on_tab_complete(tab_completer)
.with_name("path")
.fixed_width(60),
),
)
.button("Add", {

View File

@ -17,6 +17,7 @@ use std::sync::Arc;
mod cameras;
mod dirs;
mod tab_complete;
mod users;
/// Interactively edits configuration.

View File

@ -0,0 +1,141 @@
use std::{cell::RefCell, rc::Rc};
use cursive::{
direction::Direction,
event::{Event, EventResult, Key},
menu,
view::CannotFocus,
views::{self, EditView, MenuPopup},
Printer, Rect, Vec2, View,
};
type TabCompleteFn = Rc<dyn Fn(&str) -> Box<[String]>>;
pub struct TabCompleteEditView {
edit_view: Rc<RefCell<EditView>>,
tab_completer: Option<TabCompleteFn>,
}
impl TabCompleteEditView {
pub fn new(edit_view: EditView) -> Self {
Self {
edit_view: Rc::new(RefCell::new(edit_view)),
tab_completer: None,
}
}
pub fn on_tab_complete(mut self, handler: impl Fn(&str) -> Box<[String]> + 'static) -> Self {
self.tab_completer = Some(Rc::new(handler));
self
}
}
impl View for TabCompleteEditView {
fn draw(&self, printer: &Printer) {
self.edit_view.borrow().draw(printer)
}
fn layout(&mut self, size: Vec2) {
self.edit_view.borrow_mut().layout(size)
}
fn take_focus(&mut self, source: Direction) -> Result<EventResult, CannotFocus> {
self.edit_view.borrow_mut().take_focus(source)
}
fn on_event(&mut self, event: Event) -> EventResult {
if !self.edit_view.borrow().is_enabled() {
return EventResult::Ignored;
}
if let Event::Key(Key::Tab) = event {
if let Some(tab_completer) = self.tab_completer.clone() {
tab_complete(self.edit_view.clone(), tab_completer, true)
} else {
EventResult::consumed()
}
} else {
self.edit_view.borrow_mut().on_event(event)
}
}
fn important_area(&self, view_size: Vec2) -> Rect {
self.edit_view.borrow().important_area(view_size)
}
}
fn tab_complete(
edit_view: Rc<RefCell<EditView>>,
tab_completer: TabCompleteFn,
autofill_one: bool,
) -> EventResult {
let completions = tab_completer(edit_view.borrow().get_content().as_str());
EventResult::with_cb_once(move |siv| match *completions {
[] => {}
[ref completion] if autofill_one => edit_view.borrow_mut().set_content(completion)(siv),
[..] => {
siv.add_layer(TabCompletePopup {
popup: views::MenuPopup::new(Rc::new({
let mut tree = menu::Tree::new();
for completion in Vec::from(completions) {
let edit_view = edit_view.clone();
tree.add_leaf(&completion.clone(), move |siv| {
edit_view.borrow_mut().set_content(&completion)(siv)
})
}
tree
})),
edit_view,
tab_completer,
});
}
})
}
struct TabCompletePopup {
edit_view: Rc<RefCell<EditView>>,
popup: MenuPopup,
tab_completer: TabCompleteFn,
}
impl TabCompletePopup {
fn forward_event_and_refresh(&self, event: Event) -> EventResult {
let edit_view = self.edit_view.clone();
let tab_completer = self.tab_completer.clone();
EventResult::with_cb_once(move |s| {
s.pop_layer();
edit_view.borrow_mut().on_event(event).process(s);
tab_complete(edit_view, tab_completer, false).process(s);
})
}
}
impl View for TabCompletePopup {
fn draw(&self, printer: &Printer) {
self.popup.draw(printer)
}
fn required_size(&mut self, req: Vec2) -> Vec2 {
self.popup.required_size(req)
}
fn on_event(&mut self, event: Event) -> EventResult {
match self.popup.on_event(event.clone()) {
EventResult::Ignored => match event {
e @ (Event::Char(_) | Event::Key(Key::Backspace)) => {
self.forward_event_and_refresh(e)
}
Event::Key(Key::Tab) => self.popup.on_event(Event::Key(Key::Enter)),
_ => EventResult::Ignored,
},
other => other,
}
}
fn layout(&mut self, size: Vec2) {
self.popup.layout(size)
}
fn important_area(&self, size: Vec2) -> Rect {
self.popup.important_area(size)
}
}