Skip to content

Commit 694f978

Browse files
Merge pull request #293 from JustLinuxUser/shortcuts
Create a dynamic hotkey list
2 parents 896b81e + d3b3eae commit 694f978

File tree

7 files changed

+311
-18
lines changed

7 files changed

+311
-18
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
/build
33
rust/target
44
rust/build
5+
Cargo.lock

src/float.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ use ratatui::{
44
Frame,
55
};
66

7+
use crate::hint::ShortcutList;
8+
79
pub trait FloatContent {
810
fn draw(&mut self, frame: &mut Frame, area: Rect);
911
fn handle_key_event(&mut self, key: &KeyEvent) -> bool;
1012
fn is_finished(&self) -> bool;
13+
fn get_shortcut_list(&self) -> ShortcutList;
1114
}
1215

1316
pub struct Float {
@@ -69,4 +72,8 @@ impl Float {
6972
_ => self.content.handle_key_event(key),
7073
}
7174
}
75+
76+
pub fn get_shortcut_list(&self) -> ShortcutList {
77+
self.content.get_shortcut_list()
78+
}
7279
}

src/floating_text.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
use crate::{float::FloatContent, running_command::Command};
1+
use crate::{
2+
float::FloatContent,
3+
hint::{Shortcut, ShortcutList},
4+
running_command::Command,
5+
};
26
use crossterm::event::{KeyCode, KeyEvent};
37
use ratatui::{
48
layout::Rect,
@@ -103,4 +107,15 @@ impl FloatContent for FloatingText {
103107
fn is_finished(&self) -> bool {
104108
true
105109
}
110+
111+
fn get_shortcut_list(&self) -> ShortcutList {
112+
ShortcutList {
113+
scope_name: "Floating text",
114+
hints: vec![
115+
Shortcut::new(vec!["j", "Down"], "Scroll down"),
116+
Shortcut::new(vec!["k", "Up"], "Scroll up"),
117+
Shortcut::new(vec!["Enter", "q"], "Close window"),
118+
],
119+
}
120+
}
106121
}

src/hint.rs

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
use ratatui::{
2+
layout::{Margin, Rect},
3+
style::{Style, Stylize},
4+
text::{Line, Span},
5+
widgets::{Block, Borders, Paragraph},
6+
Frame,
7+
};
8+
9+
use crate::state::{AppState, Focus};
10+
11+
pub const SHORTCUT_LINES: usize = 2;
12+
13+
pub struct ShortcutList {
14+
pub scope_name: &'static str,
15+
pub hints: Vec<Shortcut>,
16+
}
17+
18+
pub struct Shortcut {
19+
pub key_sequenses: Vec<Span<'static>>,
20+
pub desc: &'static str,
21+
}
22+
23+
pub fn span_vec_len(span_vec: &[Span]) -> usize {
24+
span_vec.iter().rfold(0, |init, s| init + s.width())
25+
}
26+
impl ShortcutList {
27+
pub fn draw(&self, frame: &mut Frame, area: Rect) {
28+
let block = Block::default()
29+
.title(self.scope_name)
30+
.borders(Borders::all());
31+
let inner_area = area.inner(Margin::new(1, 1));
32+
let mut shortcut_list: Vec<Vec<Span>> = self.hints.iter().map(|h| h.to_spans()).collect();
33+
34+
let mut lines = vec![Line::default(); SHORTCUT_LINES];
35+
let mut idx = 0;
36+
37+
while idx < SHORTCUT_LINES - 1 {
38+
let split_idx = shortcut_list
39+
.iter()
40+
.scan(0usize, |total_len, s| {
41+
*total_len += span_vec_len(s);
42+
if *total_len > inner_area.width as usize {
43+
None
44+
} else {
45+
*total_len += 4;
46+
Some(1)
47+
}
48+
})
49+
.count();
50+
let new_shortcut_list = shortcut_list.split_off(split_idx);
51+
let line: Vec<_> = shortcut_list
52+
.into_iter()
53+
.flat_map(|mut s| {
54+
s.push(Span::default().content(" "));
55+
s
56+
})
57+
.collect();
58+
shortcut_list = new_shortcut_list;
59+
lines[idx] = line.into();
60+
idx += 1;
61+
}
62+
lines[idx] = shortcut_list
63+
.into_iter()
64+
.flat_map(|mut s| {
65+
s.push(Span::default().content(" "));
66+
s
67+
})
68+
.collect();
69+
70+
let p = Paragraph::new(lines).block(block);
71+
frame.render_widget(p, area);
72+
}
73+
}
74+
75+
impl Shortcut {
76+
pub fn new(key_sequences: Vec<&'static str>, desc: &'static str) -> Self {
77+
Self {
78+
key_sequenses: key_sequences
79+
.iter()
80+
.map(|s| Span::styled(*s, Style::default().bold()))
81+
.collect(),
82+
desc,
83+
}
84+
}
85+
86+
fn to_spans(&self) -> Vec<Span> {
87+
let mut ret: Vec<_> = self
88+
.key_sequenses
89+
.iter()
90+
.flat_map(|seq| {
91+
[
92+
Span::default().content("["),
93+
seq.clone(),
94+
Span::default().content("] "),
95+
]
96+
})
97+
.collect();
98+
ret.push(Span::styled(self.desc, Style::default().italic()));
99+
ret
100+
}
101+
}
102+
103+
fn get_list_item_shortcut(state: &AppState) -> Shortcut {
104+
if state.selected_item_is_dir() {
105+
Shortcut::new(vec!["l", "Right", "Enter"], "Go to selected dir")
106+
} else {
107+
Shortcut::new(vec!["l", "Right", "Enter"], "Run selected command")
108+
}
109+
}
110+
111+
pub fn draw_shortcuts(state: &AppState, frame: &mut Frame, area: Rect) {
112+
match state.focus {
113+
Focus::Search => ShortcutList {
114+
scope_name: "Search bar",
115+
hints: vec![Shortcut::new(vec!["Enter"], "Finish search")],
116+
},
117+
Focus::List => {
118+
let mut hints = Vec::new();
119+
hints.push(Shortcut::new(vec!["q", "CTRL-c"], "Exit linutil"));
120+
if state.at_root() {
121+
hints.push(Shortcut::new(vec!["h", "Left", "Tab"], "Focus tab list"));
122+
hints.push(get_list_item_shortcut(state));
123+
} else {
124+
if state.selected_item_is_up_dir() {
125+
hints.push(Shortcut::new(
126+
vec!["l", "Right", "Enter", "h", "Left"],
127+
"Go to parrent directory",
128+
));
129+
} else {
130+
hints.push(Shortcut::new(vec!["h", "Left"], "Go to parrent directory"));
131+
hints.push(get_list_item_shortcut(state));
132+
if state.selected_item_is_cmd() {
133+
hints.push(Shortcut::new(vec!["p"], "Enable preview"));
134+
}
135+
}
136+
hints.push(Shortcut::new(vec!["Tab"], "Focus tab list"));
137+
};
138+
hints.push(Shortcut::new(vec!["k", "Up"], "Select item above"));
139+
hints.push(Shortcut::new(vec!["j", "Down"], "Select item below"));
140+
hints.push(Shortcut::new(vec!["t"], "Next theme"));
141+
hints.push(Shortcut::new(vec!["T"], "Previous theme"));
142+
ShortcutList {
143+
scope_name: "Item list",
144+
hints,
145+
}
146+
}
147+
Focus::TabList => ShortcutList {
148+
scope_name: "Tab list",
149+
hints: vec![
150+
Shortcut::new(vec!["q", "CTRL-c"], "Exit linutil"),
151+
Shortcut::new(vec!["l", "Right", "Tab", "Enter"], "Focus action list"),
152+
Shortcut::new(vec!["k", "Up"], "Select item above"),
153+
Shortcut::new(vec!["j", "Down"], "Select item below"),
154+
Shortcut::new(vec!["t"], "Next theme"),
155+
Shortcut::new(vec!["T"], "Previous theme"),
156+
],
157+
},
158+
Focus::FloatingWindow(ref float) => float.get_shortcut_list(),
159+
}
160+
.draw(frame, area);
161+
}

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod filter;
22
mod float;
33
mod floating_text;
4+
mod hint;
45
mod running_command;
56
pub mod state;
67
mod tabs;
@@ -71,7 +72,6 @@ fn main() -> std::io::Result<()> {
7172
fn run<B: Backend>(terminal: &mut Terminal<B>, state: &mut AppState) -> io::Result<()> {
7273
loop {
7374
terminal.draw(|frame| state.draw(frame)).unwrap();
74-
7575
// Wait for an event
7676
if !event::poll(Duration::from_millis(10))? {
7777
continue;

src/running_command.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use crate::float::FloatContent;
1+
use crate::{
2+
float::FloatContent,
3+
hint::{Shortcut, ShortcutList},
4+
};
25
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
36
use oneshot::{channel, Receiver};
47
use portable_pty::{
@@ -123,6 +126,20 @@ impl FloatContent for RunningCommand {
123126
true
124127
}
125128
}
129+
130+
fn get_shortcut_list(&self) -> ShortcutList {
131+
if self.is_finished() {
132+
ShortcutList {
133+
scope_name: "Finished command",
134+
hints: vec![Shortcut::new(vec!["Enter", "q"], "Close window")],
135+
}
136+
} else {
137+
ShortcutList {
138+
scope_name: "Running command",
139+
hints: vec![Shortcut::new(vec!["CTRL-c"], "Kill the command")],
140+
}
141+
}
142+
}
126143
}
127144

128145
impl RunningCommand {

0 commit comments

Comments
 (0)