feat: support ANSI themes (#460)

This commit is contained in:
Sam Mohr 2024-01-01 06:07:06 -08:00 committed by GitHub
parent 6b0495f2c5
commit 10a78b5dbc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 102 additions and 31 deletions

View file

@ -28,7 +28,7 @@ parking_lot = "^0"
ratatui = "^0" ratatui = "^0"
regex = "^1" regex = "^1"
serde = "^1" serde = "^1"
syntect = { version = "^5", default-features = false, features = [ "parsing", "default-themes", "plist-load", "regex-onig" ] } syntect = { version = "^5", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
tokio = { version = "^1", features = [ "parking_lot", "macros", "rt-multi-thread", "sync", "time", "fs", "process", "io-std", "io-util" ] } tokio = { version = "^1", features = [ "parking_lot", "macros", "rt-multi-thread", "sync", "time", "fs", "process", "io-std", "io-util" ] }
tokio-stream = "^0" tokio-stream = "^0"
tokio-util = "^0" tokio-util = "^0"

View file

@ -25,9 +25,9 @@ parking_lot = "^0"
ratatui = "^0" ratatui = "^0"
serde = "^1" serde = "^1"
serde_json = "^1" serde_json = "^1"
syntect = { version = "^5", default-features = false, features = [ "parsing", "default-themes", "plist-load", "regex-onig" ] } syntect = { version = "^5", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
tokio = { version = "^1", features = [ "parking_lot", "rt-multi-thread" ] } tokio = { version = "^1", features = [ "parking_lot", "rt-multi-thread" ] }
tokio-util = "^0" tokio-util = "^0"
tracing = { version = "^0", features = [ "max_level_debug", "release_max_level_warn" ] } tracing = { version = "^0", features = [ "max_level_debug", "release_max_level_warn" ] }
unicode-width = "^0" unicode-width = "^0"
yazi-prebuild = "^0" yazi-prebuild = "0.1.2"

View file

@ -1,9 +1,10 @@
use std::{mem, path::{Path, PathBuf}, sync::{atomic::{AtomicUsize, Ordering}, OnceLock}}; use std::{io::Cursor, mem, path::{Path, PathBuf}, sync::{atomic::{AtomicUsize, Ordering}, OnceLock}};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use syntect::{dumps::from_uncompressed_data, easy::HighlightLines, highlighting::{Theme, ThemeSet}, parsing::{SyntaxReference, SyntaxSet}, util::as_24_bit_terminal_escaped}; use ratatui::text::{Line, Span, Text};
use syntect::{dumps, easy::HighlightLines, highlighting::{self, Theme, ThemeSet}, parsing::{SyntaxReference, SyntaxSet}};
use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}}; use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}};
use yazi_config::THEME; use yazi_config::{PREVIEW, THEME};
use yazi_shared::PeekError; use yazi_shared::PeekError;
static INCR: AtomicUsize = AtomicUsize::new(0); static INCR: AtomicUsize = AtomicUsize::new(0);
@ -26,10 +27,13 @@ impl Highlighter {
} }
let theme = SYNTECT_THEME.get_or_init(|| { let theme = SYNTECT_THEME.get_or_init(|| {
from_file().unwrap_or_else(|_| ThemeSet::load_defaults().themes["base16-ocean.dark"].clone()) from_file().unwrap_or_else(|_| {
ThemeSet::load_from_reader(&mut Cursor::new(yazi_prebuild::ansi_theme())).unwrap()
})
}); });
let syntaxes =
SYNTECT_SYNTAX.get_or_init(|| from_uncompressed_data(yazi_prebuild::syntaxes()).unwrap()); let syntaxes = SYNTECT_SYNTAX
.get_or_init(|| dumps::from_uncompressed_data(yazi_prebuild::syntaxes()).unwrap());
(theme, syntaxes) (theme, syntaxes)
} }
@ -52,7 +56,7 @@ impl Highlighter {
syntaxes.find_syntax_by_first_line(&line).ok_or_else(|| anyhow!("No syntax found")) syntaxes.find_syntax_by_first_line(&line).ok_or_else(|| anyhow!("No syntax found"))
} }
pub async fn highlight(&self, skip: usize, limit: usize) -> Result<String, PeekError> { pub async fn highlight(&self, skip: usize, limit: usize) -> Result<Text<'static>, PeekError> {
let mut reader = BufReader::new(File::open(&self.path).await?).lines(); let mut reader = BufReader::new(File::open(&self.path).await?).lines();
let syntax = Self::find_syntax(&self.path).await; let syntax = Self::find_syntax(&self.path).await;
@ -69,8 +73,8 @@ impl Highlighter {
} }
if !plain && line.len() > 6000 { if !plain && line.len() > 6000 {
mem::take(&mut before);
plain = true; plain = true;
drop(mem::take(&mut before));
} }
if i > skip { if i > skip {
@ -87,7 +91,7 @@ impl Highlighter {
} }
if plain { if plain {
Ok(after.join("")) Ok(Text::from(after.join("")))
} else { } else {
Self::highlight_with(before, after, syntax.unwrap()).await Self::highlight_with(before, after, syntax.unwrap()).await
} }
@ -97,13 +101,12 @@ impl Highlighter {
before: Vec<String>, before: Vec<String>,
after: Vec<String>, after: Vec<String>,
syntax: &'static SyntaxReference, syntax: &'static SyntaxReference,
) -> Result<String, PeekError> { ) -> Result<Text<'static>, PeekError> {
let ticket = INCR.load(Ordering::Relaxed); let ticket = INCR.load(Ordering::Relaxed);
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
let (theme, syntaxes) = Self::init(); let (theme, syntaxes) = Self::init();
let mut h = HighlightLines::new(syntax, theme); let mut h = HighlightLines::new(syntax, theme);
let mut result = String::new();
for line in before { for line in before {
if ticket != INCR.load(Ordering::Relaxed) { if ticket != INCR.load(Ordering::Relaxed) {
@ -111,17 +114,18 @@ impl Highlighter {
} }
h.highlight_line(&line, syntaxes).map_err(|e| anyhow!(e))?; h.highlight_line(&line, syntaxes).map_err(|e| anyhow!(e))?;
} }
let mut lines = Vec::with_capacity(after.len());
for line in after { for line in after {
if ticket != INCR.load(Ordering::Relaxed) { if ticket != INCR.load(Ordering::Relaxed) {
return Err("Highlighting cancelled".into()); return Err("Highlighting cancelled".into());
} }
let regions = h.highlight_line(&line, syntaxes).map_err(|e| anyhow!(e))?; let regions = h.highlight_line(&line, syntaxes).map_err(|e| anyhow!(e))?;
result.push_str(&as_24_bit_terminal_escaped(&regions, false)); lines.push(Self::to_line_widget(regions));
} }
result.push_str("\x1b[0m"); Ok(Text::from(lines))
Ok(result)
}) })
.await? .await?
} }
@ -129,3 +133,76 @@ impl Highlighter {
#[inline] #[inline]
pub fn abort() { INCR.fetch_add(1, Ordering::Relaxed); } pub fn abort() { INCR.fetch_add(1, Ordering::Relaxed); }
} }
impl Highlighter {
// Copy from https://github.com/sharkdp/bat/blob/master/src/terminal.rs
fn to_ansi_color(color: highlighting::Color) -> Option<ratatui::style::Color> {
if color.a == 0 {
// Themes can specify one of the user-configurable terminal colors by
// encoding them as #RRGGBBAA with AA set to 00 (transparent) and RR set
// to the 8-bit color palette number. The built-in themes ansi, base16,
// and base16-256 use this.
Some(match color.r {
// For the first 8 colors, use the Color enum to produce ANSI escape
// sequences using codes 30-37 (foreground) and 40-47 (background).
// For example, red foreground is \x1b[31m. This works on terminals
// without 256-color support.
0x00 => ratatui::style::Color::Black,
0x01 => ratatui::style::Color::Red,
0x02 => ratatui::style::Color::Green,
0x03 => ratatui::style::Color::Yellow,
0x04 => ratatui::style::Color::Blue,
0x05 => ratatui::style::Color::Magenta,
0x06 => ratatui::style::Color::Cyan,
0x07 => ratatui::style::Color::White,
// For all other colors, use Fixed to produce escape sequences using
// codes 38;5 (foreground) and 48;5 (background). For example,
// bright red foreground is \x1b[38;5;9m. This only works on
// terminals with 256-color support.
//
// TODO: When ansi_term adds support for bright variants using codes
// 90-97 (foreground) and 100-107 (background), we should use those
// for values 0x08 to 0x0f and only use Fixed for 0x10 to 0xff.
n => ratatui::style::Color::Indexed(n),
})
} else if color.a == 1 {
// Themes can specify the terminal's default foreground/background color
// (i.e. no escape sequence) using the encoding #RRGGBBAA with AA set to
// 01. The built-in theme ansi uses this.
None
} else {
Some(ratatui::style::Color::Rgb(color.r, color.g, color.b))
}
}
fn to_line_widget(regions: Vec<(highlighting::Style, &str)>) -> Line<'static> {
let indent = " ".repeat(PREVIEW.tab_size as usize);
let spans: Vec<_> = regions
.into_iter()
.map(|(style, s)| {
let mut modifier = ratatui::style::Modifier::empty();
if style.font_style.contains(highlighting::FontStyle::BOLD) {
modifier |= ratatui::style::Modifier::BOLD;
}
if style.font_style.contains(highlighting::FontStyle::ITALIC) {
modifier |= ratatui::style::Modifier::ITALIC;
}
if style.font_style.contains(highlighting::FontStyle::UNDERLINE) {
modifier |= ratatui::style::Modifier::UNDERLINED;
}
Span {
content: s.replace('\t', &indent).into(),
style: ratatui::style::Style {
fg: Self::to_ansi_color(style.foreground),
// bg: Self::to_ansi_color(style.background),
add_modifier: modifier,
..Default::default()
},
}
})
.collect();
Line::from(spans)
}
}

