use ratatui::Frame;
use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::Paragraph;
use crate::action::{Operator, Token};
use crate::app::{App, Prompt};
use crate::mode::Mode;
const STATUS_LEFT_WIDTH: u16 = 14;
const STATUS_RIGHT_WIDTH: u16 = 24;
pub(super) fn draw_status(f: &mut Frame, app: &App, area: Rect) {
let cols = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Length(STATUS_LEFT_WIDTH),
Constraint::Min(1),
Constraint::Length(STATUS_RIGHT_WIDTH),
])
.split(area);
let (label, color) = status_label(app);
let mode_span = Span::styled(
format!(" {} ", label),
Style::default()
.bg(color)
.fg(Color::Black)
.add_modifier(Modifier::BOLD),
);
f.render_widget(Paragraph::new(Line::from(vec![mode_span])), cols[0]);
let name = file_label(app);
f.render_widget(
Paragraph::new(Line::from(Span::styled(
name,
Style::default().add_modifier(Modifier::BOLD),
)))
.alignment(Alignment::Center),
cols[1],
);
let pos = format!(
"{}:{} ",
app.buffer.cursor.row + 1,
app.cursor_visual_col() + 1
);
let pending = format_pending(&app.tokens);
let mut right_spans = Vec::new();
if !pending.is_empty() {
right_spans.push(Span::styled(
pending,
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
));
right_spans.push(Span::raw(" "));
}
right_spans.push(Span::styled(pos, Style::default().fg(Color::Gray)));
f.render_widget(
Paragraph::new(Line::from(right_spans)).alignment(Alignment::Right),
cols[2],
);
}
fn file_label(app: &App) -> String {
match &app.buffer.path {
Some(p) => {
let name = p
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| p.display().to_string());
if app.buffer.dirty {
format!("{} [+]", name)
} else {
name
}
}
None => "[scratch]".to_string(),
}
}
pub(super) fn draw_command_line(f: &mut Frame, app: &App, area: Rect) {
let (prefix, content) = match &app.prompt.state {
Prompt::Command(buf) => (":", buf.as_str()),
Prompt::Search {
forward: true,
query,
} => ("/", query.as_str()),
Prompt::Search {
forward: false,
query,
} => ("?", query.as_str()),
Prompt::Rename(buf) => ("rename ▸ ", buf.as_str()),
_ => return,
};
let text = format!("{}{}", prefix, content);
f.render_widget(Paragraph::new(text), area);
}
fn status_label(app: &App) -> (String, Color) {
match &app.prompt.state {
Prompt::None => (app.mode.to_string(), mode_color(app.mode)),
Prompt::Command(_) => ("COMMAND".into(), Color::Yellow),
Prompt::Search { forward: true, .. } => ("SEARCH/".into(), Color::LightBlue),
Prompt::Search { forward: false, .. } => ("SEARCH?".into(), Color::LightBlue),
Prompt::Fuzzy(_) => ("FUZZY".into(), Color::LightMagenta),
Prompt::Rename(_) => ("RENAME".into(), Color::LightCyan),
Prompt::CodeActionMenu { .. } => ("CODE ACTION".into(), Color::LightMagenta),
Prompt::Hover { .. } => ("HOVER".into(), Color::LightBlue),
}
}
fn mode_color(mode: Mode) -> Color {
match mode {
Mode::Normal => Color::Cyan,
Mode::Insert => Color::Green,
Mode::Visual => Color::Magenta,
Mode::VisualLine => Color::LightMagenta,
Mode::VisualBlock => Color::LightRed,
}
}
fn format_pending(tokens: &[Token]) -> String {
let mut s = String::new();
for t in tokens {
match t {
Token::Count(d) => s.push_str(&d.to_string()),
Token::Op(Operator::Delete) => s.push('d'),
Token::Op(Operator::Yank) => s.push('y'),
Token::Op(Operator::Change) => s.push('c'),
Token::Op(Operator::Indent) => s.push('>'),
Token::Op(Operator::Dedent) => s.push('<'),
Token::SelfDouble(Operator::Delete) => s.push('d'),
Token::SelfDouble(Operator::Yank) => s.push('y'),
Token::SelfDouble(Operator::Change) => s.push('c'),
Token::SelfDouble(Operator::Indent) => s.push('>'),
Token::SelfDouble(Operator::Dedent) => s.push('<'),
Token::Scope(crate::action::Scope::Inner) => s.push('i'),
Token::Scope(crate::action::Scope::Around) => s.push('a'),
Token::LeaderPrefix => s.push_str("<space>"),
Token::GotoPrefix => s.push('g'),
Token::FindCharPrefix { forward, till } => {
s.push(match (forward, till) {
(true, false) => 'f',
(false, false) => 'F',
(true, true) => 't',
(false, true) => 'T',
});
}
Token::ZPrefix => s.push('z'),
Token::ReplaceCharPrefix => s.push('r'),
Token::Motion(_) | Token::Direct(_) | Token::Object(_) => s.push('?'),
}
}
s
}