feat(tui): improve Tasks sidebar inspectability (#1975)
- Show fuller turn ID prefix (16 chars) for disambiguation in task_read/task_cancel - Replace ambiguous 'X active (Y running)' with clear per-status breakdown - Add y/Y yank affordances for copying turn ID and full status from Tasks panel - Add yank hint text in Tasks panel footer
This commit is contained in:
@@ -574,9 +574,13 @@ fn task_panel_lines(app: &App, content_width: usize, max_rows: usize) -> Vec<Lin
|
||||
.as_deref()
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
// Show enough of the turn id prefix to identify it for
|
||||
// task_read / task_cancel. A UUID needs ~13 chars before the
|
||||
// first hyphen; 16 chars gives a safe prefix for disambiguation.
|
||||
let turn_prefix = truncate_line_to_width(turn_id, 16);
|
||||
lines.push(Line::from(Span::styled(
|
||||
truncate_line_to_width(
|
||||
&format!("turn {} ({status})", truncate_line_to_width(turn_id, 12)),
|
||||
&format!("turn {turn_prefix} ({status})",),
|
||||
content_width.max(1),
|
||||
),
|
||||
Style::default().fg(palette::DEEPSEEK_SKY),
|
||||
@@ -595,24 +599,18 @@ fn task_panel_lines(app: &App, content_width: usize, max_rows: usize) -> Vec<Lin
|
||||
.iter()
|
||||
.filter(|task| task.status == "running")
|
||||
.count();
|
||||
lines.push(Line::from(vec![
|
||||
Span::styled(
|
||||
if running == background_rows.len() {
|
||||
format!("Background jobs: {running} running")
|
||||
} else {
|
||||
format!("Background jobs: {} active", background_rows.len())
|
||||
},
|
||||
Style::default().fg(palette::DEEPSEEK_SKY).bold(),
|
||||
),
|
||||
Span::styled(
|
||||
if running == background_rows.len() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" ({running} running)")
|
||||
},
|
||||
Style::default().fg(palette::TEXT_MUTED),
|
||||
),
|
||||
]));
|
||||
let done = background_rows.len().saturating_sub(running);
|
||||
let label = if running == 0 {
|
||||
format!("Background jobs: {done} completed")
|
||||
} else if done == 0 {
|
||||
format!("Background jobs: {running} running")
|
||||
} else {
|
||||
format!("Background jobs: {running} running, {done} completed")
|
||||
};
|
||||
lines.push(Line::from(Span::styled(
|
||||
label,
|
||||
Style::default().fg(palette::DEEPSEEK_SKY).bold(),
|
||||
)));
|
||||
|
||||
let max_items = max_rows.saturating_sub(lines.len());
|
||||
for task in background_rows.iter().take(max_items) {
|
||||
@@ -664,6 +662,19 @@ fn task_panel_lines(app: &App, content_width: usize, max_rows: usize) -> Vec<Lin
|
||||
}
|
||||
}
|
||||
|
||||
// Yank hint: surface the keyboard path for copying the focused task/turn ID.
|
||||
if lines.len() + 1 < max_rows
|
||||
&& app.runtime_turn_id.is_some()
|
||||
&& app.sidebar_focus == SidebarFocus::Tasks
|
||||
{
|
||||
lines.push(Line::from(Span::styled(
|
||||
"y → copy turn id · Y → copy full status",
|
||||
Style::default()
|
||||
.fg(palette::TEXT_DIM)
|
||||
.add_modifier(ratatui::style::Modifier::ITALIC),
|
||||
)));
|
||||
}
|
||||
|
||||
if lines.is_empty()
|
||||
|| (lines.len() == 1
|
||||
&& app.runtime_turn_id.is_some()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! TUI event loop and rendering logic for `DeepSeek` CLI.
|
||||
|
||||
use std::fmt::Write as _;
|
||||
use std::io::{self, Stdout, Write};
|
||||
use std::path::PathBuf;
|
||||
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
|
||||
@@ -2505,6 +2506,41 @@ async fn run_event_loop(
|
||||
continue;
|
||||
}
|
||||
|
||||
// y / Y in the Tasks sidebar: yank the current turn id (y)
|
||||
// or copy full task detail (Y) to the system clipboard.
|
||||
if app.view_stack.is_empty()
|
||||
&& app.sidebar_focus == SidebarFocus::Tasks
|
||||
&& !app.runtime_turn_id.as_deref().unwrap_or("").is_empty()
|
||||
{
|
||||
if key.code == KeyCode::Char('y')
|
||||
&& key.modifiers == KeyModifiers::NONE
|
||||
{
|
||||
if let Some(turn_id) = app.runtime_turn_id.as_ref()
|
||||
&& app.clipboard.write_text(turn_id).is_ok()
|
||||
{
|
||||
app.status_message =
|
||||
Some(format!("Copied turn id {turn_id}"));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if key.code == KeyCode::Char('Y')
|
||||
&& key.modifiers == KeyModifiers::NONE
|
||||
{
|
||||
let mut detail = String::new();
|
||||
if let Some(turn_id) = app.runtime_turn_id.as_ref() {
|
||||
let _ = write!(detail, "turn {turn_id}");
|
||||
}
|
||||
if let Some(status) = app.runtime_turn_status.as_deref() {
|
||||
let _ = write!(detail, " status={status}");
|
||||
}
|
||||
if !detail.is_empty() && app.clipboard.write_text(&detail).is_ok() {
|
||||
app.status_message =
|
||||
Some(format!("Copied {detail}"));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Shifted shortcuts toggle the file-tree pane. Keep plain Ctrl+E
|
||||
// reserved for the composer end-of-line binding used by shells.
|
||||
if key_shortcuts::is_file_tree_toggle_shortcut(&key) {
|
||||
|
||||
Reference in New Issue
Block a user