Skip to content

Commit 7332293

Browse files
committed
refactor FloatContent interface
1 parent 476dbf3 commit 7332293

File tree

4 files changed

+96
-109
lines changed

4 files changed

+96
-109
lines changed

tui/src/confirmation.rs

+9-18
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,7 @@ use std::borrow::Cow;
33
use crate::{float::FloatContent, hint::Shortcut};
44

55
use crossterm::event::{KeyCode, KeyEvent};
6-
use ratatui::{
7-
layout::Alignment,
8-
prelude::*,
9-
widgets::{Block, Borders, Clear, List},
10-
};
6+
use ratatui::{prelude::*, widgets::List};
117

128
pub enum ConfirmStatus {
139
Confirm,
@@ -57,19 +53,15 @@ impl ConfirmPrompt {
5753
}
5854

5955
impl FloatContent for ConfirmPrompt {
60-
fn draw(&mut self, frame: &mut Frame, area: Rect) {
61-
let block = Block::default()
62-
.borders(Borders::ALL)
63-
.title(" Confirm selections ")
64-
.title_bottom(" [y] to continue, [n] to abort ")
65-
.title_alignment(Alignment::Center)
66-
.title_style(Style::default().bold())
67-
.style(Style::default());
68-
69-
frame.render_widget(block.clone(), area);
56+
fn top_title(&self) -> Option<Line<'_>> {
57+
Some(Line::from(" Confirm selections ").style(Style::default().bold()))
58+
}
7059

71-
let inner_area = block.inner(area);
60+
fn bottom_title(&self) -> Option<Line<'_>> {
61+
Some(Line::from(" [y] to continue, [n] to abort ").italic())
62+
}
7263

64+
fn draw(&mut self, frame: &mut Frame, area: Rect) {
7365
let paths_text = self
7466
.names
7567
.iter()
@@ -80,8 +72,7 @@ impl FloatContent for ConfirmPrompt {
8072
})
8173
.collect::<Text>();
8274

83-
frame.render_widget(Clear, inner_area);
84-
frame.render_widget(List::new(paths_text), inner_area);
75+
frame.render_widget(List::new(paths_text), area);
8576
}
8677

