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::{Block, 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;
const STATUS_PAD: u16 = 1;
const STATUS_BG: Color = Color::DarkGray;
pub(super) fn draw_status(f: &mut Frame, app: &App, area: Rect) {
f.render_widget(Block::default().style(Style::default().bg(STATUS_BG)), area);
let cols = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Length(STATUS_PAD),
Constraint::Length(STATUS_LEFT_WIDTH),
Constraint::Min(1),
Constraint::Length(STATUS_RIGHT_WIDTH),
Constraint::Length(STATUS_PAD),
])
.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])).style(Style::default().bg(STATUS_BG)),
cols[1],
);
let name = file_label(app);
f.render_widget(
Paragraph::new(Line::from(Span::styled(
name,
Style::default().bg(STATUS_BG).add_modifier(Modifier::BOLD),
)))
.style(Style::default().bg(STATUS_BG))
.alignment(Alignment::Center),
cols[2],
);
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()
.bg(STATUS_BG)
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
));
right_spans.push(Span::styled(" ", Style::default().bg(STATUS_BG)));
}
right_spans.push(Span::styled(
pos,
Style::default().bg(STATUS_BG).fg(Color::Gray),
));
f.render_widget(
Paragraph::new(Line::from(right_spans))
.style(Style::default().bg(STATUS_BG))
.alignment(Alignment::Right),
cols[3],
);
}
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, input) = match &app.prompt.state {
Prompt::Command(cp) => (":", &cp.input),
Prompt::Search {
forward: true,
query,
} => ("/", query),
Prompt::Search {
forward: false,
query,
} => ("?", query),
Prompt::Rename(buf) => ("rename ▸ ", buf),
_ => return,
};
let text = format!("{}{}", prefix, input.as_str());
f.render_widget(Paragraph::new(text), area);
let col = (crate::text_width::str_cell_width(prefix) + input.cursor_cell_col()) as u16;
let x = area.x + col.min(area.width.saturating_sub(1));
f.set_cursor_position((x, area.y));
}
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::Explorer(_) => ("EXPLORER".into(), Color::LightMagenta),
Prompt::Rename(_) => ("RENAME".into(), Color::LightCyan),
Prompt::CodeActionMenu { .. } => ("CODE ACTION".into(), Color::LightMagenta),
Prompt::Hover { .. } => ("HOVER".into(), Color::LightBlue),
Prompt::LspStatus { .. } => ("LSP".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::Op(Operator::Comment) => s.push('c'),
Token::Op(Operator::BlockComment) => s.push('b'),
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::SelfDouble(Operator::Comment) => s.push('c'),
Token::SelfDouble(Operator::BlockComment) => s.push('c'),
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::WindowPrefix => s.push('w'),
Token::CtrlWPrefix => s.push_str("<C-w>"),
Token::BracketPrefix { forward } => s.push(if *forward { ']' } else { '[' }),
Token::SurroundAddPrefix
| Token::SurroundChangePrefix
| Token::SurroundDeletePrefix => s.push('s'),
Token::SurroundChar(c) => s.push(*c),
Token::Motion(_) | Token::Direct(_) | Token::Object(_) => s.push('?'),
}
}
s
}