Skip to content

Commit 1de3775

Browse files
authored
feat: Move icon to left side in TUI (#11097)
### Description Title! ### Testing Instructions 👀 <img width="556" height="540" alt="CleanShot 2025-11-10 at 15 42 22@2x" src="https://github.com/user-attachments/assets/1ec108e0-55e1-404f-8e14-873ea73d6990" />
1 parent bd53f07 commit 1de3775

File tree

1 file changed

+50
-62
lines changed

1 file changed

+50
-62
lines changed

crates/turborepo-ui/src/tui/table.rs

Lines changed: 50 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use ratatui::{
22
layout::{Constraint, Rect},
33
style::{Color, Modifier, Style, Stylize},
4-
text::Text,
4+
text::{Line, Span, Text},
55
widgets::{Block, Borders, Cell, Row, StatefulWidget, Table, TableState},
66
};
77

@@ -44,8 +44,8 @@ impl<'b> TaskTable<'b> {
4444
.max()
4545
.unwrap_or_default()
4646
.clamp(min_width, 40) as u16;
47-
// Add space for column divider and status emoji
48-
task_name_width + 1
47+
// Add space for status emoji and space
48+
task_name_width + 2
4949
}
5050

5151
/// Update the current time of the table
@@ -72,69 +72,67 @@ impl<'b> TaskTable<'b> {
7272
}
7373
}
7474

75-
/// Get styled status icon for a finished task
76-
fn status_icon(&self, task_name: &str, result: TaskResult) -> Cell<'static> {
77-
let should_dim = self.should_dim_task(task_name);
78-
match result {
79-
// matches Next.js (and many other CLI tools) https://github.com/vercel/next.js/blob/1a04d94aaec943d3cce93487fea3b8c8f8898f31/packages/next/src/build/output/log.ts
80-
TaskResult::Success => {
81-
let style = if should_dim {
82-
Style::default().green().add_modifier(Modifier::DIM)
83-
} else {
84-
Style::default().green().bold()
85-
};
86-
Cell::new(Text::styled("✓", style))
87-
}
88-
TaskResult::CacheHit => {
89-
let style = if should_dim {
90-
Style::default().magenta().add_modifier(Modifier::DIM)
91-
} else {
92-
Style::default().magenta()
93-
};
94-
Cell::new(Text::styled("⊙", style))
95-
}
96-
TaskResult::Failure => {
97-
let style = if should_dim {
98-
Style::default().red().add_modifier(Modifier::DIM)
99-
} else {
100-
Style::default().red().bold()
101-
};
102-
Cell::new(Text::styled("⨯", style))
103-
}
104-
}
105-
}
106-
10775
fn finished_rows(&self) -> impl Iterator<Item = Row<'_>> + '_ {
10876
self.tasks_by_type.finished.iter().map(move |task| {
10977
let base_style = self.task_style(task.name());
110-
let name = if matches!(task.result(), TaskResult::CacheHit) {
111-
Cell::new(Text::styled(task.name(), base_style.italic()))
78+
let icon = match task.result() {
79+
// matches Next.js (and many other CLI tools) https://github.com/vercel/next.js/blob/1a04d94aaec943d3cce93487fea3b8c8f8898f31/packages/next/src/build/output/log.ts
80+
TaskResult::Success => {
81+
let style = if self.should_dim_task(task.name()) {
82+
Style::default().green().add_modifier(Modifier::DIM)
83+
} else {
84+
Style::default().green().bold()
85+
};
86+
Span::styled("✓ ", style)
87+
}
88+
TaskResult::CacheHit => {
89+
let style = if self.should_dim_task(task.name()) {
90+
Style::default().magenta().add_modifier(Modifier::DIM)
91+
} else {
92+
Style::default().magenta()
93+
};
94+
Span::styled("⊙ ", style)
95+
}
96+
TaskResult::Failure => {
97+
let style = if self.should_dim_task(task.name()) {
98+
Style::default().red().add_modifier(Modifier::DIM)
99+
} else {
100+
Style::default().red().bold()
101+
};
102+
Span::styled("⨯ ", style)
103+
}
104+
};
105+
106+
let name_style = if matches!(task.result(), TaskResult::CacheHit) {
107+
base_style.italic()
112108
} else {
113-
Cell::new(Text::styled(task.name(), base_style))
109+
base_style
114110
};
115111

116-
Row::new(vec![name, self.status_icon(task.name(), task.result())])
112+
let mut content = vec![icon];
113+
content.push(Span::styled(task.name(), name_style));
114+
115+
Row::new(vec![Cell::new(Line::from(content))])
117116
})
118117
}
119118

120119
fn running_rows(&self) -> impl Iterator<Item = Row<'_>> + '_ {
121120
let spinner = self.spinner.current();
122121
self.tasks_by_type.running.iter().map(move |task| {
123122
let style = self.task_style(task.name());
124-
Row::new(vec![
125-
Cell::new(Text::styled(task.name(), style)),
126-
Cell::new(Text::styled(spinner, style)),
127-
])
123+
let content = vec![
124+
Span::styled(format!("{} ", spinner), style),
125+
Span::styled(task.name(), style),
126+
];
127+
Row::new(vec![Cell::new(Line::from(content))])
128128
})
129129
}
130130

131131
fn planned_rows(&self) -> impl Iterator<Item = Row<'_>> + '_ {
132132
self.tasks_by_type.planned.iter().map(move |task| {
133133
let style = self.task_style(task.name());
134-
Row::new(vec![
135-
Cell::new(Text::styled(task.name(), style)),
136-
Cell::new(" "),
137-
])
134+
let content = vec![Span::raw(" "), Span::styled(task.name(), style)];
135+
Row::new(vec![Cell::new(Line::from(content))])
138136
})
139137
}
140138
}
@@ -147,17 +145,13 @@ impl<'a> StatefulWidget for &'a TaskTable<'a> {
147145
self.running_rows()
148146
.chain(self.planned_rows())
149147
.chain(self.finished_rows()),
150-
[
151-
Constraint::Min(15),
152-
// Status takes one cell to render
153-
Constraint::Length(1),
154-
],
148+
[Constraint::Min(15)],
155149
)
156150
.highlight_style(Style::default().fg(Color::Yellow))
157151
.column_spacing(0)
158152
.block(Block::new().borders(Borders::RIGHT))
159153
.header(
160-
vec![Text::styled(
154+
Row::new(vec![Cell::from(Text::styled(
161155
match self.section {
162156
LayoutSections::Search { results, .. }
163157
| LayoutSections::SearchLocked { results, .. } => {
@@ -166,20 +160,14 @@ impl<'a> StatefulWidget for &'a TaskTable<'a> {
166160
_ => TASK_HEADER.to_string(),
167161
},
168162
Style::default().add_modifier(Modifier::DIM),
169-
)]
170-
.into_iter()
171-
.map(Cell::from)
172-
.collect::<Row>()
163+
))])
173164
.height(1),
174165
)
175166
.footer(
176-
vec![Text::styled(
167+
Row::new(vec![Cell::from(Text::styled(
177168
format!("{TASK_NAVIGATE_INSTRUCTIONS}\n{MORE_BINDS_INSTRUCTIONS}"),
178169
Style::default().add_modifier(Modifier::DIM),
179-
)]
180-
.into_iter()
181-
.map(Cell::from)
182-
.collect::<Row>()
170+
))])
183171
.height(2),
184172
);
185173
StatefulWidget::render(table, area, buf, state);

0 commit comments

Comments
 (0)