feat(core): focus single tasks (#31159)

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
When running a single task it is automatically focused, but the tasks
list is visible and the task is not interactive

## Expected Behavior
When running a single task, the tui is minimal without a tasks list and
with interactivity by default

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
This commit is contained in:
Craigory Coppola 2025-05-09 18:44:06 -04:00 committed by GitHub
parent 5aa0c4050f
commit bf1eec5eca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 38 additions and 20 deletions

View File

@ -59,7 +59,6 @@ pub struct App {
terminal_pane_data: [TerminalPaneData; 2], terminal_pane_data: [TerminalPaneData; 2],
spacebar_mode: bool, spacebar_mode: bool,
pane_tasks: [Option<String>; 2], // Tasks assigned to panes 1 and 2 (0-indexed) pane_tasks: [Option<String>; 2], // Tasks assigned to panes 1 and 2 (0-indexed)
task_list_hidden: bool,
action_tx: Option<UnboundedSender<Action>>, action_tx: Option<UnboundedSender<Action>>,
resize_debounce_timer: Option<u128>, // Timer for debouncing resize events resize_debounce_timer: Option<u128>, // Timer for debouncing resize events
// task id -> pty instance // task id -> pty instance
@ -128,7 +127,6 @@ impl App {
terminal_pane_data: [main_terminal_pane_data, TerminalPaneData::new()], terminal_pane_data: [main_terminal_pane_data, TerminalPaneData::new()],
spacebar_mode: false, spacebar_mode: false,
pane_tasks: [None, None], pane_tasks: [None, None],
task_list_hidden: false,
action_tx: None, action_tx: None,
resize_debounce_timer: None, resize_debounce_timer: None,
pty_instances: HashMap::new(), pty_instances: HashMap::new(),
@ -155,7 +153,7 @@ impl App {
.select_task(task.clone()); .select_task(task.clone());
if pinned_tasks.len() == 1 && idx == 0 { if pinned_tasks.len() == 1 && idx == 0 {
self.display_and_focus_current_task_in_terminal_pane(true); self.display_and_focus_current_task_in_terminal_pane(self.tasks.len() != 1);
} else { } else {
self.assign_current_task_to_pane(idx); self.assign_current_task_to_pane(idx);
} }
@ -174,6 +172,9 @@ impl App {
pub fn update_task_status(&mut self, task_id: String, status: TaskStatus) { pub fn update_task_status(&mut self, task_id: String, status: TaskStatus) {
self.dispatch_action(Action::UpdateTaskStatus(task_id.clone(), status)); self.dispatch_action(Action::UpdateTaskStatus(task_id.clone(), status));
if status == TaskStatus::InProgress && self.tasks.len() == 1 {
self.terminal_pane_data[0].set_interactive(true);
}
} }
pub fn print_task_terminal_output(&mut self, task_id: String, output: String) { pub fn print_task_terminal_output(&mut self, task_id: String, output: String) {
@ -299,8 +300,12 @@ impl App {
tui::Event::Key(key) => { tui::Event::Key(key) => {
trace!("Handling Key Event: {:?}", key); trace!("Handling Key Event: {:?}", key);
// If the app is in interactive mode, interactions are with
// the running task, not the app itself
if !self.is_interactive_mode() {
// Record that the user has interacted with the app // Record that the user has interacted with the app
self.user_has_interacted = true; self.user_has_interacted = true;
}
// Handle Ctrl+C to quit, unless we're in interactive mode and the focus is on a terminal pane // 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') if key.code == KeyCode::Char('c')
@ -510,7 +515,7 @@ impl App {
self.focus_previous(); self.focus_previous();
} }
KeyCode::Esc => { KeyCode::Esc => {
if !self.task_list_hidden { if !self.is_task_list_hidden() {
self.update_focus(Focus::TaskList); self.update_focus(Focus::TaskList);
} }
} }
@ -696,8 +701,12 @@ impl App {
} }
} }
tui::Event::Mouse(mouse) => { tui::Event::Mouse(mouse) => {
// If the app is in interactive mode, interactions are with
// the running task, not the app itself
if !self.is_interactive_mode() {
// Record that the user has interacted with the app // Record that the user has interacted with the app
self.user_has_interacted = true; self.user_has_interacted = true;
}
match mouse.kind { match mouse.kind {
MouseEventKind::ScrollUp => { MouseEventKind::ScrollUp => {
@ -1063,7 +1072,7 @@ impl App {
/// Toggles the visibility of the output pane for the currently selected task. /// Toggles the visibility of the output pane for the currently selected task.
/// In spacebar mode, the output follows the task selection. /// In spacebar mode, the output follows the task selection.
fn toggle_output_visibility(&mut self) { fn toggle_output_visibility(&mut self) {
self.task_list_hidden = false; // TODO: Not sure why we do this, this action only happens when the task list is visible
self.layout_manager self.layout_manager
.set_task_list_visibility(TaskListVisibility::Visible); .set_task_list_visibility(TaskListVisibility::Visible);
@ -1146,7 +1155,7 @@ impl App {
Some(pane) => Focus::MultipleOutput(pane), Some(pane) => Focus::MultipleOutput(pane),
None => { None => {
// If the task list is hidden, try and go back to the previous pane if there is one, otherwise do nothing // If the task list is hidden, try and go back to the previous pane if there is one, otherwise do nothing
if self.task_list_hidden { if self.is_task_list_hidden() {
if current_pane > 0 { if current_pane > 0 {
Focus::MultipleOutput(current_pane - 1) Focus::MultipleOutput(current_pane - 1)
} else { } else {
@ -1188,7 +1197,7 @@ impl App {
.find(|&idx| self.pane_tasks[idx].is_some()) .find(|&idx| self.pane_tasks[idx].is_some())
{ {
Focus::MultipleOutput(prev_pane) Focus::MultipleOutput(prev_pane)
} else if !self.task_list_hidden { } else if !self.is_task_list_hidden() {
// Go to task list if it's visible // Go to task list if it's visible
Focus::TaskList Focus::TaskList
} else { } else {
@ -1204,7 +1213,7 @@ impl App {
} }
} else { } else {
// We're at leftmost pane (index 0) // We're at leftmost pane (index 0)
if !self.task_list_hidden { if !self.is_task_list_hidden() {
// Go to task list if it's visible // Go to task list if it's visible
Focus::TaskList Focus::TaskList
} else if num_panes > 1 { } else if num_panes > 1 {
@ -1235,13 +1244,7 @@ impl App {
if !self.has_visible_panes() { if !self.has_visible_panes() {
return; return;
} }
self.task_list_hidden = !self.task_list_hidden; self.layout_manager.toggle_task_list_visibility();
self.layout_manager
.set_task_list_visibility(if self.task_list_hidden {
TaskListVisibility::Hidden
} else {
TaskListVisibility::Visible
});
self.recalculate_layout_areas(); self.recalculate_layout_areas();
// Ensure the pty instances get resized appropriately (no debounce as this is based on an imperative user action) // Ensure the pty instances get resized appropriately (no debounce as this is based on an imperative user action)
let _ = self.handle_pty_resize(); let _ = self.handle_pty_resize();
@ -1418,6 +1421,10 @@ impl App {
Ok(()) Ok(())
} }
fn is_task_list_hidden(&self) -> bool {
self.layout_manager.get_task_list_visibility() == TaskListVisibility::Hidden
}
fn create_and_register_pty_instance( fn create_and_register_pty_instance(
&mut self, &mut self,
task_id: &str, task_id: &str,

View File

@ -108,7 +108,11 @@ impl LayoutManager {
min_horizontal_width: 120, // Minimum width for horizontal layout to be viable min_horizontal_width: 120, // Minimum width for horizontal layout to be viable
min_vertical_height: 30, // Minimum height for vertical layout to be viable min_vertical_height: 30, // Minimum height for vertical layout to be viable
pane_arrangement: PaneArrangement::None, pane_arrangement: PaneArrangement::None,
task_list_visibility: TaskListVisibility::Visible, task_list_visibility: if task_count > 1 {
TaskListVisibility::Visible
} else {
TaskListVisibility::Hidden
},
task_count, task_count,
horizontal_padding: 2, // Default horizontal padding of 2 characters horizontal_padding: 2, // Default horizontal padding of 2 characters
vertical_padding: 1, // Default vertical padding of 1 character vertical_padding: 1, // Default vertical padding of 1 character
@ -165,6 +169,13 @@ impl LayoutManager {
self.task_list_visibility self.task_list_visibility
} }
pub fn toggle_task_list_visibility(&mut self) {
self.task_list_visibility = match self.task_list_visibility {
TaskListVisibility::Visible => TaskListVisibility::Hidden,
TaskListVisibility::Hidden => TaskListVisibility::Visible,
};
}
/// Sets the task count. /// Sets the task count.
pub fn set_task_count(&mut self, count: usize) { pub fn set_task_count(&mut self, count: usize) {
self.task_count = count; self.task_count = count;