fix: TUIウィジェットの表示を修正

- diff表示の修正
- historyリストの修正
- markdown表示の修正

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Shunsuke Hayashi 2026-01-21 05:08:11 +09:00
parent aa2651465f
commit 6d5b55ad74
3 changed files with 169 additions and 18 deletions

View file

@ -1,14 +1,70 @@
//! Diff viewer widget placeholder.
//! Diff viewer widget for displaying file changes.
//!
//! This will eventually wrap `diff_render.rs` for reuse across overlays.
//! Wraps `diff_render.rs` for reuse across overlays with scrolling support.
use ratatui::layout::Rect;
use ratatui::Frame;
use ratatui::{
layout::Rect,
widgets::{Block, Borders, Paragraph, Wrap},
Frame,
};
use crate::diff_render::FileDiff;
use crate::diff_render::{DiffRender, FileDiff};
/// Render a diff within the given area.
pub fn render_diff_widget(frame: &mut Frame, area: Rect, diff: &FileDiff) {
let _ = (frame, area, diff);
// TODO: integrate syntax highlighting and line numbers.
/// Properties for rendering a diff widget
pub struct DiffWidgetProps {
/// The file diff to display
pub diff: FileDiff,
/// Current scroll offset
pub scroll: u16,
/// Optional title for the block
pub title: Option<String>,
}
/// Render a diff within the given area with scrolling support.
pub fn render_diff_widget(frame: &mut Frame, area: Rect, diff: &FileDiff) {
render_diff_widget_with_scroll(frame, area, diff, 0, None);
}
/// Render a diff with scroll offset and optional title.
pub fn render_diff_widget_with_scroll(
frame: &mut Frame,
area: Rect,
diff: &FileDiff,
scroll: u16,
title: Option<&str>,
) {
// Create a DiffRender with the single file
let mut renderer = DiffRender::new();
renderer.files.push(diff.clone());
// Get rendered lines
let lines = renderer.render();
// Create block with optional title
let block = if let Some(t) = title {
Block::default()
.borders(Borders::ALL)
.title(t.to_string())
} else {
Block::default()
.borders(Borders::ALL)
.title(format!("{}{}", diff.old_path, diff.new_path))
};
// Create paragraph with scroll
let paragraph = Paragraph::new(lines)
.block(block)
.wrap(Wrap { trim: false })
.scroll((scroll, 0));
frame.render_widget(paragraph, area);
}
/// Calculate the total number of lines in a diff for scroll bounds
pub fn diff_line_count(diff: &FileDiff) -> usize {
let mut count = 1; // File header
for hunk in &diff.hunks {
count += hunk.lines.len();
}
count
}

View file

@ -3,12 +3,15 @@
//! Intended to wrap `HistoryCell` rendering with consistent padding and theming.
use ratatui::{
layout::Rect,
widgets::{Block, Borders},
layout::{Alignment, Rect},
style::Style,
text::Line,
widgets::{Block, Borders, List, ListItem, Paragraph},
Frame,
};
use crate::history_cell::HistoryCell;
use crate::ui::colors;
/// Properties required to render the history list.
pub struct HistoryListProps<'a> {
@ -21,10 +24,53 @@ pub struct HistoryList;
impl HistoryList {
pub fn render(frame: &mut Frame, area: Rect, props: HistoryListProps<'_>) {
let block = Block::default().borders(Borders::ALL);
let block = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(colors::BORDER))
.title("History");
let inner = block.inner(area);
frame.render_widget(block, area);
let _ = props;
// TODO: integrate with virtualized list + markdown rendering.
if inner.height == 0 || inner.width == 0 {
return;
}
if props.items.is_empty() {
let empty = Paragraph::new("No history yet")
.style(Style::default().fg(colors::COMMENT))
.alignment(Alignment::Center);
frame.render_widget(empty, inner);
return;
}
let scroll = props.scroll as usize;
let max_lines = inner.height as usize;
let mut visible_lines: Vec<Line> = Vec::with_capacity(max_lines);
let mut line_index = 0usize;
'outer: for (idx, cell) in props.items.iter().enumerate() {
let mut rendered = cell.render(inner.width);
if idx + 1 < props.items.len() {
rendered.push(Line::from(""));
}
for line in rendered {
if line_index >= scroll {
visible_lines.push(line);
if visible_lines.len() >= max_lines {
break 'outer;
}
}
line_index += 1;
}
}
let list_items: Vec<ListItem> = visible_lines
.into_iter()
.map(ListItem::new)
.collect();
let list = List::new(list_items).style(Style::default().fg(colors::FG));
frame.render_widget(list, inner);
}
}

View file

@ -3,13 +3,62 @@
//! This file is the landing zone for `markdown_stream` integration to keep
//! rendering logic out of `history_cell.rs`.
use ratatui::layout::Rect;
use ratatui::Frame;
use ratatui::{
layout::Rect,
style::{Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, Paragraph, Wrap},
Frame,
};
use crate::markdown_render::MarkdownRenderer;
use crate::markdown_stream::MarkdownStream;
use crate::ui::colors;
/// Render a markdown stream into the provided frame area.
pub fn render_stream(frame: &mut Frame, area: Rect, stream: &MarkdownStream) {
let _ = (frame, area, stream);
// TODO: reuse MarkdownRenderer and add code block scroll support.
let block = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(colors::BORDER))
.title("Markdown");
let inner_height = area.height.saturating_sub(2) as usize;
let mut lines = if stream.is_empty() {
vec![Line::from(Span::styled(
"Waiting for response...",
Style::default().fg(colors::COMMENT),
))]
} else {
let renderer = MarkdownRenderer::default();
renderer.render(stream.content())
};
if stream.is_streaming() {
if let Some(last) = lines.last_mut() {
last.spans.push(Span::styled(
"",
Style::default()
.fg(colors::FG)
.add_modifier(Modifier::SLOW_BLINK),
));
} else {
lines.push(Line::from(Span::styled(
"",
Style::default()
.fg(colors::FG)
.add_modifier(Modifier::SLOW_BLINK),
)));
}
}
let max_offset = lines.len().saturating_sub(inner_height.max(1));
let offset = stream.scroll_offset().min(max_offset);
let paragraph = Paragraph::new(lines)
.block(block)
.wrap(Wrap { trim: false })
.scroll((offset as u16, 0));
frame.render_widget(paragraph, area);
}