View file

@ -1,6 +1,4 @@
use ansi_to_tui::IntoText; use mlua::{AnyUserData, IntoLuaMulti, Lua, Table, Value};
use mlua::{AnyUserData, ExternalResult, IntoLuaMulti, Lua, Table, Value};
use yazi_config::PREVIEW;
use yazi_shared::{emit, event::Exec, Layer, PeekError}; use yazi_shared::{emit, event::Exec, Layer, PeekError};
use super::Utils; use super::Utils;
@ -38,17 +36,13 @@ impl Utils {
let area: RectRef = t.get("area")?; let area: RectRef = t.get("area")?;
let mut lock = PreviewLock::try_from(t)?; let mut lock = PreviewLock::try_from(t)?;
let s = match Highlighter::new(&lock.url).highlight(lock.skip, area.height as usize).await { let text =
Ok(s) => s.replace('\t', &" ".repeat(PREVIEW.tab_size as usize)), match Highlighter::new(&lock.url).highlight(lock.skip, area.height as usize).await {
Err(PeekError::Exceed(max)) => return (false, max).into_lua_multi(lua), Ok(text) => text,
Err(_) => return (false, Value::Nil).into_lua_multi(lua), Err(PeekError::Exceed(max)) => return (false, max).into_lua_multi(lua),
}; Err(_) => return (false, Value::Nil).into_lua_multi(lua),
};
lock.data = vec![Box::new(Paragraph { lock.data = vec![Box::new(Paragraph { area: *area, text, ..Default::default() })];
area: *area,
text: s.into_text().into_lua_err()?,
..Default::default()
})];
emit!(Call(Exec::call("preview", vec![]).with_data(lock).vec(), Layer::Manager)); emit!(Call(Exec::call("preview", vec![]).with_data(lock).vec(), Layer::Manager));
(true, Value::Nil).into_lua_multi(lua) (true, Value::Nil).into_lua_multi(lua)