pub mod alt_screen;
pub mod cell;
pub mod plain;
pub mod qr;
pub mod retained;
pub mod screen;
pub mod theme;
pub mod worker;
use std::time::Duration;
#[derive(Debug, Clone)]
pub enum UiLine {
Welcome {
model: String,
working_dir: String,
},
User(String),
AssistantText(String),
ReasoningText(String),
AssistantLineBreak,
ToolCall {
name: String,
detail: String,
},
ToolCallInFlight {
id: String,
name: String,
detail: String,
},
ToolCallCommit {
call_id: Option<String>,
},
ToolGroupRender {
batch_id: String,
header: String,
children: Vec<ToolGroupChild>,
},
ToolGroupChildUpdate {
batch_id: String,
call_id: String,
new_text: String,
},
ToolGroupSummary {
text: String,
},
ToolResult {
success: bool,
summary: String,
},
DiffLine {
added: bool,
text: String,
},
DiffBlock(Vec<DiffEntry>),
ApprovalPrompt {
tool: String,
detail: String,
},
Error(String),
Warning(String),
TurnCancelled,
TurnComplete,
Spinner {
frame: &'static str,
label: String,
},
ClearTransient,
InputPrompt {
buf: String,
cursor_byte: usize,
menu: Option<MenuPayload>,
status: StatusLine,
attachments: Vec<usize>,
},
StreamingBox {
buf: String,
cursor_byte: usize,
frame: &'static str,
label: String,
status: StatusLine,
menu: Option<MenuPayload>,
attachments: Vec<usize>,
},
InputCommit,
CommandOutput(String),
ImageAttachment(usize),
VisionPreprocessSuccess {
msg: String,
model: String,
},
TurnSeparator {
label: String,
},
}
pub trait Renderer: Send {
fn render(&mut self, line: UiLine);
fn flush(&mut self);
fn shutdown(&mut self);
fn reset(&mut self);
fn clear_screen(&mut self);
fn suspend_for_external(&mut self);
fn resume_from_external(&mut self);
fn flush_deferred(&mut self);
fn pop_approval_prompt(&mut self) {}
fn on_resize(&mut self, _cols: u16, _rows: u16) {}
fn scroll_body(&mut self, _delta: i32) {}
fn scroll_body_to_top(&mut self) {}
fn scroll_body_to_bottom(&mut self) {}
fn begin_selection(&mut self, _col: u16, _row: u16) {}
fn update_selection(&mut self, _col: u16, _row: u16) {}
fn end_selection(&mut self) {}
fn copy_selection(&mut self) -> bool {
false
}
fn refresh_welcome_banner(&mut self, _model: &str, _working_dir: &str) {}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum MenuKind {
#[default]
SlashCommand,
AtMention,
}
#[derive(Debug, Clone, Default)]
pub struct MenuPayload {
pub items: Vec<(String, String)>, pub selected: usize,
pub kind: MenuKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum HintSeverity {
#[default]
Warning,
Info,
}
#[derive(Debug, Clone, Default)]
pub struct StatusLine {
pub model: String,
pub cwd: String, pub ctx_used: usize,
pub ctx_window: usize,
pub hint: Option<(String, HintSeverity)>,
pub mode_indicator: Option<String>,
pub session_name: Option<String>,
}
#[derive(Debug, Clone)]
pub struct DiffEntry {
pub added: bool,
pub text: String,
}
#[derive(Debug, Clone)]
pub struct ToolGroupChild {
pub call_id: String,
pub text: String,
}
pub fn fmt_dur(d: Duration) -> String {
let ms = d.as_millis();
if ms < 1000 {
format!("{}ms", ms)
} else {
format!("{:.1}s", d.as_secs_f64())
}
}