Skip to content

Commit 87ec13c

Browse files
committed
refactor(tui): reorder chat.rs for top-down reading
Move public API before trait impl, private helpers after their callers. Group: constructor + API → Component impl → scroll helpers → rendering.
1 parent 36e0c61 commit 87ec13c

1 file changed

Lines changed: 73 additions & 73 deletions

File tree

  • crates/oxide-code/src/tui/components

crates/oxide-code/src/tui/components/chat.rs

Lines changed: 73 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -100,73 +100,12 @@ impl ChatView {
100100
});
101101
}
102102

103-
fn scroll_to_bottom(&mut self) {
104-
self.scroll_offset = self
105-
.content_height
106-
.get()
107-
.saturating_sub(self.viewport_height);
108-
}
109-
110-
fn scroll_up(&mut self, lines: u16) {
111-
self.scroll_offset = self.scroll_offset.saturating_sub(lines);
112-
self.auto_scroll = false;
113-
}
114-
115-
fn scroll_down(&mut self, lines: u16) {
116-
let max = self
117-
.content_height
118-
.get()
119-
.saturating_sub(self.viewport_height);
120-
self.scroll_offset = self.scroll_offset.saturating_add(lines).min(max);
121-
if self.scroll_offset >= max {
122-
self.auto_scroll = true;
123-
}
124-
}
125-
126-
/// Build the full text content for rendering.
127-
fn build_text(&self) -> Text<'_> {
128-
let mut lines: Vec<Line<'_>> = Vec::new();
129-
130-
for msg in &self.messages {
131-
self.push_message_lines(&mut lines, msg.role, &msg.content);
132-
}
133-
134-
// Streaming buffer (not yet committed).
135-
if !self.streaming_buffer.is_empty() {
136-
self.push_message_lines(&mut lines, ChatRole::Assistant, &self.streaming_buffer);
137-
}
138-
139-
Text::from(lines)
140-
}
141-
142-
/// Append a role label and content lines for a single message.
143-
fn push_message_lines<'a>(
144-
&'a self,
145-
lines: &mut Vec<Line<'a>>,
146-
role: ChatRole,
147-
content: &'a str,
148-
) {
149-
// Single blank line between messages.
150-
if !lines.is_empty() {
151-
lines.push(Line::raw(""));
152-
}
153-
154-
// Role label.
155-
let (label, style) = match role {
156-
ChatRole::User => ("❯ You", self.theme.accent()),
157-
ChatRole::Assistant => ("⟡ Assistant", self.theme.secondary()),
158-
};
159-
lines.push(Line::from(vec![
160-
Span::raw(" "),
161-
Span::styled(label, style),
162-
]));
163-
164-
// Content lines (immediately after label, no blank line).
165-
for line in content.trim().lines() {
166-
lines.push(Line::from(vec![
167-
Span::raw(" "),
168-
Span::styled(line, self.theme.text()),
169-
]));
103+
/// Update cached viewport height and sync scroll position. Called by
104+
/// [`App`](super::super::app::App) after each frame.
105+
pub(crate) fn update_layout(&mut self, area: Rect) {
106+
self.viewport_height = area.height;
107+
if self.auto_scroll {
108+
self.scroll_to_bottom();
170109
}
171110
}
172111
}
@@ -242,7 +181,32 @@ impl Component for ChatView {
242181
}
243182
}
244183

184+
// ── Private Helpers ──
185+
245186
impl ChatView {
187+
fn scroll_to_bottom(&mut self) {
188+
self.scroll_offset = self
189+
.content_height
190+
.get()
191+
.saturating_sub(self.viewport_height);
192+
}
193+
194+
fn scroll_up(&mut self, lines: u16) {
195+
self.scroll_offset = self.scroll_offset.saturating_sub(lines);
196+
self.auto_scroll = false;
197+
}
198+
199+
fn scroll_down(&mut self, lines: u16) {
200+
let max = self
201+
.content_height
202+
.get()
203+
.saturating_sub(self.viewport_height);
204+
self.scroll_offset = self.scroll_offset.saturating_add(lines).min(max);
205+
if self.scroll_offset >= max {
206+
self.auto_scroll = true;
207+
}
208+
}
209+
246210
fn render_inner(&self, frame: &mut Frame, area: Rect) {
247211
let text = self.build_text();
248212
#[expect(
@@ -255,12 +219,48 @@ impl ChatView {
255219
frame.render_widget(paragraph, area);
256220
}
257221

258-
/// Update cached viewport height and sync scroll position. Called by
259-
/// [`App`](super::super::app::App) after each frame.
260-
pub(crate) fn update_layout(&mut self, area: Rect) {
261-
self.viewport_height = area.height;
262-
if self.auto_scroll {
263-
self.scroll_to_bottom();
222+
fn build_text(&self) -> Text<'_> {
223+
let mut lines: Vec<Line<'_>> = Vec::new();
224+
225+
for msg in &self.messages {
226+
self.push_message_lines(&mut lines, msg.role, &msg.content);
227+
}
228+
229+
// Streaming buffer (not yet committed).
230+
if !self.streaming_buffer.is_empty() {
231+
self.push_message_lines(&mut lines, ChatRole::Assistant, &self.streaming_buffer);
232+
}
233+
234+
Text::from(lines)
235+
}
236+
237+
fn push_message_lines<'a>(
238+
&'a self,
239+
lines: &mut Vec<Line<'a>>,
240+
role: ChatRole,
241+
content: &'a str,
242+
) {
243+
// Single blank line between messages.
244+
if !lines.is_empty() {
245+
lines.push(Line::raw(""));
246+
}
247+
248+
// Role label.
249+
let (label, style) = match role {
250+
ChatRole::User => ("❯ You", self.theme.accent()),
251+
ChatRole::Assistant => ("⟡ Assistant", self.theme.secondary()),
252+
};
253+
lines.push(Line::from(vec![
254+
Span::raw(" "),
255+
Span::styled(label, style),
256+
]));
257+
258+
// Content lines (immediately after label, no blank line).
259+
for line in content.trim().lines() {
260+
lines.push(Line::from(vec![
261+
Span::raw(" "),
262+
Span::styled(line, self.theme.text()),
263+
]));
264264
}
265265
}
266266
}

0 commit comments

Comments
 (0)