use std::sync::Arc;
use anstyle::{Color as AnsiColorEnum, Effects, Style as AnsiStyle};
use crate::ui_protocol::types::EditingMode;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct InlineTextStyle {
pub color: Option<AnsiColorEnum>,
pub bg_color: Option<AnsiColorEnum>,
pub effects: Effects,
}
impl InlineTextStyle {
#[must_use]
pub fn with_color(mut self, color: Option<AnsiColorEnum>) -> Self {
self.color = color;
self
}
#[must_use]
pub fn with_bg_color(mut self, color: Option<AnsiColorEnum>) -> Self {
self.bg_color = color;
self
}
#[must_use]
pub fn merge_color(mut self, fallback: Option<AnsiColorEnum>) -> Self {
if self.color.is_none() {
self.color = fallback;
}
self
}
#[must_use]
pub fn merge_bg_color(mut self, fallback: Option<AnsiColorEnum>) -> Self {
if self.bg_color.is_none() {
self.bg_color = fallback;
}
self
}
#[must_use]
pub fn bold(mut self) -> Self {
self.effects |= Effects::BOLD;
self
}
#[must_use]
pub fn italic(mut self) -> Self {
self.effects |= Effects::ITALIC;
self
}
#[must_use]
pub fn underline(mut self) -> Self {
self.effects |= Effects::UNDERLINE;
self
}
#[must_use]
pub fn dim(mut self) -> Self {
self.effects |= Effects::DIMMED;
self
}
#[must_use]
pub fn to_ansi_style(&self, fallback: Option<AnsiColorEnum>) -> AnsiStyle {
let mut style = AnsiStyle::new();
if let Some(color) = self.color.or(fallback) {
style = style.fg_color(Some(color));
}
if let Some(bg) = self.bg_color {
style = style.bg_color(Some(bg));
}
if self.effects.contains(Effects::BOLD) {
style = style.bold();
}
if self.effects.contains(Effects::ITALIC) {
style = style.italic();
}
if self.effects.contains(Effects::UNDERLINE) {
style = style.underline();
}
if self.effects.contains(Effects::DIMMED) {
style = style.dimmed();
}
style
}
}
#[derive(Clone, Debug, Default)]
pub struct InlineSegment {
pub text: String,
pub style: Arc<InlineTextStyle>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InlineLinkTarget {
Url(String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct InlineLinkRange {
pub start: usize,
pub end: usize,
pub target: InlineLinkTarget,
}
#[derive(Clone, Debug, Default)]
pub struct InlineTheme {
pub foreground: Option<AnsiColorEnum>,
pub background: Option<AnsiColorEnum>,
pub primary: Option<AnsiColorEnum>,
pub secondary: Option<AnsiColorEnum>,
pub tool_accent: Option<AnsiColorEnum>,
pub tool_body: Option<AnsiColorEnum>,
pub pty_body: Option<AnsiColorEnum>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum InlineHeaderStatusTone {
#[default]
Ready,
Warning,
Error,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct InlineHeaderStatusBadge {
pub text: String,
pub tone: InlineHeaderStatusTone,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct InlineHeaderBadge {
pub text: String,
pub style: InlineTextStyle,
pub full_background: bool,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct InlineHeaderHighlight {
pub title: String,
pub lines: Vec<String>,
}
#[derive(Clone, Debug)]
pub struct InlineHeaderContext {
pub app_name: String,
pub provider: String,
pub model: String,
pub context_window_size: Option<usize>,
pub version: String,
pub search_tools: Option<InlineHeaderStatusBadge>,
pub persistent_memory: Option<InlineHeaderStatusBadge>,
pub pr_review: Option<InlineHeaderStatusBadge>,
pub editor_context: Option<String>,
pub git: String,
pub mode: String,
pub reasoning: String,
pub reasoning_stage: Option<String>,
pub workspace_trust: String,
pub tools: String,
pub mcp: String,
pub highlights: Vec<InlineHeaderHighlight>,
pub subagent_badges: Vec<InlineHeaderBadge>,
pub editing_mode: EditingMode,
pub autonomous_mode: bool,
}
impl Default for InlineHeaderContext {
fn default() -> Self {
let version = env!("CARGO_PKG_VERSION").to_string();
Self {
app_name: "App".to_string(),
provider: "Provider: unavailable".to_string(),
model: "Model: unavailable".to_string(),
context_window_size: None,
version,
search_tools: None,
persistent_memory: None,
pr_review: None,
editor_context: None,
git: "git: unavailable".to_string(),
mode: "Inline session".to_string(),
reasoning: "Reasoning effort: unavailable".to_string(),
reasoning_stage: None,
workspace_trust: "Trust: unavailable".to_string(),
tools: "Tools: unavailable".to_string(),
mcp: "MCP: unavailable".to_string(),
highlights: Vec::new(),
subagent_badges: Vec::new(),
editing_mode: EditingMode::default(),
autonomous_mode: false,
}
}
}
fn convert_ansi_color(color: AnsiColorEnum) -> Option<AnsiColorEnum> {
Some(match color {
AnsiColorEnum::Ansi(ansi) => AnsiColorEnum::Ansi(ansi),
AnsiColorEnum::Ansi256(value) => AnsiColorEnum::Ansi256(value),
AnsiColorEnum::Rgb(rgb) => AnsiColorEnum::Rgb(rgb),
})
}
fn convert_style_color(style: &AnsiStyle) -> Option<AnsiColorEnum> {
style.get_fg_color().and_then(convert_ansi_color)
}
fn convert_style_bg_color(style: &AnsiStyle) -> Option<AnsiColorEnum> {
style.get_bg_color().and_then(convert_ansi_color)
}
pub fn convert_style(style: AnsiStyle) -> InlineTextStyle {
InlineTextStyle {
color: convert_style_color(&style),
bg_color: convert_style_bg_color(&style),
effects: style.get_effects(),
}
}
pub fn theme_from_color_fields(
foreground: AnsiColorEnum,
background: AnsiColorEnum,
primary: AnsiStyle,
secondary: AnsiStyle,
tool: AnsiStyle,
tool_detail: AnsiStyle,
pty_output: AnsiStyle,
) -> InlineTheme {
InlineTheme {
foreground: convert_ansi_color(foreground),
background: convert_ansi_color(background),
primary: convert_style_color(&primary),
secondary: convert_style_color(&secondary),
tool_accent: convert_style_color(&tool),
tool_body: convert_style_color(&tool_detail),
pty_body: convert_style_color(&pty_output),
}
}