use crate::console::{Console, ConsoleOptions};
use crate::measure::Measurement;
use crate::segment::Segment;
use crate::text::Text;
pub trait Renderable {
fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment>;
fn measure(&self, _console: &Console, options: &ConsoleOptions) -> Measurement {
Measurement::new(options.max_width, options.max_width)
}
fn fit_to_measurement(&self) -> bool {
false
}
#[doc(hidden)]
fn printed_text(&self) -> Option<Text> {
None
}
fn vertical(&self) -> Option<crate::align::VerticalAlign> {
None
}
}
pub trait LineRenderable: Renderable {
fn try_for_each_line<E>(
&self,
console: &Console,
options: &ConsoleOptions,
emit: impl FnMut(Vec<Segment>) -> Result<(), E>,
) -> Result<(), E>;
}
pub trait OwnedTableRows {
fn extend_owned_rows(&mut self, rows: Vec<Vec<String>>) -> &mut Self;
}
pub trait Highlighter {
fn highlight(&self, text: &mut Text);
}
#[derive(Clone, Debug, PartialEq)]
pub struct HighlightSpan {
pub range: std::ops::Range<usize>,
pub style: crate::style::Style,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct HighlightedLine {
pub spans: Vec<HighlightSpan>,
pub newline_style: Option<crate::style::Style>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct HighlightedCode {
pub lines: Vec<HighlightedLine>,
pub background: Option<crate::color::Color>,
pub default_style: crate::style::Style,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HighlightError {
UnknownTheme(String),
Engine(String),
}
impl std::fmt::Display for HighlightError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HighlightError::UnknownTheme(name) => write!(f, "unknown syntax theme {name:?}"),
HighlightError::Engine(message) => write!(f, "syntax highlighting failed: {message}"),
}
}
}
impl std::error::Error for HighlightError {}
pub trait CodeHighlighter: Send + Sync {
fn highlight(
&self,
code: &str,
language: Option<&str>,
theme: &str,
) -> Result<HighlightedCode, HighlightError>;
fn default_theme(&self) -> &str;
fn themes(&self) -> Vec<String>;
fn languages(&self) -> Vec<String>;
fn language_for_path(&self, _path: &std::path::Path) -> Option<String> {
None
}
fn token_style(&self, _theme: &str, _token: &str) -> Option<crate::style::Style> {
None
}
}
pub trait FenceRenderer: Send + Sync {
fn render_fence(
&self,
language: &str,
code: &str,
console: &Console,
options: &ConsoleOptions,
) -> Option<Vec<Segment>>;
}
#[derive(Clone)]
pub struct CodeHighlighting {
pub highlighter: std::sync::Arc<dyn CodeHighlighter>,
pub theme: Option<String>,
}
impl std::fmt::Debug for CodeHighlighting {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CodeHighlighting")
.field("default_theme", &self.highlighter.default_theme())
.field("theme", &self.theme)
.finish()
}
}
pub trait ConsoleCodeHighlighting {
fn set_code_highlighting(&mut self, value: Option<CodeHighlighting>);
fn code_highlighting(&self) -> Option<&CodeHighlighting>;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Support {
Unsupported,
Inferred,
Confirmed,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TargetCapabilities {
pub width: usize,
pub height: usize,
pub color_system: Option<crate::color::ColorSystem>,
pub interactive: bool,
pub unicode: bool,
pub hyperlinks: bool,
pub sixel: Support,
}
pub trait RenderEnvironment: Send + Sync {
fn capabilities(&self) -> TargetCapabilities;
}
pub trait ConsoleEnvironment {
fn set_render_environment(&mut self, value: Option<std::sync::Arc<dyn RenderEnvironment>>);
fn render_environment(&self) -> Option<&dyn RenderEnvironment>;
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum RegionRole {
Panel,
Table,
TableHeader { column: usize },
TableCell { row: usize, column: usize },
TableFooter { column: usize },
Rule,
Heading { level: u8 },
Code,
Link,
Other(String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RegionInfo {
pub role: RegionRole,
pub label: Option<String>,
pub link: Option<String>,
}
impl RegionInfo {
pub fn new(role: RegionRole) -> Self {
RegionInfo {
role,
label: None,
link: None,
}
}
pub fn label(mut self, label: impl AsRef<str>) -> Self {
let label = label.as_ref().trim();
self.label = (!label.is_empty()).then(|| label.to_string());
self
}
pub fn link(mut self, link: impl Into<String>) -> Self {
self.link = Some(link.into());
self
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct RegionId(pub u64);
pub const REGION_META_KEY: &str = "rich.region";
pub trait RegionSink: Send + Sync {
fn enter(&self, region: RegionInfo) -> RegionId;
fn exit(&self, id: RegionId);
}
pub trait ConsoleRegions {
fn set_region_sink(&mut self, value: Option<std::sync::Arc<dyn RegionSink>>);
fn region_sink(&self) -> Option<&dyn RegionSink>;
}
pub struct RegionGuard<'a> {
sink: &'a dyn RegionSink,
id: RegionId,
}
impl RegionGuard<'_> {
pub fn id(&self) -> RegionId {
self.id
}
pub fn tag(&self, segments: &mut [Segment]) {
tag_region(segments, self.id);
}
}
impl Drop for RegionGuard<'_> {
fn drop(&mut self) {
self.sink.exit(self.id);
}
}
pub fn enter_region<'a>(
console: &'a Console,
info: impl FnOnce() -> RegionInfo,
) -> Option<RegionGuard<'a>> {
let sink = console.region_sink()?;
let id = sink.enter(info());
Some(RegionGuard { sink, id })
}
pub fn report_region(
console: &Console,
info: impl FnOnce() -> RegionInfo,
render: impl FnOnce() -> Vec<Segment>,
) -> Vec<Segment> {
match enter_region(console, info) {
None => render(),
Some(guard) => {
let mut segments = render();
guard.tag(&mut segments);
segments
}
}
}
pub fn tag_region(segments: &mut [Segment], id: RegionId) {
use crate::style::{Meta, MetaValue};
for segment in segments.iter_mut().filter(|segment| !segment.control) {
let style = segment.style.take().unwrap_or_default();
if region_of(&style).is_some() {
segment.style = Some(style);
continue;
}
let mut meta = style.meta_ref().cloned().unwrap_or_else(Meta::new);
meta.insert(REGION_META_KEY, MetaValue::Int(id.0 as i64));
segment.style = Some(style.with_meta(meta));
}
}
pub fn region_of(style: &crate::style::Style) -> Option<RegionId> {
match style.meta_ref()?.get(REGION_META_KEY)? {
crate::style::MetaValue::Int(id) => Some(RegionId(*id as u64)),
_ => None,
}
}