diff --git a/Cargo.lock b/Cargo.lock index bca018f7..4f77f19c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -817,7 +817,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hl" -version = "0.20.0-beta.11" +version = "0.20.0-beta.11.1" dependencies = [ "anyhow", "atoi", diff --git a/Cargo.toml b/Cargo.toml index 479fc447..3da3838a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ categories = ["command-line-utilities"] description = "Utility for viewing json-formatted log files." keywords = ["cli", "human", "log"] name = "hl" -version = "0.20.0-beta.11" +version = "0.20.0-beta.11.1" edition = "2021" build = "build.rs" diff --git a/benches/theme.rs b/benches/theme.rs index 2a387abf..f8f9e050 100644 --- a/benches/theme.rs +++ b/benches/theme.rs @@ -97,6 +97,7 @@ fn benchmark(c: &mut Criterion) { } .into(), levels: HashMap::new(), + indicators: themecfg::IndicatorPack::default(), }); let fields = vec![ (b"key1", b"value1"), diff --git a/etc/defaults/config.yaml b/etc/defaults/config.yaml index 26e2ce90..fce5a0ac 100644 --- a/etc/defaults/config.yaml +++ b/etc/defaults/config.yaml @@ -10,7 +10,16 @@ fields: # Configuration of the predefined set of fields. predefined: time: - names: [ts, TS, time, TIME, Time, _SOURCE_REALTIME_TIMESTAMP, __REALTIME_TIMESTAMP] + names: + [ + ts, + TS, + time, + TIME, + Time, + _SOURCE_REALTIME_TIMESTAMP, + __REALTIME_TIMESTAMP, + ] logger: names: [logger, LOGGER, Logger] level: diff --git a/etc/defaults/themes/universal.yaml b/etc/defaults/themes/universal.yaml index 5a397230..b1433c4d 100644 --- a/etc/defaults/themes/universal.yaml +++ b/etc/defaults/themes/universal.yaml @@ -47,3 +47,12 @@ levels: level: foreground: bright-red modes: [reverse] +indicators: + sync: + synced: + text: ' ' + failed: + text: '!' + inner: + style: + foreground: 'yellow' diff --git a/src/app.rs b/src/app.rs index b47c8b65..b4c6134d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -427,6 +427,7 @@ impl App { let mut window = BTreeMap::::new(); let mut last_ts: Option = None; + let mut prev_ts: Option = None; let mut mem_usage = 0; let mem_limit = n * usize::from(self.options.buffer_size); @@ -437,7 +438,14 @@ impl App { break; } if let Some(entry) = window.pop_first() { + let sync_indicator = if prev_ts.map(|ts| ts <= entry.0.0).unwrap_or(true) { + &self.options.theme.indicators.sync.synced + } else { + &self.options.theme.indicators.sync.failed + }; + prev_ts = Some(entry.0.0); mem_usage -= entry.1.1.end - entry.1.1.start; + output.write_all(sync_indicator.value.as_bytes())?; output.write_all(&entry.1.0[entry.1.1.clone()])?; } } diff --git a/src/theme.rs b/src/theme.rs index c1b37286..0f40222f 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -31,6 +31,7 @@ pub trait StylingPush> { pub struct Theme { packs: EnumMap, default: StylePack, + pub indicators: IndicatorPack, } impl Theme { @@ -38,6 +39,7 @@ impl Theme { Self { packs: EnumMap::default(), default: StylePack::default(), + indicators: IndicatorPack::default(), } } @@ -81,7 +83,11 @@ impl> From for Theme { for (level, pack) in &s.levels { packs[*level] = StylePack::load(&s.elements.clone().merged(pack.clone())); } - Self { default, packs } + Self { + default, + packs, + indicators: IndicatorPack::from(&s.indicators), + } } } @@ -96,6 +102,17 @@ impl Style { buf.extend_from_slice(self.0.data()) } + #[inline(always)] + pub fn with, F: FnOnce(&mut B)>(&self, buf: &mut B, f: F) { + if self.0.data().is_empty() { + f(buf) + } else { + buf.extend_from_slice(self.0.data()); + f(buf); + buf.extend_from_slice(Self::reset().0.data()); + } + } + pub fn reset() -> Self { Sequence::reset().into() } @@ -262,6 +279,67 @@ impl StylePack { // --- +#[derive(Default)] +pub struct IndicatorPack { + pub sync: SyncIndicatorPack, +} + +impl From<&themecfg::IndicatorPack> for IndicatorPack { + fn from(indicator: &themecfg::IndicatorPack) -> Self { + Self { + sync: SyncIndicatorPack::from(&indicator.sync), + } + } +} + +// --- + +#[derive(Default)] +pub struct SyncIndicatorPack { + pub synced: Indicator, + pub failed: Indicator, +} + +impl From<&themecfg::SyncIndicatorPack> for SyncIndicatorPack { + fn from(indicator: &themecfg::SyncIndicatorPack) -> Self { + Self { + synced: Indicator::from(&indicator.synced), + failed: Indicator::from(&indicator.failed), + } + } +} + +// --- + +#[derive(Default)] +pub struct Indicator { + pub value: String, +} + +impl From<&themecfg::Indicator> for Indicator { + fn from(indicator: &themecfg::Indicator) -> Self { + let mut buf = Vec::new(); + let os = Style::from(&indicator.outer.style); + let is = Style::from(&indicator.inner.style); + os.apply(&mut buf); + os.with(&mut buf, |buf| { + buf.extend(indicator.outer.prefix.as_bytes()); + is.with(buf, |buf| { + buf.extend(indicator.inner.prefix.as_bytes()); + buf.extend(indicator.text.as_bytes()); + buf.extend(indicator.outer.prefix.as_bytes()); + }); + buf.extend(indicator.outer.suffix.as_bytes()); + }); + + Self { + value: String::from_utf8(buf).unwrap(), + } + } +} + +// --- + #[cfg(test)] mod tests { use super::*; diff --git a/src/themecfg.rs b/src/themecfg.rs index 7f147d53..95e7494c 100644 --- a/src/themecfg.rs +++ b/src/themecfg.rs @@ -25,6 +25,7 @@ use crate::{error::*, level::Level}; pub struct Theme { pub elements: StylePack, pub levels: HashMap, + pub indicators: IndicatorPack, } impl Theme { @@ -308,6 +309,71 @@ impl fmt::Display for RGB { // --- +#[derive(Clone, Debug, Default, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(default)] +pub struct IndicatorPack { + pub sync: SyncIndicatorPack, +} + +// --- + +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct SyncIndicatorPack { + pub synced: Indicator, + pub failed: Indicator, +} + +impl Default for SyncIndicatorPack { + fn default() -> Self { + Self { + synced: Indicator { + outer: IndicatorStyle::default(), + inner: IndicatorStyle::default(), + text: " ".into(), + }, + failed: Indicator { + outer: IndicatorStyle::default(), + inner: IndicatorStyle { + prefix: String::default(), + suffix: String::default(), + style: Style { + modes: Vec::default(), + background: None, + foreground: Some(Color::Plain(PlainColor::Yellow)), + }, + }, + text: "!".into(), + }, + } + } +} + +// --- + +#[derive(Clone, Debug, Default, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(default)] +pub struct Indicator { + pub outer: IndicatorStyle, + pub inner: IndicatorStyle, + pub text: String, +} + +// --- + +#[derive(Clone, Debug, Default, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(default)] +pub struct IndicatorStyle { + pub prefix: String, + pub suffix: String, + pub style: Style, +} + +// --- + #[derive(RustEmbed)] #[folder = "etc/defaults/themes/"] struct Assets;