use super::*;
#[derive(Debug, Clone)]
pub(crate) struct ThemeRoles {
pub(crate) surface_background: OpalineColor,
pub(crate) surface_panel: OpalineColor,
pub(crate) surface_panel_alt: OpalineColor,
pub(crate) surface_overlay: OpalineColor,
pub(crate) surface_selection: OpalineColor,
pub(crate) surface_code: OpalineColor,
pub(crate) surface_success: OpalineColor,
pub(crate) surface_pending: OpalineColor,
pub(crate) surface_error: OpalineColor,
pub(crate) text_primary: OpalineColor,
pub(crate) text_muted: OpalineColor,
pub(crate) text_dim: OpalineColor,
pub(crate) text_accent: OpalineColor,
pub(crate) text_title: OpalineColor,
pub(crate) text_prompt: OpalineColor,
pub(crate) text_status: OpalineColor,
pub(crate) text_help: OpalineColor,
pub(crate) text_metadata: OpalineColor,
pub(crate) text_command: OpalineColor,
pub(crate) text_command_active: OpalineColor,
pub(crate) border_default: OpalineColor,
pub(crate) border_active: OpalineColor,
pub(crate) border_alternate: OpalineColor,
pub(crate) border_command: OpalineColor,
pub(crate) border_command_active: OpalineColor,
pub(crate) border_error: OpalineColor,
pub(crate) status_queued: OpalineColor,
pub(crate) status_running: OpalineColor,
pub(crate) status_writing: OpalineColor,
pub(crate) status_success: OpalineColor,
pub(crate) status_failed: OpalineColor,
pub(crate) status_canceled: OpalineColor,
pub(crate) status_tool: OpalineColor,
pub(crate) status_subagent: OpalineColor,
pub(crate) status_assistant: OpalineColor,
pub(crate) status_diagnostic: OpalineColor,
pub(crate) transcript_user: OpalineColor,
pub(crate) transcript_assistant: OpalineColor,
pub(crate) transcript_thinking: OpalineColor,
pub(crate) transcript_tool: OpalineColor,
pub(crate) transcript_session: OpalineColor,
pub(crate) transcript_diagnostic: OpalineColor,
pub(crate) transcript_heading: OpalineColor,
pub(crate) transcript_section: OpalineColor,
pub(crate) transcript_error_label: OpalineColor,
pub(crate) transcript_error_body: OpalineColor,
pub(crate) diff_inserted: OpalineColor,
pub(crate) diff_removed: OpalineColor,
pub(crate) diff_changed: OpalineColor,
pub(crate) diff_context: OpalineColor,
pub(crate) diff_hunk_header: OpalineColor,
pub(crate) diff_file_header: OpalineColor,
pub(crate) diff_metadata: OpalineColor,
pub(crate) syntax_plain: OpalineColor,
pub(crate) syntax_keyword: OpalineColor,
pub(crate) syntax_string: OpalineColor,
pub(crate) syntax_comment: OpalineColor,
pub(crate) syntax_number: OpalineColor,
pub(crate) syntax_function: OpalineColor,
pub(crate) syntax_type: OpalineColor,
pub(crate) syntax_operator: OpalineColor,
pub(crate) syntax_punctuation: OpalineColor,
pub(crate) syntax_code_fence: OpalineColor,
pub(crate) syntax_language_label: OpalineColor,
pub(crate) syntax_gutter: OpalineColor,
pub(crate) syntax_fallback: OpalineColor,
pub(crate) heading_style: OpalineStyle,
pub(crate) user_heading_style: OpalineStyle,
pub(crate) assistant_heading_style: OpalineStyle,
pub(crate) thinking_heading_style: OpalineStyle,
pub(crate) tool_heading_style: OpalineStyle,
pub(crate) standard_styles: ThemeStandardStyles,
pub(crate) gradients: ThemeGradients,
}
#[derive(Debug, Clone)]
pub(crate) struct ThemeGradients {
pub(crate) fast: Gradient,
}
#[derive(Debug, Clone)]
pub(crate) struct ThemeStandardStyles {
pub(crate) keyword: OpalineStyle,
pub(crate) line_number: OpalineStyle,
pub(crate) selected: OpalineStyle,
pub(crate) active_selected: OpalineStyle,
pub(crate) focused_border: OpalineStyle,
pub(crate) unfocused_border: OpalineStyle,
pub(crate) success: OpalineStyle,
pub(crate) error: OpalineStyle,
pub(crate) warning: OpalineStyle,
pub(crate) info: OpalineStyle,
pub(crate) dimmed: OpalineStyle,
pub(crate) muted: OpalineStyle,
pub(crate) inline_code: OpalineStyle,
}
impl ThemeRoles {
pub(super) fn from_theme(theme: &Theme) -> Self {
let text_primary = role_color(theme, "magi.text.primary", &[tokens::TEXT_PRIMARY]);
let text_muted = role_color(theme, "magi.text.muted", &[tokens::TEXT_MUTED]);
let text_dim = role_color(theme, "magi.text.dim", &[tokens::TEXT_DIM]);
let text_accent = role_color(theme, "magi.text.accent", &[tokens::ACCENT_PRIMARY]);
let text_title = role_color(
theme,
"magi.text.title",
&["magi.text.accent", tokens::ACCENT_PRIMARY],
);
let text_prompt = role_color(theme, "magi.text.prompt", &[tokens::TEXT_PRIMARY]);
let text_status = role_color(theme, "magi.text.status", &[tokens::TEXT_SECONDARY]);
let text_help = role_color(theme, "magi.text.help", &[tokens::TEXT_SECONDARY]);
let text_metadata = role_color(theme, "magi.text.metadata", &[tokens::TEXT_MUTED]);
let text_command = role_color(theme, "magi.text.command", &[tokens::ACCENT_SECONDARY]);
let text_command_active =
role_color(theme, "magi.text.command_active", &[tokens::ACCENT_PRIMARY]);
let surface_background = role_color(theme, "magi.surface.background", &[tokens::BG_BASE]);
let surface_panel = role_color(
theme,
"magi.surface.panel",
&[tokens::BG_PANEL, tokens::BG_BASE],
);
let surface_panel_alt = role_color(
theme,
"magi.surface.panel_alt",
&[tokens::BG_HIGHLIGHT, tokens::BG_PANEL],
);
let surface_overlay = role_color(
theme,
"magi.surface.overlay",
&["bg.elevated", tokens::BG_HIGHLIGHT, tokens::BG_PANEL],
);
let surface_selection = role_color(
theme,
"magi.surface.selection",
&[tokens::BG_SELECTION, tokens::BG_HIGHLIGHT],
);
let surface_code = role_color(
theme,
"magi.surface.code",
&[tokens::BG_CODE, tokens::BG_BASE],
);
let surface_success = role_color(
theme,
"magi.surface.success",
&[tokens::BG_HIGHLIGHT, tokens::BG_PANEL],
);
let surface_pending = role_color(
theme,
"magi.surface.pending",
&[tokens::BG_HIGHLIGHT, tokens::BG_PANEL],
);
let surface_error = role_color(
theme,
"magi.surface.error",
&[tokens::BG_HIGHLIGHT, tokens::BG_PANEL],
);
let border_default = role_color(theme, "magi.border.default", &[tokens::BORDER_UNFOCUSED]);
let border_active = role_color(theme, "magi.border.active", &[tokens::BORDER_FOCUSED]);
let border_alternate =
role_color(theme, "magi.border.alternate", &[tokens::ACCENT_SECONDARY]);
let border_command = role_color(theme, "magi.border.command", &[tokens::BORDER_UNFOCUSED]);
let border_command_active = role_color(
theme,
"magi.border.command_active",
&[tokens::BORDER_FOCUSED],
);
let border_error = role_color(theme, "magi.border.error", &[tokens::ERROR]);
let status_queued = role_color(theme, "magi.status.queued", &[tokens::TEXT_MUTED]);
let status_running = role_color(theme, "magi.status.running", &[tokens::WARNING]);
let status_writing = role_color(theme, "magi.status.writing", &[tokens::WARNING]);
let status_success = role_color(theme, "magi.status.success", &[tokens::SUCCESS]);
let status_failed = role_color(theme, "magi.status.failed", &[tokens::ERROR]);
let status_canceled = role_color(theme, "magi.status.canceled", &[tokens::TEXT_MUTED]);
let status_tool = role_color(theme, "magi.status.tool", &[tokens::ACCENT_SECONDARY]);
let status_subagent = role_color(theme, "magi.status.subagent", &[tokens::ACCENT_TERTIARY]);
let status_assistant =
role_color(theme, "magi.status.assistant", &[tokens::ACCENT_PRIMARY]);
let status_diagnostic = role_color(theme, "magi.status.diagnostic", &[tokens::INFO]);
let transcript_user =
role_color(theme, "magi.transcript.user", &[tokens::ACCENT_SECONDARY]);
let transcript_assistant =
role_color(theme, "magi.transcript.assistant", &[tokens::TEXT_PRIMARY]);
let transcript_thinking = role_color(
theme,
"magi.transcript.thinking",
&[tokens::ACCENT_TERTIARY],
);
let transcript_tool =
role_color(theme, "magi.transcript.tool", &[tokens::ACCENT_SECONDARY]);
let transcript_session =
role_color(theme, "magi.transcript.session", &[tokens::TEXT_MUTED]);
let transcript_diagnostic =
role_color(theme, "magi.transcript.diagnostic", &[tokens::INFO]);
let transcript_heading =
role_color(theme, "magi.transcript.heading", &[tokens::ACCENT_PRIMARY]);
let transcript_section =
role_color(theme, "magi.transcript.section", &[tokens::TEXT_PRIMARY]);
let transcript_error_label =
role_color(theme, "magi.transcript.error_label", &[tokens::ERROR]);
let transcript_error_body =
role_color(theme, "magi.transcript.error_body", &[tokens::TEXT_PRIMARY]);
let diff_inserted = role_color(theme, "magi.diff.inserted", &[tokens::SUCCESS]);
let diff_removed = role_color(theme, "magi.diff.removed", &[tokens::ERROR]);
let diff_changed = role_color(theme, "magi.diff.changed", &[tokens::WARNING]);
let diff_context = role_color(theme, "magi.diff.context", &[tokens::TEXT_PRIMARY]);
let diff_hunk_header =
role_color(theme, "magi.diff.hunk_header", &[tokens::ACCENT_SECONDARY]);
let diff_file_header =
role_color(theme, "magi.diff.file_header", &[tokens::ACCENT_PRIMARY]);
let diff_metadata = role_color(theme, "magi.diff.metadata", &[tokens::TEXT_MUTED]);
let syntax_plain = role_color(theme, "magi.syntax.plain", &[tokens::TEXT_PRIMARY]);
let syntax_keyword = role_color(theme, "magi.syntax.keyword", &[tokens::CODE_KEYWORD]);
let syntax_string = role_color(theme, "magi.syntax.string", &[tokens::CODE_STRING]);
let syntax_comment = role_color(theme, "magi.syntax.comment", &[tokens::CODE_COMMENT]);
let syntax_number = role_color(theme, "magi.syntax.number", &[tokens::CODE_NUMBER]);
let syntax_function = role_color(theme, "magi.syntax.function", &[tokens::CODE_FUNCTION]);
let syntax_type = role_color(theme, "magi.syntax.type", &[tokens::CODE_TYPE]);
let syntax_operator =
role_color(theme, "magi.syntax.operator", &[tokens::ACCENT_SECONDARY]);
let syntax_punctuation =
role_color(theme, "magi.syntax.punctuation", &[tokens::TEXT_SECONDARY]);
let syntax_code_fence =
role_color(theme, "magi.syntax.code_fence", &[tokens::ACCENT_PRIMARY]);
let syntax_language_label = role_color(
theme,
"magi.syntax.language_label",
&[tokens::ACCENT_SECONDARY],
);
let syntax_gutter = role_color(theme, "magi.syntax.gutter", &[tokens::CODE_LINE_NUMBER]);
let syntax_fallback = role_color(theme, "magi.syntax.fallback", &[tokens::TEXT_SECONDARY]);
let default_heading_style = heading_style(theme, styles::INFO_STYLE, transcript_heading);
let user_heading_style = heading_style(theme, styles::INFO_STYLE, transcript_user);
let assistant_heading_style =
heading_style(theme, styles::INFO_STYLE, transcript_assistant);
let thinking_heading_style = heading_style(theme, styles::INFO_STYLE, transcript_thinking);
let tool_heading_style = heading_style(theme, styles::INFO_STYLE, transcript_tool);
let standard_styles = ThemeStandardStyles {
keyword: standard_style(theme, styles::KEYWORD, syntax_keyword),
line_number: standard_style(theme, styles::LINE_NUMBER, syntax_gutter),
selected: standard_style(theme, styles::SELECTED, text_primary),
active_selected: standard_style(theme, styles::ACTIVE_SELECTED, text_primary),
focused_border: standard_style(theme, styles::FOCUSED_BORDER, border_active),
unfocused_border: standard_style(theme, styles::UNFOCUSED_BORDER, border_default),
success: standard_style(theme, styles::SUCCESS_STYLE, status_success),
error: standard_style(theme, styles::ERROR_STYLE, status_failed),
warning: standard_style(theme, styles::WARNING_STYLE, status_running),
info: standard_style(theme, styles::INFO_STYLE, status_diagnostic),
dimmed: standard_style(theme, styles::DIMMED, text_dim),
muted: standard_style(theme, styles::MUTED, text_muted),
inline_code: standard_style(theme, styles::INLINE_CODE, syntax_plain),
};
let fast = fast_gradient(theme, Gradient::new(vec![text_accent, text_primary]));
Self {
surface_background,
surface_panel,
surface_panel_alt,
surface_overlay,
surface_selection,
surface_code,
surface_success,
surface_pending,
surface_error,
text_primary,
text_muted,
text_dim,
text_accent,
text_title,
text_prompt,
text_status,
text_help,
text_metadata,
text_command,
text_command_active,
border_default,
border_active,
border_alternate,
border_command,
border_command_active,
border_error,
status_queued,
status_running,
status_writing,
status_success,
status_failed,
status_canceled,
status_tool,
status_subagent,
status_assistant,
status_diagnostic,
transcript_user,
transcript_assistant,
transcript_thinking,
transcript_tool,
transcript_session,
transcript_diagnostic,
transcript_heading,
transcript_section,
transcript_error_label,
transcript_error_body,
diff_inserted,
diff_removed,
diff_changed,
diff_context,
diff_hunk_header,
diff_file_header,
diff_metadata,
syntax_plain,
syntax_keyword,
syntax_string,
syntax_comment,
syntax_number,
syntax_function,
syntax_type,
syntax_operator,
syntax_punctuation,
syntax_code_fence,
syntax_language_label,
syntax_gutter,
syntax_fallback,
heading_style: default_heading_style,
user_heading_style,
assistant_heading_style,
thinking_heading_style,
tool_heading_style,
gradients: ThemeGradients { fast },
standard_styles,
}
}
}
fn role_color(theme: &Theme, extension: &str, fallbacks: &[&str]) -> OpalineColor {
token_color(theme, extension)
.or_else(|| fallbacks.iter().find_map(|token| token_color(theme, token)))
.unwrap_or(OpalineColor::FALLBACK)
}
fn token_color(theme: &Theme, name: &str) -> Option<OpalineColor> {
theme
.token_names()
.contains(&name)
.then(|| theme.try_color(name))
.flatten()
}
fn standard_style(theme: &Theme, standard_style: &str, fallback: OpalineColor) -> OpalineStyle {
let mut style = theme
.try_style(standard_style)
.cloned()
.unwrap_or_else(|| OpalineStyle::fg(fallback));
if style.fg.is_none() {
style.fg = Some(fallback);
}
style
}
fn heading_style(theme: &Theme, standard_style: &str, fallback: OpalineColor) -> OpalineStyle {
let mut style = theme
.try_style(standard_style)
.cloned()
.unwrap_or_else(|| OpalineStyle::fg(fallback));
if style.fg.is_none() {
style.fg = Some(fallback);
}
style.bold = true;
style
}
fn fast_gradient(theme: &Theme, fallback: Gradient) -> Gradient {
theme
.get_gradient(FAST_GRADIENT_NAME)
.cloned()
.unwrap_or(fallback)
}