Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@
## 2024-11-21 - Centering Empty States for TUI Lists
**Learning:** Rendering an empty list message (like "no matches") in the top-left corner of the list area can look unbalanced, especially when rendering lists in large UI components or side panes.
**Action:** Always center the text for empty state indicators in lists visually (using `.alignment(ratatui::layout::Alignment::Center)`) and vertically (using `center_text(msg, area.height)`) when using `Paragraph` to render empty states in a list widget.

## 2024-11-23 - Search Input Placeholders
**Learning:** In the TUI, empty text inputs for `branch`, `finder`, and `grep` simply showed `> ` without indication of what the user should type. Blank spaces do not provide enough context for the user to understand what input is expected, which hurts discoverability and usability.
**Action:** When implementing search or filter input fields in a TUI, always include descriptive placeholder text (e.g., using `Color::DarkGray`) when the query is empty. This acts as an inline hint. Make sure to account for the width of the placeholder text when calculating alignment paddings to maintain balanced layouts.
25 changes: 19 additions & 6 deletions src/component/branch/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,26 @@ fn draw_input(frame: &mut Frame, state: &BranchState, area: Rect) {
let count = format!("{}/{}", state.matches.len(), state.total());
let used = query_text.chars().count() + 1 + count.chars().count();
let pad = (area.width as usize).saturating_sub(used);
let line = Line::from(vec![
Span::raw(query_text),

let mut spans = vec![
Span::raw(query_text.clone()),
Span::styled(" ", Style::default().add_modifier(Modifier::REVERSED)),
Span::raw(" ".repeat(pad)),
Span::styled(count, Style::default().fg(Color::DarkGray)),
]);
frame.render_widget(Paragraph::new(line), area);
];

let mut pad = pad;
if state.query.is_empty() {
let placeholder = crate::lang::t(crate::lang::Msg::HelpFuzzyFilterBranchNames);
spans.push(Span::styled(
placeholder.to_string(),
Style::default().fg(Color::DarkGray),
));
pad = pad.saturating_sub(placeholder.chars().count());
}

spans.push(Span::raw(" ".repeat(pad)));
spans.push(Span::styled(count, Style::default().fg(Color::DarkGray)));

frame.render_widget(Paragraph::new(Line::from(spans)), area);
}

fn draw_list(frame: &mut Frame, state: &mut BranchState, area: Rect) {
Expand Down
26 changes: 19 additions & 7 deletions src/component/finder/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,26 @@ fn draw_finder_input(frame: &mut Frame, finder: &Finder, area: Rect) {
// 狭ければ埋めず単純に連結するだけにする (折り返しは Paragraph に任せる)
let used = query_text.chars().count() + 1 + count.chars().count();
let pad = (area.width as usize).saturating_sub(used);
let line = Line::from(vec![
Span::raw(query_text),
// 常に末尾に立つ簡易カーソル (他の入力行と同じ表現)

let mut spans = vec![
Span::raw(query_text.clone()),
Span::styled(" ", Style::default().add_modifier(Modifier::REVERSED)),
Span::raw(" ".repeat(pad)),
Span::styled(count, Style::default().fg(Color::DarkGray)),
]);
frame.render_widget(Paragraph::new(line), area);
];

let mut pad = pad;
if finder.query.is_empty() {
let placeholder = crate::lang::t(crate::lang::Msg::HelpFilterByQuery);
spans.push(Span::styled(
placeholder.to_string(),
Style::default().fg(Color::DarkGray),
));
pad = pad.saturating_sub(placeholder.chars().count());
}

spans.push(Span::raw(" ".repeat(pad)));
spans.push(Span::styled(count, Style::default().fg(Color::DarkGray)));

frame.render_widget(Paragraph::new(Line::from(spans)), area);
}

fn draw_finder_list(frame: &mut Frame, finder: &mut Finder, area: Rect) {
Expand Down
1 change: 1 addition & 0 deletions src/component/grep/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ impl Cache {
/// 予約を戻す。古い世代の走査が差し替え後に戻すと (差し替えで正確な値に置き直した後なので)
/// 引きすぎになるため、0 で止める。ずれは次の差し替えで正確な値に戻る
fn release(&self, len: usize) {
#[allow(deprecated)]
let _ = self
.bytes
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |b| {
Expand Down
15 changes: 12 additions & 3 deletions src/component/grep/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,20 @@ fn title(grep: &GrepState) -> String {
}

fn draw_input(frame: &mut Frame, grep: &GrepState, area: Rect) {
let line = Line::from(vec![
let mut spans = vec![
Span::raw(format!("> {}", grep.query)),
// 常に末尾に立つ簡易カーソル (Finder と同じ表現)
Span::styled(" ", Style::default().add_modifier(Modifier::REVERSED)),
]);
];

if grep.query.is_empty() {
let placeholder = crate::lang::t(crate::lang::Msg::HelpGrepQuery);
spans.push(Span::styled(
placeholder.to_string(),
Style::default().fg(Color::DarkGray),
));
}

let line = Line::from(spans);
frame.render_widget(Paragraph::new(line), area);
}

Expand Down
Loading