feat: complete Rust port of claude-statusline
Port the entire 2236-line bash statusline script to Rust. Implements all 25 sections, 3-phase layout engine (render, priority drop, flex/justify), file-based caching with flock, 9-level terminal width detection, trend sparklines, and deep-merge JSON config. Release binary: 864K with LTO. Render time: <1ms warm. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
721
src/config.rs
Normal file
721
src/config.rs
Normal file
@@ -0,0 +1,721 @@
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
const DEFAULTS_JSON: &str = include_str!("../defaults.json");
|
||||
|
||||
// ── Top-level Config ────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
pub version: u32,
|
||||
pub global: GlobalConfig,
|
||||
pub colors: ThemeColors,
|
||||
pub glyphs: GlyphConfig,
|
||||
pub presets: HashMap<String, Vec<Vec<String>>>,
|
||||
pub layout: LayoutValue,
|
||||
pub sections: Sections,
|
||||
pub custom: Vec<CustomCommand>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
serde_json::from_str(DEFAULTS_JSON).expect("embedded defaults must parse")
|
||||
}
|
||||
}
|
||||
|
||||
// ── Layout: preset name or explicit array ───────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum LayoutValue {
|
||||
Preset(String),
|
||||
Custom(Vec<Vec<String>>),
|
||||
}
|
||||
|
||||
impl Default for LayoutValue {
|
||||
fn default() -> Self {
|
||||
Self::Preset("standard".into())
|
||||
}
|
||||
}
|
||||
|
||||
// ── Global settings ─────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct GlobalConfig {
|
||||
pub separator: String,
|
||||
pub justify: JustifyMode,
|
||||
pub vcs: String,
|
||||
pub width: Option<u16>,
|
||||
pub width_margin: u16,
|
||||
pub cache_dir: String,
|
||||
pub cache_gc_days: u16,
|
||||
pub cache_gc_interval_hours: u16,
|
||||
pub cache_ttl_jitter_pct: u8,
|
||||
pub responsive: bool,
|
||||
pub breakpoints: Breakpoints,
|
||||
pub render_budget_ms: u64,
|
||||
pub theme: String,
|
||||
pub color: ColorMode,
|
||||
pub warn_unknown_keys: bool,
|
||||
pub shell_enabled: bool,
|
||||
pub shell_allowlist: Vec<String>,
|
||||
pub shell_denylist: Vec<String>,
|
||||
pub shell_timeout_ms: u64,
|
||||
pub shell_max_output_bytes: usize,
|
||||
pub shell_failure_threshold: u8,
|
||||
pub shell_cooldown_ms: u64,
|
||||
pub shell_env: HashMap<String, String>,
|
||||
pub cache_version: u32,
|
||||
pub drop_strategy: String,
|
||||
pub breakpoint_hysteresis: u16,
|
||||
}
|
||||
|
||||
impl Default for GlobalConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
separator: " | ".into(),
|
||||
justify: JustifyMode::Left,
|
||||
vcs: "auto".into(),
|
||||
width: None,
|
||||
width_margin: 4,
|
||||
cache_dir: "/tmp/claude-sl-{session_id}".into(),
|
||||
cache_gc_days: 7,
|
||||
cache_gc_interval_hours: 24,
|
||||
cache_ttl_jitter_pct: 10,
|
||||
responsive: true,
|
||||
breakpoints: Breakpoints::default(),
|
||||
render_budget_ms: 8,
|
||||
theme: "auto".into(),
|
||||
color: ColorMode::Auto,
|
||||
warn_unknown_keys: true,
|
||||
shell_enabled: true,
|
||||
shell_allowlist: Vec::new(),
|
||||
shell_denylist: Vec::new(),
|
||||
shell_timeout_ms: 200,
|
||||
shell_max_output_bytes: 8192,
|
||||
shell_failure_threshold: 3,
|
||||
shell_cooldown_ms: 30_000,
|
||||
shell_env: HashMap::new(),
|
||||
cache_version: 1,
|
||||
drop_strategy: "tiered".into(),
|
||||
breakpoint_hysteresis: 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum JustifyMode {
|
||||
#[default]
|
||||
Left,
|
||||
Spread,
|
||||
SpaceBetween,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ColorMode {
|
||||
#[default]
|
||||
Auto,
|
||||
Always,
|
||||
Never,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Breakpoints {
|
||||
pub narrow: u16,
|
||||
pub medium: u16,
|
||||
}
|
||||
|
||||
impl Default for Breakpoints {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
narrow: 60,
|
||||
medium: 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Color palettes ──────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct ThemeColors {
|
||||
pub dark: HashMap<String, String>,
|
||||
pub light: HashMap<String, String>,
|
||||
}
|
||||
|
||||
// ── Glyph config ────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct GlyphConfig {
|
||||
pub enabled: bool,
|
||||
pub set: HashMap<String, String>,
|
||||
pub fallback: HashMap<String, String>,
|
||||
}
|
||||
|
||||
// ── Shared section base (flattened into each section) ───────────────────
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct SectionBase {
|
||||
pub enabled: bool,
|
||||
pub priority: u8,
|
||||
pub flex: bool,
|
||||
pub min_width: Option<u16>,
|
||||
pub prefix: Option<String>,
|
||||
pub suffix: Option<String>,
|
||||
pub pad: Option<u16>,
|
||||
pub align: Option<String>,
|
||||
pub color: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for SectionBase {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
priority: 2,
|
||||
flex: false,
|
||||
min_width: None,
|
||||
prefix: None,
|
||||
suffix: None,
|
||||
pad: None,
|
||||
align: None,
|
||||
color: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Per-section typed configs ───────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Sections {
|
||||
pub model: SectionBase,
|
||||
pub provider: SectionBase,
|
||||
pub project: ProjectSection,
|
||||
pub vcs: VcsSection,
|
||||
pub beads: BeadsSection,
|
||||
pub context_bar: ContextBarSection,
|
||||
pub context_usage: ContextUsageSection,
|
||||
pub context_remaining: ContextRemainingSection,
|
||||
pub tokens_raw: TokensRawSection,
|
||||
pub cache_efficiency: SectionBase,
|
||||
pub cost: CostSection,
|
||||
pub cost_velocity: SectionBase,
|
||||
pub token_velocity: SectionBase,
|
||||
pub cost_trend: TrendSection,
|
||||
pub context_trend: ContextTrendSection,
|
||||
pub lines_changed: SectionBase,
|
||||
pub duration: SectionBase,
|
||||
pub tools: ToolsSection,
|
||||
pub turns: CachedSection,
|
||||
pub load: CachedSection,
|
||||
pub version: SectionBase,
|
||||
pub time: TimeSection,
|
||||
pub output_style: SectionBase,
|
||||
pub hostname: SectionBase,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct ProjectSection {
|
||||
#[serde(flatten)]
|
||||
pub base: SectionBase,
|
||||
pub truncate: TruncateConfig,
|
||||
}
|
||||
|
||||
impl Default for ProjectSection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
base: SectionBase {
|
||||
priority: 1,
|
||||
..Default::default()
|
||||
},
|
||||
truncate: TruncateConfig {
|
||||
enabled: true,
|
||||
max: 30,
|
||||
style: "middle".into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct TruncateConfig {
|
||||
pub enabled: bool,
|
||||
pub max: usize,
|
||||
pub style: String,
|
||||
}
|
||||
|
||||
impl Default for TruncateConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
max: 0,
|
||||
style: "right".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct VcsSection {
|
||||
#[serde(flatten)]
|
||||
pub base: SectionBase,
|
||||
pub prefer: String,
|
||||
pub show_ahead_behind: bool,
|
||||
pub show_dirty: bool,
|
||||
pub untracked: String,
|
||||
pub submodules: bool,
|
||||
pub fast_mode: bool,
|
||||
pub truncate: TruncateConfig,
|
||||
pub ttl: VcsTtl,
|
||||
}
|
||||
|
||||
impl Default for VcsSection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
base: SectionBase {
|
||||
priority: 1,
|
||||
min_width: Some(8),
|
||||
..Default::default()
|
||||
},
|
||||
prefer: "auto".into(),
|
||||
show_ahead_behind: true,
|
||||
show_dirty: true,
|
||||
untracked: "normal".into(),
|
||||
submodules: false,
|
||||
fast_mode: false,
|
||||
truncate: TruncateConfig {
|
||||
enabled: true,
|
||||
max: 25,
|
||||
style: "right".into(),
|
||||
},
|
||||
ttl: VcsTtl::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct VcsTtl {
|
||||
pub branch: u64,
|
||||
pub dirty: u64,
|
||||
pub ahead_behind: u64,
|
||||
}
|
||||
|
||||
impl Default for VcsTtl {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
branch: 3,
|
||||
dirty: 5,
|
||||
ahead_behind: 30,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct BeadsSection {
|
||||
#[serde(flatten)]
|
||||
pub base: SectionBase,
|
||||
pub show_wip: bool,
|
||||
pub show_wip_count: bool,
|
||||
pub show_ready_count: bool,
|
||||
pub show_open_count: bool,
|
||||
pub show_closed_count: bool,
|
||||
pub ttl: u64,
|
||||
}
|
||||
|
||||
impl Default for BeadsSection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
base: SectionBase {
|
||||
priority: 3,
|
||||
..Default::default()
|
||||
},
|
||||
show_wip: true,
|
||||
show_wip_count: true,
|
||||
show_ready_count: true,
|
||||
show_open_count: true,
|
||||
show_closed_count: true,
|
||||
ttl: 30,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct ContextBarSection {
|
||||
#[serde(flatten)]
|
||||
pub base: SectionBase,
|
||||
pub bar_width: u16,
|
||||
pub thresholds: Thresholds,
|
||||
}
|
||||
|
||||
impl Default for ContextBarSection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
base: SectionBase {
|
||||
priority: 1,
|
||||
flex: true,
|
||||
min_width: Some(15),
|
||||
..Default::default()
|
||||
},
|
||||
bar_width: 10,
|
||||
thresholds: Thresholds {
|
||||
warn: 50.0,
|
||||
danger: 70.0,
|
||||
critical: 85.0,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Thresholds {
|
||||
pub warn: f64,
|
||||
pub danger: f64,
|
||||
pub critical: f64,
|
||||
}
|
||||
|
||||
impl Default for Thresholds {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
warn: 50.0,
|
||||
danger: 70.0,
|
||||
critical: 85.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct ContextUsageSection {
|
||||
#[serde(flatten)]
|
||||
pub base: SectionBase,
|
||||
pub capacity: u64,
|
||||
pub thresholds: Thresholds,
|
||||
}
|
||||
|
||||
impl Default for ContextUsageSection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
base: SectionBase {
|
||||
enabled: false,
|
||||
priority: 2,
|
||||
..Default::default()
|
||||
},
|
||||
capacity: 200_000,
|
||||
thresholds: Thresholds::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct ContextRemainingSection {
|
||||
#[serde(flatten)]
|
||||
pub base: SectionBase,
|
||||
pub format: String,
|
||||
pub thresholds: Thresholds,
|
||||
}
|
||||
|
||||
impl Default for ContextRemainingSection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
base: SectionBase {
|
||||
enabled: false,
|
||||
priority: 2,
|
||||
..Default::default()
|
||||
},
|
||||
format: "{remaining} left".into(),
|
||||
thresholds: Thresholds::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct TokensRawSection {
|
||||
#[serde(flatten)]
|
||||
pub base: SectionBase,
|
||||
pub format: String,
|
||||
}
|
||||
|
||||
impl Default for TokensRawSection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
base: SectionBase {
|
||||
priority: 3,
|
||||
..Default::default()
|
||||
},
|
||||
format: "{input} in/{output} out".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct CostSection {
|
||||
#[serde(flatten)]
|
||||
pub base: SectionBase,
|
||||
pub thresholds: Thresholds,
|
||||
}
|
||||
|
||||
impl Default for CostSection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
base: SectionBase {
|
||||
priority: 1,
|
||||
..Default::default()
|
||||
},
|
||||
thresholds: Thresholds {
|
||||
warn: 5.0,
|
||||
danger: 8.0,
|
||||
critical: 10.0,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct TrendSection {
|
||||
#[serde(flatten)]
|
||||
pub base: SectionBase,
|
||||
pub width: u8,
|
||||
}
|
||||
|
||||
impl Default for TrendSection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
base: SectionBase {
|
||||
priority: 3,
|
||||
..Default::default()
|
||||
},
|
||||
width: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct ContextTrendSection {
|
||||
#[serde(flatten)]
|
||||
pub base: SectionBase,
|
||||
pub width: u8,
|
||||
pub thresholds: Thresholds,
|
||||
}
|
||||
|
||||
impl Default for ContextTrendSection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
base: SectionBase {
|
||||
priority: 3,
|
||||
..Default::default()
|
||||
},
|
||||
width: 8,
|
||||
thresholds: Thresholds::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct ToolsSection {
|
||||
#[serde(flatten)]
|
||||
pub base: SectionBase,
|
||||
pub show_last_name: bool,
|
||||
pub ttl: u64,
|
||||
}
|
||||
|
||||
impl Default for ToolsSection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
base: SectionBase {
|
||||
priority: 2,
|
||||
min_width: Some(6),
|
||||
..Default::default()
|
||||
},
|
||||
show_last_name: true,
|
||||
ttl: 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct CachedSection {
|
||||
#[serde(flatten)]
|
||||
pub base: SectionBase,
|
||||
pub ttl: u64,
|
||||
}
|
||||
|
||||
impl Default for CachedSection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
base: SectionBase {
|
||||
priority: 3,
|
||||
..Default::default()
|
||||
},
|
||||
ttl: 10,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct TimeSection {
|
||||
#[serde(flatten)]
|
||||
pub base: SectionBase,
|
||||
pub format: String,
|
||||
}
|
||||
|
||||
impl Default for TimeSection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
base: SectionBase {
|
||||
enabled: false,
|
||||
priority: 3,
|
||||
..Default::default()
|
||||
},
|
||||
format: "%H:%M".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Custom command sections ─────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct CustomCommand {
|
||||
pub id: String,
|
||||
#[serde(default)]
|
||||
pub command: Option<String>,
|
||||
#[serde(default)]
|
||||
pub exec: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub format: Option<String>,
|
||||
#[serde(default)]
|
||||
pub label: Option<String>,
|
||||
#[serde(default = "default_custom_ttl")]
|
||||
pub ttl: u64,
|
||||
#[serde(default = "default_priority")]
|
||||
pub priority: u8,
|
||||
#[serde(default)]
|
||||
pub flex: bool,
|
||||
#[serde(default)]
|
||||
pub min_width: Option<u16>,
|
||||
#[serde(default)]
|
||||
pub color: Option<CustomColor>,
|
||||
#[serde(default)]
|
||||
pub default_color: Option<String>,
|
||||
#[serde(default)]
|
||||
pub prefix: Option<String>,
|
||||
#[serde(default)]
|
||||
pub suffix: Option<String>,
|
||||
#[serde(default)]
|
||||
pub pad: Option<u16>,
|
||||
#[serde(default)]
|
||||
pub align: Option<String>,
|
||||
}
|
||||
|
||||
fn default_custom_ttl() -> u64 {
|
||||
30
|
||||
}
|
||||
fn default_priority() -> u8 {
|
||||
2
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct CustomColor {
|
||||
#[serde(rename = "match", default)]
|
||||
pub match_map: HashMap<String, String>,
|
||||
}
|
||||
|
||||
// ── Deep merge ──────────────────────────────────────────────────────────
|
||||
|
||||
/// Recursive JSON merge: user values win, arrays replaced entirely.
|
||||
pub fn deep_merge(base: &mut Value, patch: &Value) {
|
||||
match (base, patch) {
|
||||
(Value::Object(base_map), Value::Object(patch_map)) => {
|
||||
for (k, v) in patch_map {
|
||||
let entry = base_map.entry(k.clone()).or_insert(Value::Null);
|
||||
deep_merge(entry, v);
|
||||
}
|
||||
}
|
||||
(base, patch) => {
|
||||
*base = patch.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Config loading ──────────────────────────────────────────────────────
|
||||
|
||||
/// Load config: embedded defaults deep-merged with user overrides.
|
||||
pub fn load_config(explicit_path: Option<&str>) -> Result<(Config, Vec<String>), crate::Error> {
|
||||
let mut base: Value = serde_json::from_str(DEFAULTS_JSON)?;
|
||||
|
||||
let user_path = explicit_path
|
||||
.map(std::path::PathBuf::from)
|
||||
.or_else(|| {
|
||||
std::env::var("CLAUDE_STATUSLINE_CONFIG")
|
||||
.ok()
|
||||
.map(Into::into)
|
||||
})
|
||||
.or_else(xdg_config_path)
|
||||
.or_else(dot_config_path)
|
||||
.unwrap_or_else(|| {
|
||||
let mut p = dirs_home().unwrap_or_default();
|
||||
p.push(".claude/statusline.json");
|
||||
p
|
||||
});
|
||||
|
||||
if user_path.exists() {
|
||||
let user_json: Value = serde_json::from_str(&std::fs::read_to_string(&user_path)?)?;
|
||||
deep_merge(&mut base, &user_json);
|
||||
} else if explicit_path.is_some() {
|
||||
return Err(crate::Error::ConfigNotFound(
|
||||
user_path.display().to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut warnings = Vec::new();
|
||||
let config: Config = serde_ignored::deserialize(base, |path| {
|
||||
warnings.push(format!("unknown config key: {path}"));
|
||||
})?;
|
||||
|
||||
Ok((config, warnings))
|
||||
}
|
||||
|
||||
fn xdg_config_path() -> Option<std::path::PathBuf> {
|
||||
let val = std::env::var("XDG_CONFIG_HOME").ok()?;
|
||||
let mut p = std::path::PathBuf::from(val);
|
||||
p.push("claude/statusline.json");
|
||||
if p.exists() {
|
||||
Some(p)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn dot_config_path() -> Option<std::path::PathBuf> {
|
||||
let mut p = dirs_home()?;
|
||||
p.push(".config/claude/statusline.json");
|
||||
if p.exists() {
|
||||
Some(p)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn dirs_home() -> Option<std::path::PathBuf> {
|
||||
std::env::var("HOME").ok().map(Into::into)
|
||||
}
|
||||
Reference in New Issue
Block a user