mirror of
https://github.com/ChrisTitusTech/linutil.git
synced 2024-11-21 21:09:42 +00:00
implement dynamic shortcut list sizing (#668)
* implement dynamic shortcut list sizing * Remove all dynamic allocations from shortcut creation
This commit is contained in:
parent
97b7d2860a
commit
a2480bf1bd
|
@ -4,13 +4,13 @@ use ratatui::{
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::hint::ShortcutList;
|
use crate::hint::Shortcut;
|
||||||
|
|
||||||
pub trait FloatContent {
|
pub trait FloatContent {
|
||||||
fn draw(&mut self, frame: &mut Frame, area: Rect);
|
fn draw(&mut self, frame: &mut Frame, area: Rect);
|
||||||
fn handle_key_event(&mut self, key: &KeyEvent) -> bool;
|
fn handle_key_event(&mut self, key: &KeyEvent) -> bool;
|
||||||
fn is_finished(&self) -> bool;
|
fn is_finished(&self) -> bool;
|
||||||
fn get_shortcut_list(&self) -> ShortcutList;
|
fn get_shortcut_list(&self) -> (&str, Box<[Shortcut]>);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Float {
|
pub struct Float {
|
||||||
|
@ -69,7 +69,7 @@ impl Float {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_shortcut_list(&self) -> ShortcutList {
|
pub fn get_shortcut_list(&self) -> (&str, Box<[Shortcut]>) {
|
||||||
self.content.get_shortcut_list()
|
self.content.get_shortcut_list()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,7 @@ use std::{
|
||||||
io::{Cursor, Read as _, Seek, SeekFrom, Write as _},
|
io::{Cursor, Read as _, Seek, SeekFrom, Write as _},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{float::FloatContent, hint::Shortcut};
|
||||||
float::FloatContent,
|
|
||||||
hint::{Shortcut, ShortcutList},
|
|
||||||
};
|
|
||||||
|
|
||||||
use linutil_core::Command;
|
use linutil_core::Command;
|
||||||
|
|
||||||
|
@ -293,16 +290,16 @@ impl FloatContent for FloatingText {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_shortcut_list(&self) -> ShortcutList {
|
fn get_shortcut_list(&self) -> (&str, Box<[Shortcut]>) {
|
||||||
ShortcutList {
|
(
|
||||||
scope_name: self.mode_title,
|
self.mode_title,
|
||||||
hints: vec![
|
Box::new([
|
||||||
Shortcut::new(vec!["j", "Down"], "Scroll down"),
|
Shortcut::new("Scroll down", ["j", "Down"]),
|
||||||
Shortcut::new(vec!["k", "Up"], "Scroll up"),
|
Shortcut::new("Scroll up", ["k", "Up"]),
|
||||||
Shortcut::new(vec!["h", "Left"], "Scroll left"),
|
Shortcut::new("Scroll left", ["h", "Left"]),
|
||||||
Shortcut::new(vec!["l", "Right"], "Scroll right"),
|
Shortcut::new("Scroll right", ["l", "Right"]),
|
||||||
Shortcut::new(vec!["Enter", "p", "d", "g"], "Close window"),
|
Shortcut::new("Close window", ["Enter", "p", "d", "g"]),
|
||||||
],
|
]),
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
145
tui/src/hint.rs
145
tui/src/hint.rs
|
@ -1,20 +1,10 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Margin, Rect},
|
|
||||||
style::{Style, Stylize},
|
style::{Style, Stylize},
|
||||||
text::{Line, Span},
|
text::{Line, Span},
|
||||||
widgets::{Block, Borders, Paragraph},
|
|
||||||
Frame,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::state::{AppState, Focus};
|
|
||||||
|
|
||||||
pub const SHORTCUT_LINES: usize = 2;
|
|
||||||
|
|
||||||
pub struct ShortcutList {
|
|
||||||
pub scope_name: &'static str,
|
|
||||||
pub hints: Vec<Shortcut>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Shortcut {
|
pub struct Shortcut {
|
||||||
pub key_sequences: Vec<Span<'static>>,
|
pub key_sequences: Vec<Span<'static>>,
|
||||||
pub desc: &'static str,
|
pub desc: &'static str,
|
||||||
|
@ -32,54 +22,63 @@ fn add_spacing(list: Vec<Vec<Span>>) -> Line {
|
||||||
pub fn span_vec_len(span_vec: &[Span]) -> usize {
|
pub fn span_vec_len(span_vec: &[Span]) -> usize {
|
||||||
span_vec.iter().rfold(0, |init, s| init + s.width())
|
span_vec.iter().rfold(0, |init, s| init + s.width())
|
||||||
}
|
}
|
||||||
impl ShortcutList {
|
|
||||||
pub fn draw(&self, frame: &mut Frame, area: Rect) {
|
|
||||||
let block = Block::default()
|
|
||||||
.title(format!(" {} ", self.scope_name))
|
|
||||||
.borders(Borders::all());
|
|
||||||
let inner_area = area.inner(Margin::new(1, 1));
|
|
||||||
let shortcut_spans: Vec<Vec<Span>> = self.hints.iter().map(|h| h.to_spans()).collect();
|
|
||||||
|
|
||||||
let mut lines: Vec<Line> = Vec::with_capacity(SHORTCUT_LINES);
|
pub fn create_shortcut_list(
|
||||||
|
shortcuts: impl IntoIterator<Item = Shortcut>,
|
||||||
|
render_width: u16,
|
||||||
|
) -> Box<[Line<'static>]> {
|
||||||
|
let hints = shortcuts.into_iter().collect::<Box<[Shortcut]>>();
|
||||||
|
|
||||||
let shortcut_list = (0..SHORTCUT_LINES - 1).fold(shortcut_spans, |mut acc, _| {
|
let mut shortcut_spans: Vec<Vec<Span<'static>>> = hints.iter().map(|h| h.to_spans()).collect();
|
||||||
let split_idx = acc
|
|
||||||
|
let mut lines: Vec<Line<'static>> = vec![];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let split_idx = shortcut_spans
|
||||||
.iter()
|
.iter()
|
||||||
.scan(0_usize, |total_len, s| {
|
.scan(0usize, |total_len, s| {
|
||||||
|
// take at least one so that we guarantee that we drain the list
|
||||||
|
// otherwise, this might lock up if there's a shortcut that exceeds the window width
|
||||||
|
if *total_len == 0 {
|
||||||
|
*total_len += span_vec_len(s) + 4;
|
||||||
|
Some(())
|
||||||
|
} else {
|
||||||
*total_len += span_vec_len(s);
|
*total_len += span_vec_len(s);
|
||||||
if *total_len > inner_area.width as usize {
|
if *total_len > render_width as usize {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
*total_len += 4;
|
*total_len += 4;
|
||||||
Some(1)
|
Some(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
let new_shortcut_list = acc.split_off(split_idx);
|
let rest = shortcut_spans.split_off(split_idx);
|
||||||
lines.push(add_spacing(acc));
|
lines.push(add_spacing(shortcut_spans));
|
||||||
|
|
||||||
new_shortcut_list
|
if rest.is_empty() {
|
||||||
});
|
break;
|
||||||
lines.push(add_spacing(shortcut_list));
|
} else {
|
||||||
|
shortcut_spans = rest;
|
||||||
let p = Paragraph::new(lines).block(block);
|
|
||||||
frame.render_widget(p, area);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lines.into_boxed_slice()
|
||||||
|
}
|
||||||
|
|
||||||
impl Shortcut {
|
impl Shortcut {
|
||||||
pub fn new(key_sequences: Vec<&'static str>, desc: &'static str) -> Self {
|
pub fn new<const N: usize>(desc: &'static str, key_sequences: [&'static str; N]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
key_sequences: key_sequences
|
key_sequences: key_sequences
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| Span::styled(*s, Style::default().bold()))
|
.map(|s| Span::styled(Cow::<'static, str>::Borrowed(s), Style::default().bold()))
|
||||||
.collect(),
|
.collect(),
|
||||||
desc,
|
desc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_spans(&self) -> Vec<Span> {
|
fn to_spans(&self) -> Vec<Span<'static>> {
|
||||||
let mut ret: Vec<_> = self
|
let mut ret: Vec<_> = self
|
||||||
.key_sequences
|
.key_sequences
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -95,77 +94,3 @@ impl Shortcut {
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_list_item_shortcut(state: &AppState) -> Vec<Shortcut> {
|
|
||||||
if state.selected_item_is_dir() {
|
|
||||||
vec![Shortcut::new(
|
|
||||||
vec!["l", "Right", "Enter"],
|
|
||||||
"Go to selected dir",
|
|
||||||
)]
|
|
||||||
} else {
|
|
||||||
vec![
|
|
||||||
Shortcut::new(vec!["l", "Right", "Enter"], "Run selected command"),
|
|
||||||
Shortcut::new(vec!["p"], "Enable preview"),
|
|
||||||
Shortcut::new(vec!["d"], "Command Description"),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_shortcuts(state: &AppState, frame: &mut Frame, area: Rect) {
|
|
||||||
match state.focus {
|
|
||||||
Focus::Search => ShortcutList {
|
|
||||||
scope_name: "Search bar",
|
|
||||||
hints: vec![Shortcut::new(vec!["Enter"], "Finish search")],
|
|
||||||
},
|
|
||||||
|
|
||||||
Focus::List => {
|
|
||||||
let mut hints = Vec::new();
|
|
||||||
hints.push(Shortcut::new(vec!["q", "CTRL-c"], "Exit linutil"));
|
|
||||||
|
|
||||||
if state.at_root() {
|
|
||||||
hints.push(Shortcut::new(vec!["h", "Left"], "Focus tab list"));
|
|
||||||
hints.extend(get_list_item_shortcut(state));
|
|
||||||
} else if state.selected_item_is_up_dir() {
|
|
||||||
hints.push(Shortcut::new(
|
|
||||||
vec!["l", "Right", "Enter", "h", "Left"],
|
|
||||||
"Go to parent directory",
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
hints.push(Shortcut::new(vec!["h", "Left"], "Go to parent directory"));
|
|
||||||
hints.extend(get_list_item_shortcut(state));
|
|
||||||
}
|
|
||||||
hints.push(Shortcut::new(vec!["k", "Up"], "Select item above"));
|
|
||||||
hints.push(Shortcut::new(vec!["j", "Down"], "Select item below"));
|
|
||||||
hints.push(Shortcut::new(vec!["t"], "Next theme"));
|
|
||||||
hints.push(Shortcut::new(vec!["T"], "Previous theme"));
|
|
||||||
if state.is_current_tab_multi_selectable() {
|
|
||||||
hints.push(Shortcut::new(vec!["v"], "Toggle multi-selection mode"));
|
|
||||||
hints.push(Shortcut::new(vec!["Space"], "Select multiple commands"));
|
|
||||||
}
|
|
||||||
hints.push(Shortcut::new(vec!["Tab"], "Next tab"));
|
|
||||||
hints.push(Shortcut::new(vec!["Shift-Tab"], "Previous tab"));
|
|
||||||
hints.push(Shortcut::new(vec!["g"], "Important actions guide"));
|
|
||||||
ShortcutList {
|
|
||||||
scope_name: "Command list",
|
|
||||||
hints,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Focus::TabList => ShortcutList {
|
|
||||||
scope_name: "Tab list",
|
|
||||||
hints: vec![
|
|
||||||
Shortcut::new(vec!["q", "CTRL-c"], "Exit linutil"),
|
|
||||||
Shortcut::new(vec!["l", "Right", "Enter"], "Focus action list"),
|
|
||||||
Shortcut::new(vec!["k", "Up"], "Select item above"),
|
|
||||||
Shortcut::new(vec!["j", "Down"], "Select item below"),
|
|
||||||
Shortcut::new(vec!["t"], "Next theme"),
|
|
||||||
Shortcut::new(vec!["T"], "Previous theme"),
|
|
||||||
Shortcut::new(vec!["Tab"], "Next tab"),
|
|
||||||
Shortcut::new(vec!["Shift-Tab"], "Previous tab"),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
Focus::FloatingWindow(ref float) => float.get_shortcut_list(),
|
|
||||||
}
|
|
||||||
.draw(frame, area);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
use crate::{
|
use crate::{float::FloatContent, hint::Shortcut};
|
||||||
float::FloatContent,
|
|
||||||
hint::{Shortcut, ShortcutList},
|
|
||||||
};
|
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
use linutil_core::Command;
|
use linutil_core::Command;
|
||||||
use oneshot::{channel, Receiver};
|
use oneshot::{channel, Receiver};
|
||||||
|
@ -120,17 +117,17 @@ impl FloatContent for RunningCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_shortcut_list(&self) -> ShortcutList {
|
fn get_shortcut_list(&self) -> (&str, Box<[Shortcut]>) {
|
||||||
if self.is_finished() {
|
if self.is_finished() {
|
||||||
ShortcutList {
|
(
|
||||||
scope_name: "Finished command",
|
"Finished command",
|
||||||
hints: vec![Shortcut::new(vec!["Enter", "q"], "Close window")],
|
Box::new([Shortcut::new("Close window", ["Enter", "q"])]),
|
||||||
}
|
)
|
||||||
} else {
|
} else {
|
||||||
ShortcutList {
|
(
|
||||||
scope_name: "Running command",
|
"Running command",
|
||||||
hints: vec![Shortcut::new(vec!["CTRL-c"], "Kill the command")],
|
Box::new([Shortcut::new("Kill the command", ["CTRL-c"])]),
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
125
tui/src/state.rs
125
tui/src/state.rs
|
@ -2,7 +2,7 @@ use crate::{
|
||||||
filter::{Filter, SearchAction},
|
filter::{Filter, SearchAction},
|
||||||
float::{Float, FloatContent},
|
float::{Float, FloatContent},
|
||||||
floating_text::{FloatingText, FloatingTextMode},
|
floating_text::{FloatingText, FloatingTextMode},
|
||||||
hint::{draw_shortcuts, SHORTCUT_LINES},
|
hint::{create_shortcut_list, Shortcut},
|
||||||
running_command::RunningCommand,
|
running_command::RunningCommand,
|
||||||
theme::Theme,
|
theme::Theme,
|
||||||
};
|
};
|
||||||
|
@ -12,9 +12,9 @@ use linutil_core::{Command, ListNode, Tab};
|
||||||
#[cfg(feature = "tips")]
|
#[cfg(feature = "tips")]
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Alignment, Constraint, Direction, Layout},
|
layout::{Alignment, Constraint, Direction, Flex, Layout},
|
||||||
style::{Style, Stylize},
|
style::{Style, Stylize},
|
||||||
text::{Line, Span},
|
text::{Line, Span, Text},
|
||||||
widgets::{Block, Borders, List, ListState, Paragraph},
|
widgets::{Block, Borders, List, ListState, Paragraph},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
@ -76,6 +76,7 @@ impl AppState {
|
||||||
pub fn new(theme: Theme, override_validation: bool) -> Self {
|
pub fn new(theme: Theme, override_validation: bool) -> Self {
|
||||||
let tabs = linutil_core::get_tabs(!override_validation);
|
let tabs = linutil_core::get_tabs(!override_validation);
|
||||||
let root_id = tabs[0].tree.root().id();
|
let root_id = tabs[0].tree.root().id();
|
||||||
|
|
||||||
let mut state = Self {
|
let mut state = Self {
|
||||||
theme,
|
theme,
|
||||||
focus: Focus::List,
|
focus: Focus::List,
|
||||||
|
@ -90,9 +91,82 @@ impl AppState {
|
||||||
#[cfg(feature = "tips")]
|
#[cfg(feature = "tips")]
|
||||||
tip: get_random_tip(),
|
tip: get_random_tip(),
|
||||||
};
|
};
|
||||||
|
|
||||||
state.update_items();
|
state.update_items();
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_list_item_shortcut(&self) -> Box<[Shortcut]> {
|
||||||
|
if self.selected_item_is_dir() {
|
||||||
|
Box::new([Shortcut::new("Go to selected dir", ["l", "Right", "Enter"])])
|
||||||
|
} else {
|
||||||
|
Box::new([
|
||||||
|
Shortcut::new("Run selected command", ["l", "Right", "Enter"]),
|
||||||
|
Shortcut::new("Enable preview", ["p"]),
|
||||||
|
Shortcut::new("Command Description", ["d"]),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_keybinds(&self) -> (&str, Box<[Shortcut]>) {
|
||||||
|
match self.focus {
|
||||||
|
Focus::Search => (
|
||||||
|
"Search bar",
|
||||||
|
Box::new([Shortcut::new("Finish search", ["Enter"])]),
|
||||||
|
),
|
||||||
|
|
||||||
|
Focus::List => {
|
||||||
|
let mut hints = Vec::new();
|
||||||
|
hints.push(Shortcut::new("Exit linutil", ["q", "CTRL-c"]));
|
||||||
|
|
||||||
|
if self.at_root() {
|
||||||
|
hints.push(Shortcut::new("Focus tab list", ["h", "Left"]));
|
||||||
|
hints.extend(self.get_list_item_shortcut());
|
||||||
|
} else if self.selected_item_is_up_dir() {
|
||||||
|
hints.push(Shortcut::new(
|
||||||
|
"Go to parent directory",
|
||||||
|
["l", "Right", "Enter", "h", "Left"],
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
hints.push(Shortcut::new("Go to parent directory", ["h", "Left"]));
|
||||||
|
hints.extend(self.get_list_item_shortcut());
|
||||||
|
}
|
||||||
|
|
||||||
|
hints.push(Shortcut::new("Select item above", ["k", "Up"]));
|
||||||
|
hints.push(Shortcut::new("Select item below", ["j", "Down"]));
|
||||||
|
hints.push(Shortcut::new("Next theme", ["t"]));
|
||||||
|
hints.push(Shortcut::new("Previous theme", ["T"]));
|
||||||
|
|
||||||
|
if self.is_current_tab_multi_selectable() {
|
||||||
|
hints.push(Shortcut::new("Toggle multi-selection mode", ["v"]));
|
||||||
|
hints.push(Shortcut::new("Select multiple commands", ["Space"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
hints.push(Shortcut::new("Next tab", ["Tab"]));
|
||||||
|
hints.push(Shortcut::new("Previous tab", ["Shift-Tab"]));
|
||||||
|
hints.push(Shortcut::new("Important actions guide", ["g"]));
|
||||||
|
|
||||||
|
("Command list", hints.into_boxed_slice())
|
||||||
|
}
|
||||||
|
|
||||||
|
Focus::TabList => (
|
||||||
|
"Tab list",
|
||||||
|
Box::new([
|
||||||
|
Shortcut::new("Exit linutil", ["q", "CTRL-c"]),
|
||||||
|
Shortcut::new("Focus action list", ["l", "Right", "Enter"]),
|
||||||
|
Shortcut::new("Select item above", ["k", "Up"]),
|
||||||
|
Shortcut::new("Select item below", ["j", "Down"]),
|
||||||
|
Shortcut::new("Next theme", ["t"]),
|
||||||
|
Shortcut::new("Previous theme", ["T"]),
|
||||||
|
Shortcut::new("Next tab", ["Tab"]),
|
||||||
|
Shortcut::new("Previous tab", ["Shift-Tab"]),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
|
||||||
|
Focus::FloatingWindow(ref float) => float.get_shortcut_list(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self, frame: &mut Frame) {
|
pub fn draw(&mut self, frame: &mut Frame) {
|
||||||
let terminal_size = frame.area();
|
let terminal_size = frame.area();
|
||||||
|
|
||||||
|
@ -153,12 +227,26 @@ impl AppState {
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
.max(str1.len() + str2.len());
|
.max(str1.len() + str2.len());
|
||||||
|
|
||||||
|
let (keybind_scope, shortcuts) = self.get_keybinds();
|
||||||
|
|
||||||
|
let keybind_render_width = terminal_size.width - 2;
|
||||||
|
|
||||||
|
let keybinds_block = Block::default()
|
||||||
|
.title(format!(" {} ", keybind_scope))
|
||||||
|
.borders(Borders::all());
|
||||||
|
|
||||||
|
let keybinds = create_shortcut_list(shortcuts, keybind_render_width);
|
||||||
|
let n_lines = keybinds.len() as u16;
|
||||||
|
|
||||||
|
let keybind_para = Paragraph::new(Text::from_iter(keybinds)).block(keybinds_block);
|
||||||
|
|
||||||
let vertical = Layout::default()
|
let vertical = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([
|
.constraints([
|
||||||
Constraint::Percentage(100),
|
Constraint::Percentage(0),
|
||||||
Constraint::Min(2 + SHORTCUT_LINES as u16),
|
Constraint::Max(n_lines as u16 + 2),
|
||||||
])
|
])
|
||||||
|
.flex(Flex::Legacy)
|
||||||
.margin(0)
|
.margin(0)
|
||||||
.split(frame.area());
|
.split(frame.area());
|
||||||
|
|
||||||
|
@ -305,7 +393,7 @@ impl AppState {
|
||||||
float.draw(frame, chunks[1]);
|
float.draw(frame, chunks[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_shortcuts(self, frame, vertical[1]);
|
frame.render_widget(keybind_para, vertical[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_key(&mut self, key: &KeyEvent) -> bool {
|
pub fn handle_key(&mut self, key: &KeyEvent) -> bool {
|
||||||
|
@ -355,11 +443,13 @@ impl AppState {
|
||||||
self.focus = Focus::List;
|
self.focus = Focus::List;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(),
|
||||||
SearchAction::None => {}
|
SearchAction::None => {}
|
||||||
},
|
},
|
||||||
|
|
||||||
Focus::TabList => match key.code {
|
Focus::TabList => match key.code {
|
||||||
KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right => self.focus = Focus::List,
|
KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right => self.focus = Focus::List,
|
||||||
|
|
||||||
|
@ -381,19 +471,14 @@ impl AppState {
|
||||||
KeyCode::Char('g') => self.toggle_task_list_guide(),
|
KeyCode::Char('g') => self.toggle_task_list_guide(),
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
|
|
||||||
Focus::List if key.kind != KeyEventKind::Release => match key.code {
|
Focus::List if key.kind != KeyEventKind::Release => match key.code {
|
||||||
KeyCode::Char('j') | KeyCode::Down => self.selection.select_next(),
|
KeyCode::Char('j') | KeyCode::Down => self.selection.select_next(),
|
||||||
KeyCode::Char('k') | KeyCode::Up => self.selection.select_previous(),
|
KeyCode::Char('k') | KeyCode::Up => self.selection.select_previous(),
|
||||||
KeyCode::Char('p') | KeyCode::Char('P') => self.enable_preview(),
|
KeyCode::Char('p') | KeyCode::Char('P') => self.enable_preview(),
|
||||||
KeyCode::Char('d') | KeyCode::Char('D') => self.enable_description(),
|
KeyCode::Char('d') | KeyCode::Char('D') => self.enable_description(),
|
||||||
KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right => self.handle_enter(),
|
KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right => self.handle_enter(),
|
||||||
KeyCode::Char('h') | KeyCode::Left => {
|
KeyCode::Char('h') | KeyCode::Left => self.go_back(),
|
||||||
if self.at_root() {
|
|
||||||
self.focus = Focus::TabList;
|
|
||||||
} else {
|
|
||||||
self.enter_parent_directory();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Char('/') => self.enter_search(),
|
KeyCode::Char('/') => self.enter_search(),
|
||||||
KeyCode::Char('t') => self.theme.next(),
|
KeyCode::Char('t') => self.theme.next(),
|
||||||
KeyCode::Char('T') => self.theme.prev(),
|
KeyCode::Char('T') => self.theme.prev(),
|
||||||
|
@ -402,10 +487,12 @@ impl AppState {
|
||||||
KeyCode::Char(' ') if self.multi_select => self.toggle_selection(),
|
KeyCode::Char(' ') if self.multi_select => self.toggle_selection(),
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
|
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_multi_select(&mut self) {
|
fn toggle_multi_select(&mut self) {
|
||||||
if self.is_current_tab_multi_selectable() {
|
if self.is_current_tab_multi_selectable() {
|
||||||
self.multi_select = !self.multi_select;
|
self.multi_select = !self.multi_select;
|
||||||
|
@ -414,6 +501,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_command() {
|
||||||
if self.selected_commands.contains(&command) {
|
if self.selected_commands.contains(&command) {
|
||||||
|
@ -423,12 +511,14 @@ impl AppState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_current_tab_multi_selectable(&self) -> bool {
|
pub fn is_current_tab_multi_selectable(&self) -> bool {
|
||||||
let index = self.current_tab.selected().unwrap_or(0);
|
let index = self.current_tab.selected().unwrap_or(0);
|
||||||
self.tabs
|
self.tabs
|
||||||
.get(index)
|
.get(index)
|
||||||
.map_or(false, |tab| tab.multi_selectable)
|
.map_or(false, |tab| tab.multi_selectable)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_items(&mut self) {
|
fn update_items(&mut self) {
|
||||||
self.filter.update_items(
|
self.filter.update_items(
|
||||||
&self.tabs,
|
&self.tabs,
|
||||||
|
@ -448,11 +538,20 @@ impl AppState {
|
||||||
self.visit_stack.len() == 1
|
self.visit_stack.len() == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn go_back(&mut self) {
|
||||||
|
if self.at_root() {
|
||||||
|
self.focus = Focus::TabList;
|
||||||
|
} else {
|
||||||
|
self.enter_parent_directory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn enter_parent_directory(&mut self) {
|
fn enter_parent_directory(&mut self) {
|
||||||
self.visit_stack.pop();
|
self.visit_stack.pop();
|
||||||
self.selection.select(Some(0));
|
self.selection.select(Some(0));
|
||||||
self.update_items();
|
self.update_items();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_selected_node(&self) -> Option<&ListNode> {
|
fn get_selected_node(&self) -> Option<&ListNode> {
|
||||||
let mut selected_index = self.selection.selected().unwrap_or(0);
|
let mut selected_index = self.selection.selected().unwrap_or(0);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user