chore(core): tui polish and tasks_list unit tests (#30972)

This commit is contained in:
James Henry 2025-05-02 02:42:32 +04:00 committed by GitHub
parent e013125136
commit 157a1f1168
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 1438 additions and 381 deletions

View File

@ -143,15 +143,15 @@ impl App {
let pinned_tasks = self.pinned_tasks.clone();
for (idx, task) in pinned_tasks.iter().enumerate() {
if idx < 2 {
if idx == 0 {
self.selection_manager
.lock()
.unwrap()
.select_task(task.clone());
self.dispatch_action(Action::SortTasks);
self.selection_manager
.lock()
.unwrap()
.select_task(task.clone());
if pinned_tasks.len() == 1 && idx == 0 {
self.display_and_focus_current_task_in_terminal_pane(true);
} else {
self.pane_tasks[idx] = Some(task.clone());
self.assign_current_task_to_pane(idx);
}
}
}
@ -296,14 +296,33 @@ impl App {
// Record that the user has interacted with the app
self.user_has_interacted = true;
// Handle Ctrl+C to quit
if key.code == KeyCode::Char('c') && key.modifiers == KeyModifiers::CONTROL {
// Handle Ctrl+C to quit, unless we're in interactive mode and the focus is on a terminal pane
if key.code == KeyCode::Char('c')
&& key.modifiers == KeyModifiers::CONTROL
&& !(matches!(self.focus, Focus::MultipleOutput(_))
&& self.is_interactive_mode())
{
self.is_forced_shutdown = true;
// Quit immediately
self.quit_at = Some(std::time::Instant::now());
return Ok(true);
}
if matches!(self.focus, Focus::MultipleOutput(_)) && self.is_interactive_mode() {
return match key.code {
KeyCode::Char('z') if key.modifiers == KeyModifiers::CONTROL => {
// Disable interactive mode when Ctrl+Z is pressed
self.set_interactive_mode(false);
Ok(false)
}
_ => {
// The TasksList will forward the key event to the focused terminal pane
self.handle_key_event(key).ok();
Ok(false)
}
};
}
// Only handle '?' key if we're not in interactive mode and the countdown popup is not open
if matches!(key.code, KeyCode::Char('?'))
&& !self.is_interactive_mode()
@ -372,10 +391,8 @@ impl App {
.iter_mut()
.find_map(|c| c.as_any_mut().downcast_mut::<TasksList>())
{
// Handle Q or Ctrl+C to trigger countdown
if key.code == KeyCode::Char('c') && key.modifiers == KeyModifiers::CONTROL
|| (!tasks_list.filter_mode && key.code == KeyCode::Char('q'))
{
// Handle Q to trigger countdown
if !tasks_list.filter_mode && key.code == KeyCode::Char('q') {
self.is_forced_shutdown = true;
self.begin_exit_countdown();
return Ok(false);
@ -473,7 +490,9 @@ impl App {
self.toggle_task_list();
}
KeyCode::Char('m') => {
self.cycle_layout_modes();
if let Some(area) = self.frame_area {
self.toggle_layout_mode(area);
}
}
_ => {
// Forward other keys for interactivity, scrolling (j/k) etc
@ -587,7 +606,12 @@ impl App {
let _ = self.debounce_pty_resize();
}
'b' => self.toggle_task_list(),
'm' => self.cycle_layout_modes(),
'm' => {
if let Some(area) = self.frame_area
{
self.toggle_layout_mode(area);
}
}
_ => {}
}
}
@ -870,12 +894,23 @@ impl App {
_ => false,
};
// Figure out if this pane is the next tab target
let is_next_tab_target = !is_focused
&& match self.focus {
// If the task list is focused, the next tab target is the first pane
Focus::TaskList => pane_idx == 0,
// If the first pane is focused, the next tab target is the second pane
Focus::MultipleOutput(0) => pane_idx == 1,
_ => false,
};
let mut state = TerminalPaneState::new(
task.name.clone(),
task.status,
task.continuous,
is_focused,
has_pty,
is_next_tab_target,
);
let terminal_pane = TerminalPane::new()
@ -1164,9 +1199,8 @@ impl App {
let _ = self.handle_pty_resize();
}
fn cycle_layout_modes(&mut self) {
// TODO: add visual feedback about layout modes
self.layout_manager.cycle_layout_mode();
fn toggle_layout_mode(&mut self, area: Rect) {
self.layout_manager.toggle_layout_mode(area);
self.recalculate_layout_areas();
// Ensure the pty instances get resized appropriately (no debounce as this is based on an imperative user action)
let _ = self.handle_pty_resize();
@ -1263,6 +1297,12 @@ impl App {
}
}
pub fn set_interactive_mode(&mut self, interactive: bool) {
if let Focus::MultipleOutput(pane_idx) = self.focus {
self.terminal_pane_data[pane_idx].set_interactive(interactive);
}
}
/// Ensures that the PTY instances get resized appropriately based on the latest layout areas.
fn debounce_pty_resize(&mut self) -> io::Result<()> {
// Get current time in milliseconds

View File

@ -134,7 +134,7 @@ impl HelpPopup {
("b", "Toggle task list visibility"),
(
"m",
"Cycle through layout modes: auto, vertical, horizontal",
"Toggle between vertical and horizontal layouts (auto by default)",
),
("1", "Pin task to be shown in output pane 1"),
("2", "Pin task to be shown in output pane 2"),

View File

@ -125,12 +125,23 @@ impl LayoutManager {
self.mode
}
/// Cycles the layout mode.
pub fn cycle_layout_mode(&mut self) {
/// Manually toggle the layout mode.
/// Initially we will be attempting to automatically pick the best layout using auto, but with this function we should
/// figure out our current orientation and toggle it to the opposite.
/// i.e. in the simple case, if currently horizontal, toggle to vertical and vice versa.
/// In the case where we are in auto mode, we need to figure out our current orientation and set the mode to the opposite.
pub fn toggle_layout_mode(&mut self, area: Rect) {
self.mode = match self.mode {
LayoutMode::Auto => LayoutMode::Vertical,
LayoutMode::Auto => {
// If we are in auto mode, we need to figure out our current orientation and set the mode to the opposite.
if self.is_vertical_layout_preferred(area.width, area.height, self.task_count) {
LayoutMode::Horizontal
} else {
LayoutMode::Vertical
}
}
LayoutMode::Vertical => LayoutMode::Horizontal,
LayoutMode::Horizontal => LayoutMode::Auto,
LayoutMode::Horizontal => LayoutMode::Vertical,
};
}

View File

@ -0,0 +1,19 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Running Test Tasks... Cache Duration"
" │ "
" "
" "
" "
" "
" "
" "
" "
" "
" Filter: app1 "
" -> 3 tasks filtered out. Press / to persist, <esc> to clear "
" "
" ← 1/1 → quit: q help: ? navigate: ↑ ↓ filter: / pin output: 1 or 2 show output: <enter> "

View File

@ -0,0 +1,19 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Running Test Tasks... Cache Duration"
" │ "
" │⠋ task1 ... ..."
" │⠋ task2 ... ..."
">│⠋ task3 ... ..."
" └ "
" "
" "
" "
" "
" "
" "
" "
" ← 1/1 → quit: q help: ? navigate: ↑ ↓ filter: / pin output: 1 or 2 show output: <enter> "

View File

@ -0,0 +1,19 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Completed Test Tasks Cache Duration"
" "
" ✖ task2 - ..."
"> ✔ task1 - ..."
" ✔ task3 - ..."
" "
" "
" "
" "
" "
" "
" "
" "
" ← 1/1 → quit: q help: ? View results at https://nx.app/runs/123"

View File

@ -0,0 +1,19 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Completed Test Tasks (60.0s) Cache Duration"
" "
" ✔ task1 - 2.0s"
" ✔ task2 - 10.0s"
"> ✔ task3 - 60.0s"
" "
" "
" "
" "
" "
" "
" "
" "
" ← 1/1 → quit: q help: ? navigate: ↑ ↓ filter: / pin output: 1 or 2 show output: <enter> "

View File

@ -0,0 +1,19 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Running Test Tasks... Cache Duration"
" │ "
" │⠋ continuous-task - Continuous"
" │· Waiting for task... "
" └ "
"> · task1 ... ..."
" · task2 ... ..."
" · task3 ... ..."
" "
" "
" "
" "
" "
" ← 1/1 → quit: q help: ? navigate: ↑ ↓ filter: / pin output: 1 or 2 show output: <enter> "

View File

@ -0,0 +1,19 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Running Test Tasks... Cache Duration"
" "
" "
"> · task1 ... ..."
" · task2 ... ..."
" · task3 ... ..."
" "
" "
" "
" "
" "
" "
" "
" ← 1/1 → quit: q help: ? navigate: ↑ ↓ filter: / pin output: 1 or 2 show output: <enter> "

View File

@ -0,0 +1,19 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Running Test Tasks... Duration"
" "
" "
"> · task1 ..."
" · task2 ..."
" · task3 ..."
" "
" "
" "
" "
" "
" "
" ← 1/1 → "
" quit: q help: ? "

View File

@ -0,0 +1,19 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Completed Test Tasks Cache Duration"
" "
" ✖ task2 - ..."
" ✔ task1 - ..."
"> ⏭ task3 - ..."
" "
" "
" "
" "
" "
" "
" "
" "
" ← 1/1 → quit: q help: ? navigate: ↑ ↓ filter: / pin output: 1 or 2 show output: <enter> "

View File

@ -0,0 +1,19 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Running Test Task Duration"
" "
" "
"> · task1 ..."
" · task2 ..."
" · task3 ..."
" "
" "
" "
" "
" "
" "
" ← 1/1 → "
" quit: q help: ? "

View File

@ -0,0 +1,19 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Running Test Tasks... Cache Duration"
" │ "
" │⠋ task1 ... ..."
" │· Waiting for task... "
" └ "
"> · task2 ... ..."
" · task3 ... ..."
" "
" "
" "
" "
" "
" "
" ← 1/1 → quit: q help: ? navigate: ↑ ↓ filter: / pin output: 1 or 2 show output: <enter> "

View File

@ -0,0 +1,19 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Running Many Tasks... Cache Duration"
" │ "
" │· Waiting for task... "
" │· Waiting for task... "
" └ "
"> · task1 ... ..."
" · task10 ... ..."
" · task11 ... ..."
" · task12 ... ..."
" · task2 ... ..."
" · task3 ... ..."
" · task4 ... ..."
" "
" ← 1/2 → quit: q help: ? navigate: ↑ ↓ filter: / pin output: 1 or 2 show output: <enter> "

View File

@ -0,0 +1,19 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Running Many Tasks... Cache Duration"
" "
"> · task5 ... ..."
" · task6 ... ..."
" · task7 ... ..."
" · task8 ... ..."
" · task9 ... ..."
" "
" "
" "
" "
" "
" "
" ← 2/2 → quit: q help: ? navigate: ↑ ↓ filter: / pin output: 1 or 2 show output: <enter> "

View File

@ -0,0 +1,19 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Running Test Tasks... Cache Duration"
" │ "
" "
" "
" "
" "
" "
" "
" "
" "
" Filter: app1 "
" -> 3 tasks filtered out. Press / to edit, <esc> to clear "
" "
" ← 1/1 → quit: q help: ? navigate: ↑ ↓ filter: / pin output: 1 or 2 show output: <enter> "

View File

@ -0,0 +1,19 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Running Test Tasks... Cache Duration"
" │ "
" │⠋ task1 [Pinned output 1] ... ..."
" │· Waiting for task... "
" └ "
"> · task2 ... ..."
" · task3 ... ..."
" "
" "
" "
" "
" "
" "
" ← 1/1 → quit: q help: ? navigate: ↑ ↓ filter: / pin output: 1 or 2 show output: <enter> "

View File

@ -0,0 +1,19 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Running Run One Mode... Cache Duration"
" │ "
" │· Waiting for task... "
" │· Waiting for task... "
" └ "
"> · task1 ... ..."
" "
" · task2 ... ..."
" "
" "
" "
" "
" "
" ← 1/1 → quit: q help: ? navigate: ↑ ↓ filter: / pin output: 1 or 2 show output: <enter> "

View File

@ -0,0 +1,14 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Running Test Tasks... Cache Duration"
" │ "
" │⠋ task1 ... ..."
" │· Waiting for task... "
" └ "
"> · task2 ... ..."
" · task3 ... ..."
" "
" ← 1/1 → quit: q help: ? "

View File

@ -0,0 +1,19 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Running Test Tasks... Cache Duration"
" │ "
" │⠋ task1 ... ..."
" │· Waiting for task... "
" └ "
"> · task2 ... ..."
" · task3 ... ..."
" "
" "
" "
" "
" "
" "
" ← 1/1 → quit: q help: ? navigate: ↑ ↓ filter: / pin output: 1 or 2 show output: <enter> "

View File

@ -0,0 +1,19 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Completed Test Tasks Cache Duration"
" "
" ✔ task1 Local ..."
" ✔ task2 Remote ..."
"> ✔ task3 Kept Existing ..."
" "
" "
" "
" "
" "
" "
" "
" "
" ← 1/1 → quit: q help: ? navigate: ↑ ↓ filter: / pin output: 1 or 2 show output: <enter> "

View File

@ -0,0 +1,19 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Running Test Tasks... Cache Duration"
" │ "
" │⠋ task1 ... ..."
" │· Waiting for task... "
" └ "
"> · task2 ... ..."
" · task3 ... ..."
" "
" "
" "
" "
" "
" "
" ← 1/1 → quit: q help: ? navigate: ↑ ↓ filter: / pin output: 1 or 2 show output: <enter> "

View File

@ -0,0 +1,19 @@
---
source: packages/nx/src/native/tui/components/tasks_list.rs
expression: terminal.backend()
---
" "
" NX Duration"
" │ "
" │⠋ ..."
" │· "
" └ "
"> · ..."
" · ..."
" "
" "
" "
" "
" "
" ← 1/1 → "
" quit: q help: ? "

File diff suppressed because it is too large Load Diff

View File

@ -145,6 +145,7 @@ pub struct TerminalPaneState {
pub scroll_offset: usize,
pub scrollbar_state: ScrollbarState,
pub has_pty: bool,
pub is_next_tab_target: bool,
}
impl TerminalPaneState {
@ -154,6 +155,7 @@ impl TerminalPaneState {
is_continuous: bool,
is_focused: bool,
has_pty: bool,
is_next_tab_target: bool,
) -> Self {
Self {
task_name,
@ -163,6 +165,7 @@ impl TerminalPaneState {
scroll_offset: 0,
scrollbar_state: ScrollbarState::default(),
has_pty,
is_next_tab_target,
}
}
}
@ -192,24 +195,15 @@ impl<'a> TerminalPane<'a> {
fn get_status_icon(&self, status: TaskStatus) -> Span {
match status {
TaskStatus::Success => Span::styled(
TaskStatus::Success
| TaskStatus::LocalCacheKeptExisting
| TaskStatus::LocalCache
| TaskStatus::RemoteCache => Span::styled(
"",
Style::default()
.fg(Color::Green)
.add_modifier(Modifier::BOLD),
),
TaskStatus::LocalCacheKeptExisting | TaskStatus::LocalCache => Span::styled(
"",
Style::default()
.fg(Color::Green)
.add_modifier(Modifier::BOLD),
),
TaskStatus::RemoteCache => Span::styled(
"",
Style::default()
.fg(Color::Green)
.add_modifier(Modifier::BOLD),
),
TaskStatus::Failure => Span::styled(
"",
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
@ -227,7 +221,7 @@ impl<'a> TerminalPane<'a> {
.add_modifier(Modifier::BOLD),
),
TaskStatus::Stopped => Span::styled(
" ⯀️ ",
" ",
Style::default()
.fg(Color::DarkGray)
.add_modifier(Modifier::BOLD),
@ -326,11 +320,24 @@ impl<'a> StatefulWidget for TerminalPane<'a> {
let status_icon = self.get_status_icon(state.task_status);
let block = Block::default()
.title(Line::from(vec![
status_icon.clone(),
Span::raw(format!("{} ", state.task_name))
.style(Style::default().fg(Color::White)),
]))
.title(Line::from(if state.is_focused {
vec![
status_icon.clone(),
Span::raw(format!("{} ", state.task_name))
.style(Style::default().fg(Color::White)),
]
} else {
vec![
status_icon.clone(),
Span::raw(format!("{} ", state.task_name))
.style(Style::default().fg(Color::White)),
if state.is_next_tab_target {
Span::raw("Press <tab> to focus ")
} else {
Span::raw("")
},
]
}))
.title_alignment(Alignment::Left)
.borders(Borders::ALL)
.border_type(BorderType::Plain)