diff --git a/CHANGELOG.md b/CHANGELOG.md index b972a53c8a..8fd487e135 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Added +* support loading custom syntax highlighting themes from a file [[@acuteenvy](https://github.com/acuteenvy)] ([#2565](https://github.com/gitui-org/gitui/pull/2565)) * Select syntax highlighting theme out of the defaults from syntect [[@vasilismanol](https://github.com/vasilismanol)] ([#1931](https://github.com/extrawurst/gitui/issues/1931)) * new command-line option to override the default log file path (`--logfile`) [[@acuteenvy](https://github.com/acuteenvy)] ([#2539](https://github.com/gitui-org/gitui/pull/2539)) diff --git a/Cargo.lock b/Cargo.lock index 6904ff988d..f15e14f983 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2657,6 +2657,19 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "plist" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" +dependencies = [ + "base64", + "indexmap", + "quick-xml", + "serde", + "time", +] + [[package]] name = "poly1305" version = "0.8.0" @@ -2748,6 +2761,15 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "quick-xml" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.38" @@ -3379,6 +3401,7 @@ dependencies = [ "fnv", "once_cell", "onig", + "plist", "regex-syntax", "serde", "serde_derive", diff --git a/Cargo.toml b/Cargo.toml index a1f93e07ca..75bb1cd9c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ syntect = { version = "5.2", default-features = false, features = [ "parsing", "default-syntaxes", "default-themes", + "plist-load", "html", ] } tui-textarea = "0.7" diff --git a/src/args.rs b/src/args.rs index 444b4b1f92..8ef3aeb42f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -49,7 +49,9 @@ pub fn process_cmdline() -> Result { .get_one::("theme") .map_or_else(|| PathBuf::from("theme.ron"), PathBuf::from); - let theme = get_app_config_path()?.join(arg_theme); + let confpath = get_app_config_path()?; + fs::create_dir_all(&confpath)?; + let theme = confpath.join(arg_theme); let notify_watcher: bool = *arg_matches.get_one("watcher").unwrap_or(&false); @@ -166,7 +168,6 @@ pub fn get_app_config_path() -> Result { .ok_or_else(|| anyhow!("failed to find os config dir."))?; path.push("gitui"); - fs::create_dir_all(&path)?; Ok(path) } diff --git a/src/keys/key_config.rs b/src/keys/key_config.rs index 9ba0c79131..9cd4eb73f2 100644 --- a/src/keys/key_config.rs +++ b/src/keys/key_config.rs @@ -136,6 +136,7 @@ mod tests { #[test] fn test_symbolic_links() { let app_home = get_app_config_path().unwrap(); + fs::create_dir_all(&app_home).unwrap(); // save current config let original_key_list_path = app_home.join(KEY_LIST_FILENAME); let renamed_key_list = if original_key_list_path.exists() { diff --git a/src/ui/syntax_text.rs b/src/ui/syntax_text.rs index e0cd5bf6c5..8d758f20cc 100644 --- a/src/ui/syntax_text.rs +++ b/src/ui/syntax_text.rs @@ -2,7 +2,7 @@ use asyncgit::{ asyncjob::{AsyncJob, RunParams}, ProgressPercent, }; -use once_cell::sync::Lazy; +use once_cell::sync::{Lazy, OnceCell}; use ratatui::text::{Line, Span}; use scopetime::scope_time; use std::{ @@ -14,7 +14,7 @@ use std::{ use syntect::{ highlighting::{ FontStyle, HighlightState, Highlighter, - RangedHighlightIterator, Style, ThemeSet, + RangedHighlightIterator, Style, Theme, ThemeSet, }, parsing::{ParseState, ScopeStack, SyntaxSet}, }; @@ -35,7 +35,7 @@ pub struct SyntaxText { static SYNTAX_SET: Lazy = Lazy::new(two_face::syntax::extra_no_newlines); -static THEME_SET: Lazy = Lazy::new(ThemeSet::load_defaults); +static THEME: OnceCell = OnceCell::new(); pub struct AsyncProgressBuffer { current: usize, @@ -89,13 +89,25 @@ impl SyntaxText { ParseState::new(syntax) }; - let theme = - THEME_SET.themes.get(syntax).unwrap_or_else(|| { - log::error!("The syntax theme:\"{}\" cannot be found. Using default theme:\"{}\" instead.", syntax, DEFAULT_SYNTAX_THEME); - &THEME_SET.themes[DEFAULT_SYNTAX_THEME] - }); - let highlighter = Highlighter::new(theme); + let theme = THEME.get_or_try_init(|| -> Result { + let theme_path = crate::args::get_app_config_path() + .map_err(|e| asyncgit::Error::Generic(e.to_string()))?.join(format!("{syntax}.tmTheme")); + + match ThemeSet::get_theme(&theme_path) { + Ok(t) => return Ok(t), + Err(e) => log::info!("could not load '{}': {e}, trying from the set of default themes", theme_path.display()), + } + let mut theme_set = ThemeSet::load_defaults(); + if let Some(t) = theme_set.themes.remove(syntax) { + return Ok(t); + } + + log::error!("the syntax theme '{syntax}' cannot be found. Using default theme ('{DEFAULT_SYNTAX_THEME}') instead"); + Ok(theme_set.themes.remove(DEFAULT_SYNTAX_THEME).expect("the default theme should be there")) + })?; + + let highlighter = Highlighter::new(theme); let mut syntax_lines: Vec = Vec::new(); let mut highlight_state =