color.rs
255 lines
| 7.4 KiB
| application/rls-services+xml
|
RustLexer
Simon Sapin
|
r49584 | use crate::ui::formatted; | ||
use crate::ui::plain; | ||||
use format_bytes::write_bytes; | ||||
use hg::config::Config; | ||||
use hg::config::ConfigOrigin; | ||||
use hg::errors::HgError; | ||||
use std::collections::HashMap; | ||||
pub type Effect = u32; | ||||
pub type EffectsMap = HashMap<Vec<u8>, Vec<Effect>>; | ||||
macro_rules! effects { | ||||
($( $name: ident: $value: expr ,)+) => { | ||||
#[allow(non_upper_case_globals)] | ||||
mod effects { | ||||
$( | ||||
pub const $name: super::Effect = $value; | ||||
)+ | ||||
} | ||||
fn effect(name: &[u8]) -> Option<Effect> { | ||||
$( | ||||
if name == stringify!($name).as_bytes() { | ||||
Some(effects::$name) | ||||
} else | ||||
)+ | ||||
{ | ||||
None | ||||
} | ||||
} | ||||
}; | ||||
} | ||||
effects! { | ||||
none: 0, | ||||
black: 30, | ||||
red: 31, | ||||
green: 32, | ||||
yellow: 33, | ||||
blue: 34, | ||||
magenta: 35, | ||||
cyan: 36, | ||||
white: 37, | ||||
bold: 1, | ||||
italic: 3, | ||||
underline: 4, | ||||
inverse: 7, | ||||
dim: 2, | ||||
black_background: 40, | ||||
red_background: 41, | ||||
green_background: 42, | ||||
yellow_background: 43, | ||||
blue_background: 44, | ||||
purple_background: 45, | ||||
cyan_background: 46, | ||||
white_background: 47, | ||||
} | ||||
macro_rules! default_styles { | ||||
($( $key: expr => [$($value: expr),*],)+) => { | ||||
fn default_styles() -> EffectsMap { | ||||
use effects::*; | ||||
let mut map = HashMap::new(); | ||||
$( | ||||
map.insert($key[..].to_owned(), vec![$( $value ),*]); | ||||
)+ | ||||
map | ||||
} | ||||
}; | ||||
} | ||||
default_styles! { | ||||
b"grep.match" => [red, bold], | ||||
b"grep.linenumber" => [green], | ||||
b"grep.rev" => [blue], | ||||
b"grep.sep" => [cyan], | ||||
b"grep.filename" => [magenta], | ||||
b"grep.user" => [magenta], | ||||
b"grep.date" => [magenta], | ||||
b"grep.inserted" => [green, bold], | ||||
b"grep.deleted" => [red, bold], | ||||
b"bookmarks.active" => [green], | ||||
b"branches.active" => [none], | ||||
b"branches.closed" => [black, bold], | ||||
b"branches.current" => [green], | ||||
b"branches.inactive" => [none], | ||||
b"diff.changed" => [white], | ||||
b"diff.deleted" => [red], | ||||
b"diff.deleted.changed" => [red, bold, underline], | ||||
b"diff.deleted.unchanged" => [red], | ||||
b"diff.diffline" => [bold], | ||||
b"diff.extended" => [cyan, bold], | ||||
b"diff.file_a" => [red, bold], | ||||
b"diff.file_b" => [green, bold], | ||||
b"diff.hunk" => [magenta], | ||||
b"diff.inserted" => [green], | ||||
b"diff.inserted.changed" => [green, bold, underline], | ||||
b"diff.inserted.unchanged" => [green], | ||||
b"diff.tab" => [], | ||||
b"diff.trailingwhitespace" => [bold, red_background], | ||||
b"changeset.public" => [], | ||||
b"changeset.draft" => [], | ||||
b"changeset.secret" => [], | ||||
b"diffstat.deleted" => [red], | ||||
b"diffstat.inserted" => [green], | ||||
b"formatvariant.name.mismatchconfig" => [red], | ||||
b"formatvariant.name.mismatchdefault" => [yellow], | ||||
b"formatvariant.name.uptodate" => [green], | ||||
b"formatvariant.repo.mismatchconfig" => [red], | ||||
b"formatvariant.repo.mismatchdefault" => [yellow], | ||||
b"formatvariant.repo.uptodate" => [green], | ||||
b"formatvariant.config.special" => [yellow], | ||||
b"formatvariant.config.default" => [green], | ||||
b"formatvariant.default" => [], | ||||
b"histedit.remaining" => [red, bold], | ||||
b"ui.addremove.added" => [green], | ||||
b"ui.addremove.removed" => [red], | ||||
b"ui.error" => [red], | ||||
b"ui.prompt" => [yellow], | ||||
b"log.changeset" => [yellow], | ||||
b"patchbomb.finalsummary" => [], | ||||
b"patchbomb.from" => [magenta], | ||||
b"patchbomb.to" => [cyan], | ||||
b"patchbomb.subject" => [green], | ||||
b"patchbomb.diffstats" => [], | ||||
b"rebase.rebased" => [blue], | ||||
b"rebase.remaining" => [red, bold], | ||||
b"resolve.resolved" => [green, bold], | ||||
b"resolve.unresolved" => [red, bold], | ||||
b"shelve.age" => [cyan], | ||||
b"shelve.newest" => [green, bold], | ||||
b"shelve.name" => [blue, bold], | ||||
b"status.added" => [green, bold], | ||||
b"status.clean" => [none], | ||||
b"status.copied" => [none], | ||||
b"status.deleted" => [cyan, bold, underline], | ||||
b"status.ignored" => [black, bold], | ||||
b"status.modified" => [blue, bold], | ||||
b"status.removed" => [red, bold], | ||||
b"status.unknown" => [magenta, bold, underline], | ||||
b"tags.normal" => [green], | ||||
b"tags.local" => [black, bold], | ||||
b"upgrade-repo.requirement.preserved" => [cyan], | ||||
b"upgrade-repo.requirement.added" => [green], | ||||
b"upgrade-repo.requirement.removed" => [red], | ||||
} | ||||
fn parse_effect(config_key: &[u8], effect_name: &[u8]) -> Option<Effect> { | ||||
let found = effect(effect_name); | ||||
if found.is_none() { | ||||
// TODO: have some API for warnings | ||||
// TODO: handle IO errors during warnings | ||||
let stderr = std::io::stderr(); | ||||
let _ = write_bytes!( | ||||
&mut stderr.lock(), | ||||
b"ignoring unknown color/effect '{}' \ | ||||
(configured in color.{})\n", | ||||
effect_name, | ||||
config_key, | ||||
); | ||||
} | ||||
found | ||||
} | ||||
fn effects_from_config(config: &Config) -> EffectsMap { | ||||
let mut styles = default_styles(); | ||||
for (key, _value) in config.iter_section(b"color") { | ||||
if !key.contains(&b'.') | ||||
|| key.starts_with(b"color.") | ||||
|| key.starts_with(b"terminfo.") | ||||
{ | ||||
continue; | ||||
} | ||||
// `unwrap` shouldn’t panic since we just got this key from | ||||
// iteration | ||||
let list = config.get_list(b"color", key).unwrap(); | ||||
let parsed = list | ||||
.iter() | ||||
.filter_map(|name| parse_effect(key, name)) | ||||
.collect(); | ||||
styles.insert(key.to_owned(), parsed); | ||||
} | ||||
styles | ||||
} | ||||
enum ColorMode { | ||||
// TODO: support other modes | ||||
Ansi, | ||||
} | ||||
impl ColorMode { | ||||
// Similar to _modesetup in mercurial/color.py | ||||
fn get(config: &Config) -> Result<Option<Self>, HgError> { | ||||
if plain(Some("color")) { | ||||
return Ok(None); | ||||
} | ||||
let enabled_default = b"auto"; | ||||
// `origin` is only used when `!auto`, so its default doesn’t matter | ||||
let (enabled, origin) = config | ||||
.get_with_origin(b"ui", b"color") | ||||
.unwrap_or((enabled_default, &ConfigOrigin::CommandLineColor)); | ||||
if enabled == b"debug" { | ||||
return Err(HgError::unsupported("debug color mode")); | ||||
} | ||||
let auto = enabled == b"auto"; | ||||
let always; | ||||
if !auto { | ||||
let enabled_bool = config.get_bool(b"ui", b"color")?; | ||||
if !enabled_bool { | ||||
return Ok(None); | ||||
} | ||||
always = enabled == b"always" | ||||
|| *origin == ConfigOrigin::CommandLineColor | ||||
} else { | ||||
always = false | ||||
}; | ||||
let formatted = always | ||||
|| (std::env::var_os("TERM").unwrap_or_default() != "dumb" | ||||
&& formatted(config)?); | ||||
let mode_default = b"auto"; | ||||
let mode = config.get(b"color", b"mode").unwrap_or(mode_default); | ||||
if formatted { | ||||
match mode { | ||||
b"ansi" | b"auto" => Ok(Some(ColorMode::Ansi)), | ||||
// TODO: support other modes | ||||
_ => Err(HgError::UnsupportedFeature(format!( | ||||
"color mode {}", | ||||
String::from_utf8_lossy(mode) | ||||
))), | ||||
} | ||||
} else { | ||||
Ok(None) | ||||
} | ||||
} | ||||
} | ||||
pub struct ColorConfig { | ||||
pub styles: EffectsMap, | ||||
} | ||||
impl ColorConfig { | ||||
// Similar to _modesetup in mercurial/color.py | ||||
pub fn new(config: &Config) -> Result<Option<Self>, HgError> { | ||||
Ok(match ColorMode::get(config)? { | ||||
None => None, | ||||
Some(ColorMode::Ansi) => Some(ColorConfig { | ||||
styles: effects_from_config(config), | ||||
}), | ||||
}) | ||||
} | ||||
} | ||||