Menyeragamkan format kode sesuai rustfmt (126 file). Sebelumnya lefthook pre-commit 'cargo fmt --check' akan gagal pada commit apa pun.
128 lines
4.7 KiB
Rust
128 lines
4.7 KiB
Rust
//! Chat input box and autocomplete dropdown rendering.
|
||
//! Implements the Component trait.
|
||
|
||
use super::theme::Theme;
|
||
use crate::components::Component;
|
||
use ratatui::layout::Rect;
|
||
use ratatui::style::{Modifier, Style};
|
||
use ratatui::text::{Line, Span};
|
||
use ratatui::widgets::{Block, BorderType, Borders, Paragraph};
|
||
use ratatui::Frame;
|
||
|
||
#[derive(Default)]
|
||
pub struct InputComponent;
|
||
|
||
impl Component for InputComponent {
|
||
fn draw(&mut self, frame: &mut Frame, area: Rect, state: &crate::state::AppStateRest) {
|
||
if state.input.autocomplete_visible && !state.input.autocomplete_candidates.is_empty() {
|
||
let n = state.input.autocomplete_candidates.len().min(10) as u16;
|
||
let dropdown_height = n + 2;
|
||
let dropdown_area = Rect {
|
||
x: area.x,
|
||
y: area.y.saturating_sub(dropdown_height),
|
||
width: area.width.min(45),
|
||
height: dropdown_height,
|
||
};
|
||
let dropdown_title = match state.input.autocomplete_kind {
|
||
crate::state::AutocompleteKind::Command => " ⌘ Commands ",
|
||
crate::state::AutocompleteKind::FileMention => " 📁 Files ",
|
||
};
|
||
let dropdown_block = Block::default()
|
||
.borders(Borders::ALL)
|
||
.border_type(BorderType::Rounded)
|
||
.border_style(Style::default().fg(Theme::BORDER))
|
||
.title(Span::styled(
|
||
dropdown_title,
|
||
Style::default()
|
||
.fg(Theme::PRIMARY)
|
||
.add_modifier(Modifier::BOLD),
|
||
))
|
||
.style(Style::default().bg(Theme::SURFACE_ELEVATED));
|
||
|
||
let mut lines: Vec<Line> = Vec::new();
|
||
let selected = state.input.autocomplete_idx;
|
||
for (i, candidate) in state
|
||
.input
|
||
.autocomplete_candidates
|
||
.iter()
|
||
.enumerate()
|
||
.take(10)
|
||
{
|
||
let prefix = if i == selected { " ▸ " } else { " " };
|
||
let style = if i == selected {
|
||
Style::default()
|
||
.fg(Theme::TEXT)
|
||
.bg(Theme::HIGHLIGHT_DIM)
|
||
.add_modifier(Modifier::BOLD)
|
||
} else {
|
||
Style::default().fg(Theme::TEXT)
|
||
};
|
||
let label = format!("{prefix}{candidate}");
|
||
lines.push(Line::from(Span::styled(label, style)));
|
||
}
|
||
let dropdown = Paragraph::new(lines).block(dropdown_block);
|
||
frame.render_widget(ratatui::widgets::Clear, dropdown_area);
|
||
frame.render_widget(dropdown, dropdown_area);
|
||
}
|
||
|
||
let block = Block::default()
|
||
.borders(Borders::ALL)
|
||
.border_type(BorderType::Rounded)
|
||
.border_style(Style::default().fg(Theme::BORDER_FOCUSED))
|
||
.style(Style::default().bg(Theme::BG));
|
||
|
||
let input_text = &state.input.buffer;
|
||
let cursor_pos = state.input.cursor;
|
||
|
||
let prompt = Span::styled(
|
||
" ❯ ",
|
||
Style::default()
|
||
.fg(Theme::PRIMARY)
|
||
.add_modifier(Modifier::BOLD),
|
||
);
|
||
|
||
let mut spans = vec![prompt];
|
||
|
||
if input_text.is_empty() {
|
||
spans.push(Span::styled(
|
||
"Type a message, @file, or /command...",
|
||
Style::default()
|
||
.fg(Theme::TEXT_DIM)
|
||
.add_modifier(Modifier::ITALIC),
|
||
));
|
||
} else {
|
||
let (before, after) = input_text.split_at(cursor_pos);
|
||
spans.push(Span::raw(before.to_string()));
|
||
let (cursor_char, after_char) = if after.is_empty() {
|
||
(" ".to_string(), "")
|
||
} else {
|
||
let c = after.chars().next().unwrap();
|
||
let char_len = c.len_utf8();
|
||
(c.to_string(), &after[char_len..])
|
||
};
|
||
|
||
// Blinking cursor logic based on tick count
|
||
let cursor_style = if state.misc.tick_count % 10 < 5 {
|
||
Style::default()
|
||
.bg(Theme::PRIMARY)
|
||
.fg(Theme::BG)
|
||
.add_modifier(Modifier::BOLD)
|
||
} else {
|
||
Style::default()
|
||
.bg(Theme::TEXT_DIM)
|
||
.fg(Theme::BG)
|
||
.add_modifier(Modifier::BOLD)
|
||
};
|
||
|
||
spans.push(Span::styled(cursor_char, cursor_style));
|
||
if !after_char.is_empty() {
|
||
spans.push(Span::raw(after_char.to_string()));
|
||
}
|
||
}
|
||
|
||
let line = Line::from(spans);
|
||
let paragraph = Paragraph::new(line).block(block);
|
||
frame.render_widget(paragraph, area);
|
||
}
|
||
}
|