moonfire-nvr/server/src/cmds/config/tab_complete.rs
2023-08-03 15:58:57 -05:00

150 lines
4.5 KiB
Rust

// This file is part of Moonfire NVR, a security camera network video recorder.
// Copyright (C) 2020 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
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, With,
};
type TabCompleteFn = Rc<dyn Fn(&str) -> Vec<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) -> Vec<String> + 'static) -> Self {
self.tab_completer = Some(Rc::new(handler));
self
}
pub fn get_content(&self) -> Rc<String> {
self.edit_view.borrow_mut().get_content()
}
}
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({
menu::Tree::new().with(|tree| {
for completion in completions {
let edit_view = edit_view.clone();
tree.add_leaf(&completion.clone(), move |siv| {
edit_view.borrow_mut().set_content(&completion)(siv)
})
}
})
})),
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)
}
}