mirror of
https://github.com/ChrisTitusTech/linutil.git
synced 2024-11-05 13:15:21 +00:00
Feat: confirmation prompts (#687)
* confirmation prompt * actually implement scrolling * finalize styling * get rid of generics on AppState and Focus * add bottom title as help text * number formatting
This commit is contained in:
parent
6a8987b35a
commit
7cc38df7e1
|
@ -1,13 +1,15 @@
|
||||||
use crate::{Command, ListNode, Tab};
|
|
||||||
use ego_tree::{NodeMut, Tree};
|
|
||||||
use include_dir::{include_dir, Dir};
|
|
||||||
use serde::Deserialize;
|
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{BufRead, BufReader, Read},
|
io::{BufRead, BufReader, Read},
|
||||||
os::unix::fs::PermissionsExt,
|
os::unix::fs::PermissionsExt,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::{Command, ListNode, Tab};
|
||||||
|
use ego_tree::{NodeMut, Tree};
|
||||||
|
use include_dir::{include_dir, Dir};
|
||||||
|
use serde::Deserialize;
|
||||||
use tempdir::TempDir;
|
use tempdir::TempDir;
|
||||||
|
|
||||||
const TAB_DATA: Dir = include_dir!("$CARGO_MANIFEST_DIR/tabs");
|
const TAB_DATA: Dir = include_dir!("$CARGO_MANIFEST_DIR/tabs");
|
||||||
|
@ -35,12 +37,12 @@ pub fn get_tabs(validate: bool) -> Vec<Tab> {
|
||||||
},
|
},
|
||||||
directory,
|
directory,
|
||||||
)| {
|
)| {
|
||||||
let mut tree = Tree::new(ListNode {
|
let mut tree = Tree::new(Rc::new(ListNode {
|
||||||
name: "root".to_string(),
|
name: "root".to_string(),
|
||||||
description: String::new(),
|
description: String::new(),
|
||||||
command: Command::None,
|
command: Command::None,
|
||||||
task_list: String::new(),
|
task_list: String::new(),
|
||||||
});
|
}));
|
||||||
let mut root = tree.root_mut();
|
let mut root = tree.root_mut();
|
||||||
create_directory(data, &mut root, &directory, validate);
|
create_directory(data, &mut root, &directory, validate);
|
||||||
Tab {
|
Tab {
|
||||||
|
@ -164,28 +166,28 @@ fn filter_entries(entries: &mut Vec<Entry>) {
|
||||||
|
|
||||||
fn create_directory(
|
fn create_directory(
|
||||||
data: Vec<Entry>,
|
data: Vec<Entry>,
|
||||||
node: &mut NodeMut<ListNode>,
|
node: &mut NodeMut<Rc<ListNode>>,
|
||||||
command_dir: &Path,
|
command_dir: &Path,
|
||||||
validate: bool,
|
validate: bool,
|
||||||
) {
|
) {
|
||||||
for entry in data {
|
for entry in data {
|
||||||
match entry.entry_type {
|
match entry.entry_type {
|
||||||
EntryType::Entries(entries) => {
|
EntryType::Entries(entries) => {
|
||||||
let mut node = node.append(ListNode {
|
let mut node = node.append(Rc::new(ListNode {
|
||||||
name: entry.name,
|
name: entry.name,
|
||||||
description: entry.description,
|
description: entry.description,
|
||||||
command: Command::None,
|
command: Command::None,
|
||||||
task_list: String::new(),
|
task_list: String::new(),
|
||||||
});
|
}));
|
||||||
create_directory(entries, &mut node, command_dir, validate);
|
create_directory(entries, &mut node, command_dir, validate);
|
||||||
}
|
}
|
||||||
EntryType::Command(command) => {
|
EntryType::Command(command) => {
|
||||||
node.append(ListNode {
|
node.append(Rc::new(ListNode {
|
||||||
name: entry.name,
|
name: entry.name,
|
||||||
description: entry.description,
|
description: entry.description,
|
||||||
command: Command::Raw(command),
|
command: Command::Raw(command),
|
||||||
task_list: String::new(),
|
task_list: String::new(),
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
EntryType::Script(script) => {
|
EntryType::Script(script) => {
|
||||||
let script = command_dir.join(script);
|
let script = command_dir.join(script);
|
||||||
|
@ -194,7 +196,7 @@ fn create_directory(
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((executable, args)) = get_shebang(&script, validate) {
|
if let Some((executable, args)) = get_shebang(&script, validate) {
|
||||||
node.append(ListNode {
|
node.append(Rc::new(ListNode {
|
||||||
name: entry.name,
|
name: entry.name,
|
||||||
description: entry.description,
|
description: entry.description,
|
||||||
command: Command::LocalFile {
|
command: Command::LocalFile {
|
||||||
|
@ -203,7 +205,7 @@ fn create_directory(
|
||||||
file: script,
|
file: script,
|
||||||
},
|
},
|
||||||
task_list: entry.task_list,
|
task_list: entry.task_list,
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
mod inner;
|
mod inner;
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use ego_tree::Tree;
|
use ego_tree::Tree;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
@ -20,7 +22,7 @@ pub enum Command {
|
||||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||||
pub struct Tab {
|
pub struct Tab {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub tree: Tree<ListNode>,
|
pub tree: Tree<Rc<ListNode>>,
|
||||||
pub multi_selectable: bool,
|
pub multi_selectable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
126
tui/src/confirmation.rs
Normal file
126
tui/src/confirmation.rs
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use crate::{float::FloatContent, hint::Shortcut};
|
||||||
|
|
||||||
|
use crossterm::event::{KeyCode, KeyEvent};
|
||||||
|
use ratatui::{
|
||||||
|
layout::Alignment,
|
||||||
|
prelude::*,
|
||||||
|
widgets::{Block, Borders, Clear, List},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum ConfirmStatus {
|
||||||
|
Confirm,
|
||||||
|
Abort,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ConfirmPrompt {
|
||||||
|
pub names: Box<[String]>,
|
||||||
|
pub status: ConfirmStatus,
|
||||||
|
scroll: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfirmPrompt {
|
||||||
|
pub fn new(names: &[&str]) -> Self {
|
||||||
|
let max_count_str = format!("{}", names.len());
|
||||||
|
let names = names
|
||||||
|
.iter()
|
||||||
|
.zip(1..)
|
||||||
|
.map(|(name, n)| {
|
||||||
|
let count_str = format!("{n}");
|
||||||
|
let space_str = (0..(max_count_str.len() - count_str.len()))
|
||||||
|
.map(|_| ' ')
|
||||||
|
.collect::<String>();
|
||||||
|
format!("{space_str}{n}. {name}")
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
names,
|
||||||
|
status: ConfirmStatus::None,
|
||||||
|
scroll: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_down(&mut self) {
|
||||||
|
if self.scroll < self.names.len() - 1 {
|
||||||
|
self.scroll += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_up(&mut self) {
|
||||||
|
if self.scroll > 0 {
|
||||||
|
self.scroll -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FloatContent for ConfirmPrompt {
|
||||||
|
fn draw(&mut self, frame: &mut Frame, area: Rect) {
|
||||||
|
let block = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title(" Confirm selections ")
|
||||||
|
.title_bottom(" [y] to continue, [n] to abort ")
|
||||||
|
.title_alignment(Alignment::Center)
|
||||||
|
.title_style(Style::default().bold())
|
||||||
|
.style(Style::default());
|
||||||
|
|
||||||
|
frame.render_widget(block.clone(), area);
|
||||||
|
|
||||||
|
let inner_area = block.inner(area);
|
||||||
|
|
||||||
|
let paths_text = self
|
||||||
|
.names
|
||||||
|
.iter()
|
||||||
|
.skip(self.scroll)
|
||||||
|
.map(|p| {
|
||||||
|
let span = Span::from(Cow::<'_, str>::Borrowed(p));
|
||||||
|
Line::from(span).style(Style::default())
|
||||||
|
})
|
||||||
|
.collect::<Text>();
|
||||||
|
|
||||||
|
frame.render_widget(Clear, inner_area);
|
||||||
|
frame.render_widget(List::new(paths_text), inner_area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_key_event(&mut self, key: &KeyEvent) -> bool {
|
||||||
|
use KeyCode::*;
|
||||||
|
self.status = match key.code {
|
||||||
|
Char('y') | Char('Y') => ConfirmStatus::Confirm,
|
||||||
|
Char('n') | Char('N') | Esc => ConfirmStatus::Abort,
|
||||||
|
Char('j') => {
|
||||||
|
self.scroll_down();
|
||||||
|
ConfirmStatus::None
|
||||||
|
}
|
||||||
|
Char('k') => {
|
||||||
|
self.scroll_up();
|
||||||
|
ConfirmStatus::None
|
||||||
|
}
|
||||||
|
_ => ConfirmStatus::None,
|
||||||
|
};
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_finished(&self) -> bool {
|
||||||
|
use ConfirmStatus::*;
|
||||||
|
match self.status {
|
||||||
|
Confirm | Abort => true,
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shortcut_list(&self) -> (&str, Box<[Shortcut]>) {
|
||||||
|
(
|
||||||
|
"Confirmation prompt",
|
||||||
|
Box::new([
|
||||||
|
Shortcut::new("Continue", ["Y", "y"]),
|
||||||
|
Shortcut::new("Abort", ["N", "n"]),
|
||||||
|
Shortcut::new("Scroll up", ["j"]),
|
||||||
|
Shortcut::new("Scroll down", ["k"]),
|
||||||
|
Shortcut::new("Close linutil", ["CTRL-c", "q"]),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,14 +13,14 @@ pub trait FloatContent {
|
||||||
fn get_shortcut_list(&self) -> (&str, Box<[Shortcut]>);
|
fn get_shortcut_list(&self) -> (&str, Box<[Shortcut]>);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Float {
|
pub struct Float<Content: FloatContent + ?Sized> {
|
||||||
content: Box<dyn FloatContent>,
|
pub content: Box<Content>,
|
||||||
width_percent: u16,
|
width_percent: u16,
|
||||||
height_percent: u16,
|
height_percent: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Float {
|
impl<Content: FloatContent + ?Sized> Float<Content> {
|
||||||
pub fn new(content: Box<dyn FloatContent>, width_percent: u16, height_percent: u16) -> Self {
|
pub fn new(content: Box<Content>, width_percent: u16, height_percent: u16) -> Self {
|
||||||
Self {
|
Self {
|
||||||
content,
|
content,
|
||||||
width_percent,
|
width_percent,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod confirmation;
|
||||||
mod filter;
|
mod filter;
|
||||||
mod float;
|
mod float;
|
||||||
mod floating_text;
|
mod floating_text;
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
confirmation::{ConfirmPrompt, ConfirmStatus},
|
||||||
filter::{Filter, SearchAction},
|
filter::{Filter, SearchAction},
|
||||||
float::{Float, FloatContent},
|
float::{Float, FloatContent},
|
||||||
floating_text::{FloatingText, FloatingTextMode},
|
floating_text::{FloatingText, FloatingTextMode},
|
||||||
|
@ -8,7 +11,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
||||||
use ego_tree::NodeId;
|
use ego_tree::NodeId;
|
||||||
use linutil_core::{Command, ListNode, Tab};
|
use linutil_core::{ListNode, Tab};
|
||||||
#[cfg(feature = "tips")]
|
#[cfg(feature = "tips")]
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
|
@ -53,7 +56,7 @@ pub struct AppState {
|
||||||
selection: ListState,
|
selection: ListState,
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
multi_select: bool,
|
multi_select: bool,
|
||||||
selected_commands: Vec<Command>,
|
selected_commands: Vec<Rc<ListNode>>,
|
||||||
drawable: bool,
|
drawable: bool,
|
||||||
#[cfg(feature = "tips")]
|
#[cfg(feature = "tips")]
|
||||||
tip: &'static str,
|
tip: &'static str,
|
||||||
|
@ -63,11 +66,12 @@ pub enum Focus {
|
||||||
Search,
|
Search,
|
||||||
TabList,
|
TabList,
|
||||||
List,
|
List,
|
||||||
FloatingWindow(Float),
|
FloatingWindow(Float<dyn FloatContent>),
|
||||||
|
ConfirmationPrompt(Float<ConfirmPrompt>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ListEntry {
|
pub struct ListEntry {
|
||||||
pub node: ListNode,
|
pub node: Rc<ListNode>,
|
||||||
pub id: NodeId,
|
pub id: NodeId,
|
||||||
pub has_children: bool,
|
pub has_children: bool,
|
||||||
}
|
}
|
||||||
|
@ -164,6 +168,7 @@ impl AppState {
|
||||||
),
|
),
|
||||||
|
|
||||||
Focus::FloatingWindow(ref float) => float.get_shortcut_list(),
|
Focus::FloatingWindow(ref float) => float.get_shortcut_list(),
|
||||||
|
Focus::ConfirmationPrompt(ref prompt) => prompt.get_shortcut_list(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,7 +313,7 @@ impl AppState {
|
||||||
|ListEntry {
|
|ListEntry {
|
||||||
node, has_children, ..
|
node, has_children, ..
|
||||||
}| {
|
}| {
|
||||||
let is_selected = self.selected_commands.contains(&node.command);
|
let is_selected = self.selected_commands.contains(node);
|
||||||
let (indicator, style) = if is_selected {
|
let (indicator, style) = if is_selected {
|
||||||
(self.theme.multi_select_icon(), Style::default().bold())
|
(self.theme.multi_select_icon(), Style::default().bold())
|
||||||
} else {
|
} else {
|
||||||
|
@ -389,8 +394,10 @@ impl AppState {
|
||||||
|
|
||||||
frame.render_stateful_widget(disclaimer_list, list_chunks[1], &mut self.selection);
|
frame.render_stateful_widget(disclaimer_list, list_chunks[1], &mut self.selection);
|
||||||
|
|
||||||
if let Focus::FloatingWindow(float) = &mut self.focus {
|
match &mut self.focus {
|
||||||
float.draw(frame, chunks[1]);
|
Focus::FloatingWindow(float) => float.draw(frame, chunks[1]),
|
||||||
|
Focus::ConfirmationPrompt(prompt) => prompt.draw(frame, chunks[1]),
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
frame.render_widget(keybind_para, vertical[1]);
|
frame.render_widget(keybind_para, vertical[1]);
|
||||||
|
@ -400,9 +407,11 @@ impl AppState {
|
||||||
// This should be defined first to allow closing
|
// This should be defined first to allow closing
|
||||||
// the application even when not drawable ( If terminal is small )
|
// the application even when not drawable ( If terminal is small )
|
||||||
// Exit on 'q' or 'Ctrl-c' input
|
// Exit on 'q' or 'Ctrl-c' input
|
||||||
if matches!(self.focus, Focus::TabList | Focus::List)
|
if matches!(
|
||||||
&& (key.code == KeyCode::Char('q')
|
self.focus,
|
||||||
|| key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('c'))
|
Focus::TabList | Focus::List | Focus::ConfirmationPrompt(_)
|
||||||
|
) && (key.code == KeyCode::Char('q')
|
||||||
|
|| key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('c'))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -444,6 +453,22 @@ impl AppState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Focus::ConfirmationPrompt(confirm) => {
|
||||||
|
confirm.content.handle_key_event(key);
|
||||||
|
match confirm.content.status {
|
||||||
|
ConfirmStatus::Abort => {
|
||||||
|
self.focus = Focus::List;
|
||||||
|
// selected command was pushed to selection list if multi-select was
|
||||||
|
// enabled, need to clear it to prevent state corruption
|
||||||
|
if !self.multi_select {
|
||||||
|
self.selected_commands.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConfirmStatus::Confirm => self.handle_confirm_command(),
|
||||||
|
ConfirmStatus::None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Focus::Search => match self.filter.handle_key(key) {
|
Focus::Search => match self.filter.handle_key(key) {
|
||||||
SearchAction::Exit => self.exit_search(),
|
SearchAction::Exit => self.exit_search(),
|
||||||
SearchAction::Update => self.update_items(),
|
SearchAction::Update => self.update_items(),
|
||||||
|
@ -503,7 +528,7 @@ impl AppState {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_selection(&mut self) {
|
fn toggle_selection(&mut self) {
|
||||||
if let Some(command) = self.get_selected_command() {
|
if let Some(command) = self.get_selected_node() {
|
||||||
if self.selected_commands.contains(&command) {
|
if self.selected_commands.contains(&command) {
|
||||||
self.selected_commands.retain(|c| c != &command);
|
self.selected_commands.retain(|c| c != &command);
|
||||||
} else {
|
} else {
|
||||||
|
@ -552,7 +577,7 @@ impl AppState {
|
||||||
self.update_items();
|
self.update_items();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_selected_node(&self) -> Option<&ListNode> {
|
fn get_selected_node(&self) -> Option<Rc<ListNode>> {
|
||||||
let mut selected_index = self.selection.selected().unwrap_or(0);
|
let mut selected_index = self.selection.selected().unwrap_or(0);
|
||||||
|
|
||||||
if !self.at_root() && selected_index == 0 {
|
if !self.at_root() && selected_index == 0 {
|
||||||
|
@ -564,18 +589,17 @@ impl AppState {
|
||||||
|
|
||||||
if let Some(item) = self.filter.item_list().get(selected_index) {
|
if let Some(item) = self.filter.item_list().get(selected_index) {
|
||||||
if !item.has_children {
|
if !item.has_children {
|
||||||
return Some(&item.node);
|
return Some(item.node.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pub fn get_selected_command(&self) -> Option<Command> {
|
|
||||||
self.get_selected_node().map(|node| node.command.clone())
|
|
||||||
}
|
|
||||||
fn get_selected_description(&self) -> Option<String> {
|
fn get_selected_description(&self) -> Option<String> {
|
||||||
self.get_selected_node()
|
self.get_selected_node()
|
||||||
.map(|node| node.description.clone())
|
.map(|node| node.description.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn go_to_selected_dir(&mut self) {
|
pub fn go_to_selected_dir(&mut self) {
|
||||||
let mut selected_index = self.selection.selected().unwrap_or(0);
|
let mut selected_index = self.selection.selected().unwrap_or(0);
|
||||||
|
|
||||||
|
@ -596,6 +620,7 @@ impl AppState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selected_item_is_dir(&self) -> bool {
|
pub fn selected_item_is_dir(&self) -> bool {
|
||||||
let mut selected_index = self.selection.selected().unwrap_or(0);
|
let mut selected_index = self.selection.selected().unwrap_or(0);
|
||||||
|
|
||||||
|
@ -618,18 +643,23 @@ impl AppState {
|
||||||
self.selection.selected().is_some()
|
self.selection.selected().is_some()
|
||||||
&& !(self.selected_item_is_up_dir() || self.selected_item_is_dir())
|
&& !(self.selected_item_is_up_dir() || self.selected_item_is_dir())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selected_item_is_up_dir(&self) -> bool {
|
pub fn selected_item_is_up_dir(&self) -> bool {
|
||||||
let selected_index = self.selection.selected().unwrap_or(0);
|
let selected_index = self.selection.selected().unwrap_or(0);
|
||||||
|
|
||||||
!self.at_root() && selected_index == 0
|
!self.at_root() && selected_index == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enable_preview(&mut self) {
|
fn enable_preview(&mut self) {
|
||||||
if let Some(command) = self.get_selected_command() {
|
if let Some(node) = self.get_selected_node() {
|
||||||
if let Some(preview) = FloatingText::from_command(&command, FloatingTextMode::Preview) {
|
if let Some(preview) =
|
||||||
|
FloatingText::from_command(&node.command, FloatingTextMode::Preview)
|
||||||
|
{
|
||||||
self.spawn_float(preview, 80, 80);
|
self.spawn_float(preview, 80, 80);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enable_description(&mut self) {
|
fn enable_description(&mut self) {
|
||||||
if let Some(command_description) = self.get_selected_description() {
|
if let Some(command_description) = self.get_selected_description() {
|
||||||
let description = FloatingText::new(command_description, FloatingTextMode::Description);
|
let description = FloatingText::new(command_description, FloatingTextMode::Description);
|
||||||
|
@ -640,31 +670,53 @@ impl AppState {
|
||||||
fn handle_enter(&mut self) {
|
fn handle_enter(&mut self) {
|
||||||
if self.selected_item_is_cmd() {
|
if self.selected_item_is_cmd() {
|
||||||
if self.selected_commands.is_empty() {
|
if self.selected_commands.is_empty() {
|
||||||
if let Some(cmd) = self.get_selected_command() {
|
if let Some(node) = self.get_selected_node() {
|
||||||
self.selected_commands.push(cmd);
|
self.selected_commands.push(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let command = RunningCommand::new(self.selected_commands.clone());
|
|
||||||
self.spawn_float(command, 80, 80);
|
let cmd_names = self
|
||||||
self.selected_commands.clear();
|
.selected_commands
|
||||||
|
.iter()
|
||||||
|
.map(|node| node.name.as_str())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let prompt = ConfirmPrompt::new(&cmd_names[..]);
|
||||||
|
self.focus = Focus::ConfirmationPrompt(Float::new(Box::new(prompt), 40, 40));
|
||||||
} else {
|
} else {
|
||||||
self.go_to_selected_dir();
|
self.go_to_selected_dir();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_confirm_command(&mut self) {
|
||||||
|
let commands = self
|
||||||
|
.selected_commands
|
||||||
|
.iter()
|
||||||
|
.map(|node| node.command.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let command = RunningCommand::new(commands);
|
||||||
|
self.spawn_float(command, 80, 80);
|
||||||
|
self.selected_commands.clear();
|
||||||
|
}
|
||||||
|
|
||||||
fn spawn_float<T: FloatContent + 'static>(&mut self, float: T, width: u16, height: u16) {
|
fn spawn_float<T: FloatContent + 'static>(&mut self, float: T, width: u16, height: u16) {
|
||||||
self.focus = Focus::FloatingWindow(Float::new(Box::new(float), width, height));
|
self.focus = Focus::FloatingWindow(Float::new(Box::new(float), width, height));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enter_search(&mut self) {
|
fn enter_search(&mut self) {
|
||||||
self.focus = Focus::Search;
|
self.focus = Focus::Search;
|
||||||
self.filter.activate_search();
|
self.filter.activate_search();
|
||||||
self.selection.select(None);
|
self.selection.select(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit_search(&mut self) {
|
fn exit_search(&mut self) {
|
||||||
self.selection.select(Some(0));
|
self.selection.select(Some(0));
|
||||||
self.focus = Focus::List;
|
self.focus = Focus::List;
|
||||||
self.filter.deactivate_search();
|
self.filter.deactivate_search();
|
||||||
self.update_items();
|
self.update_items();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn refresh_tab(&mut self) {
|
fn refresh_tab(&mut self) {
|
||||||
self.visit_stack = vec![self.tabs[self.current_tab.selected().unwrap()]
|
self.visit_stack = vec![self.tabs[self.current_tab.selected().unwrap()]
|
||||||
.tree
|
.tree
|
||||||
|
|
Loading…
Reference in New Issue
Block a user