8778
fn handle_key_event(&mut self, key: &KeyEvent) -> bool {

tui/src/float.rs

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
use crossterm::event::{KeyCode, KeyEvent};
22
use ratatui::{
3-
layout::{Constraint, Direction, Layout, Rect},
3+
layout::{Alignment, Constraint, Direction, Layout, Rect},
4+
style::{Style, Stylize},
5+
text::Line,
6+
widgets::{Block, Borders, Clear},
47
Frame,
58
};
69

710
use crate::hint::Shortcut;
811

912
pub trait FloatContent {
1013
fn draw(&mut self, frame: &mut Frame, area: Rect);
14+
fn top_title(&self) -> Option<Line<'_>>;
15+
fn bottom_title(&self) -> Option<Line<'_>>;
1116
fn handle_key_event(&mut self, key: &KeyEvent) -> bool;
1217
fn is_finished(&self) -> bool;
1318
fn get_shortcut_list(&self) -> (&str, Box<[Shortcut]>);
@@ -50,7 +55,22 @@ impl<Content: FloatContent + ?Sized> Float<Content> {
5055

5156
pub fn draw(&mut self, frame: &mut Frame, parent_area: Rect) {
5257
let popup_area = self.floating_window(parent_area);
53-
self.content.draw(frame, popup_area);
58+
let mut block = Block::new()
59+
.borders(Borders::ALL)
60+
.title_alignment(Alignment::Center)
61+
.style(Style::new().reset());
62+
63+
if let Some(top_title) = self.content.top_title() {
64+
block = block.title_top(top_title);
65+
}
66+
67+
if let Some(bottom_title) = self.content.bottom_title() {
68+
block = block.title_bottom(bottom_title);
69+
}
70+
71+
frame.render_widget(Clear, popup_area);
72+
frame.render_widget(&block, popup_area);
73+
self.content.draw(frame, block.inner(popup_area));
5474
}
5575

5676
// Returns true if the floating window is finished.

tui/src/floating_text.rs

+15-19
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use ratatui::{
1414
layout::Rect,
1515
style::{Style, Stylize},
1616
text::Line,
17-
widgets::{Block, Borders, Clear, List},
17+
widgets::{Block, List},
1818
Frame,
1919
};
2020

@@ -210,21 +210,20 @@ impl FloatingText {
210210
}
211211

212212
impl FloatContent for FloatingText {
213+
fn top_title(&self) -> Option<Line<'_>> {
214+
let title_text = format!(" {} ", self.mode_title);
215+
let title_line = Line::from(title_text)
216+
.centered()
217+
.style(Style::default().reversed());
218+
Some(title_line)
219+
}
220+
221+
fn bottom_title(&self) -> Option<Line<'_>> {
222+
None
223+
}
224+
213225
fn draw(&mut self, frame: &mut Frame, area: Rect) {
214-
// Define the Block with a border and background color
215-
let block = Block::default()
216-
.borders(Borders::ALL)
217-
.title(self.mode_title)
218-
.title_alignment(ratatui::layout::Alignment::Center)
219-
.title_style(Style::default().reversed())
220-
.style(Style::default());
221-
222-
// Draw the Block first
223-
frame.render_widget(block.clone(), area);
224-
225-
// Calculate the inner area to ensure text is not drawn over the border
226-
let inner_area = block.inner(area);
227-
let Rect { height, .. } = inner_area;
226+
let Rect { height, .. } = area;
228227
let lines = self
229228
.src
230229
.iter()
@@ -267,11 +266,8 @@ impl FloatContent for FloatingText {
267266
.block(Block::default())
268267
.highlight_style(Style::default().reversed());
269268

270-
// Clear the text underneath the floats rendered area
271-
frame.render_widget(Clear, inner_area);
272-
273269
// Render the list inside the bordered area
274-
frame.render_widget(list, inner_area);
270+
frame.render_widget(list, area);
275271
}
276272

277273
fn handle_key_event(&mut self, key: &KeyEvent) -> bool {

tui/src/running_command.rs

+50-70
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,39 @@
1+
use std::{
2+
cell::{Cell, RefCell},
3+
io::Write,
4+
sync::{Arc, Mutex},
5+
thread::JoinHandle,
6+
};
7+
18
use crate::{float::FloatContent, hint::Shortcut};
9+
210
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
3-
use linutil_core::Command;
11+
412
use oneshot::{channel, Receiver};
13+
514
use portable_pty::{
615
ChildKiller, CommandBuilder, ExitStatus, MasterPty, NativePtySystem, PtySize, PtySystem,
716
};
17+
818
use ratatui::{
9-
layout::{Rect, Size},
10-
style::{Color, Style, Stylize},
11-
text::{Line, Span},
12-
widgets::{Block, Borders},
19+
layout::Rect,
20+
style::{Style, Stylize},
21+
text::Line,
1322
Frame,
1423
};
15-
use std::{
16-
io::Write,
17-
sync::{Arc, Mutex},
18-
thread::JoinHandle,
19-
};
24+
2025
use tui_term::{
2126
vt100::{self, Screen},
2227
widget::PseudoTerminal,
2328
};
2429

30+
use linutil_core::Command;
31+
2532
pub struct RunningCommand {
2633
/// A buffer to save all the command output (accumulates, until the command exits)
2734
buffer: Arc<Mutex<Vec<u8>>>,
2835
/// A handle for the thread running the command
29-
command_thread: Option<JoinHandle<ExitStatus>>,
36+
command_thread: Cell<Option<JoinHandle<ExitStatus>>>,
3037
/// A handle to kill the running process; it's an option because it can only be used once
3138
child_killer: Option<Receiver<Box<dyn ChildKiller + Send + Sync>>>,
3239
/// A join handle for the thread that reads command output and sends it to the main thread
@@ -36,56 +43,31 @@ pub struct RunningCommand {
3643
/// Used for sending keys to the emulated terminal
3744
writer: Box<dyn Write + Send>,
3845
/// Only set after the process has ended
39-
status: Option<ExitStatus>,
46+
status: RefCell<Option<ExitStatus>>,
4047
scroll_offset: usize,
4148
}
4249

4350
impl FloatContent for RunningCommand {
44-
fn draw(&mut self, frame: &mut Frame, area: Rect) {
45-
// Calculate the inner size of the terminal area, considering borders
46-
let inner_size = Size {
47-
width: area.width - 2, // Adjust for border width
48-
height: area.height - 2,
49-
};
50-
51-
// Define the block for the terminal display
52-
let block = if !self.is_finished() {
53-
// Display a block indicating the command is running
54-
Block::default()
55-
.borders(Borders::ALL)
56-
.title_top(Line::from("Running the command....").centered())
57-
.title_style(Style::default().reversed())
58-
.title_bottom(Line::from("Press Ctrl-C to KILL the command"))
51+
fn top_title(&self) -> Option<Line<'_>> {
52+
let (content, content_style) = if !self.is_finished() {
53+
(" Running command... ", Style::default().reversed())
54+
} else if self.wait_command().success() {
55+
(" Success ", Style::default().green().reversed())
5956
} else {
60-
// Display a block with the command's exit status
61-
let mut title_line = if self.get_exit_status().success() {
62-
Line::from(
63-
Span::default()
64-
.content("SUCCESS!")
65-
.style(Style::default().fg(Color::Green).reversed()),
66-
)
67-
} else {
68-
Line::from(
69-
Span::default()
70-
.content("FAILED!")
71-
.style(Style::default().fg(Color::Red).reversed()),
72-
)
73-
};
57+
(" Failed ", Style::default().red().reversed())
58+
};
7459

75-
title_line.push_span(
76-
Span::default()
77-
.content(" press <ENTER> to close this window ")
78-
.style(Style::default()),
79-
);
60+
Some(Line::from(content).style(content_style))
61+
}
8062

81-
Block::default()
82-
.borders(Borders::ALL)
83-
.title_top(title_line.centered())
84-
};
63+
fn bottom_title(&self) -> Option<Line<'_>> {
64+
Some(Line::from("Press Ctrl-C to KILL the command"))
65+
}
8566

67+
fn draw(&mut self, frame: &mut Frame, area: Rect) {
8668
// Process the buffer and create the pseudo-terminal widget
87-
let screen = self.screen(inner_size);
88-
let pseudo_term = PseudoTerminal::new(&screen).block(block);
69+
let screen = self.screen(area);
70+
let pseudo_term = PseudoTerminal::new(&screen);
8971

9072
// Render the widget on the frame
9173
frame.render_widget(pseudo_term, area);
@@ -116,12 +98,7 @@ impl FloatContent for RunningCommand {
11698
}
11799

118100
fn is_finished(&self) -> bool {
119-
// Check if the command thread has finished
120-
if let Some(command_thread) = &self.command_thread {
121-
command_thread.is_finished()
122-
} else {
123-
true
124-
}
101+
self.status.borrow().is_some()
125102
}
126103

127104
fn get_shortcut_list(&self) -> (&str, Box<[Shortcut]>) {
@@ -229,17 +206,17 @@ impl RunningCommand {
229206
let writer = pair.master.take_writer().unwrap();
230207
Self {
231208
buffer: command_buffer,
232-
command_thread: Some(command_handle),
233-
child_killer: Some(rx),
209+
command_thread: Some(command_handle).into(),
210+
child_killer: rx.into(),
234211
_reader_thread: reader_handle,
235212
pty_master: pair.master,
236213
writer,
237-
status: None,
214+
status: None.into(),
238215
scroll_offset: 0,
239216
}
240217
}
241218

242-
fn screen(&mut self, size: Size) -> Screen {
219+
fn screen(&mut self, size: Rect) -> Screen {
243220
// Resize the emulated pty
244221
self.pty_master
245222
.resize(PtySize {
@@ -263,14 +240,16 @@ impl RunningCommand {
263240
}
264241

265242
/// This function will block if the command is not finished
266-
fn get_exit_status(&mut self) -> ExitStatus {
267-
if self.command_thread.is_some() {
268-
let handle = self.command_thread.take().unwrap();
269-
let exit_status = handle.join().unwrap();
270-
self.status = Some(exit_status.clone());
271-
exit_status
272-
} else {
273-
self.status.as_ref().unwrap().clone()
243+
fn wait_command(&self) -> ExitStatus {
244+
let status = { self.status.borrow().clone() };
245+
match status {
246+
Some(status) => status,
247+
None => {
248+
let handle = self.command_thread.take().unwrap();
249+
let exit_status = handle.join().unwrap();
250+
self.status.replace(Some(exit_status.clone()));
251+
exit_status
252+
}
274253
}
275254
}
276255

@@ -279,6 +258,7 @@ impl RunningCommand {
279258
if !self.is_finished() {
280259
let mut killer = self.child_killer.take().unwrap().recv().unwrap();
281260
killer.kill().unwrap();
261+
self.wait_command();
282262
}
283263
}
284264

0 commit comments

Comments
 (0)