linesmith_core/segments/
version.rs1use std::collections::BTreeMap;
6
7use super::{RenderContext, RenderResult, RenderedSegment, Segment, SegmentDefaults};
8use crate::data_context::DataContext;
9use crate::theme::Role;
10
11pub const ID: &str = "version";
12
13const PRIORITY: u8 = 160;
15
16const DEFAULT_PREFIX: &str = "v";
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub(crate) struct Config {
20 pub(crate) prefix: String,
21}
22
23impl Default for Config {
24 fn default() -> Self {
25 Self {
26 prefix: DEFAULT_PREFIX.to_string(),
27 }
28 }
29}
30
31#[derive(Default)]
32pub struct VersionSegment {
33 cfg: Config,
34}
35
36impl VersionSegment {
37 pub fn from_extras(
41 extras: &BTreeMap<String, toml::Value>,
42 warn: &mut impl FnMut(&str),
43 ) -> Self {
44 let mut cfg = Config::default();
45 if let Some(v) = extras.get("prefix") {
46 match v.as_str() {
47 Some(s) => cfg.prefix = s.to_string(),
48 None => warn(&format!(
49 "segments.{ID}.prefix: expected a string; ignoring"
50 )),
51 }
52 }
53 Self { cfg }
54 }
55}
56
57impl Segment for VersionSegment {
58 fn render(&self, ctx: &DataContext, _rc: &RenderContext) -> RenderResult {
59 let Some(v) = ctx.status.version.as_deref() else {
60 crate::lsm_debug!("version: status.version absent; hiding");
61 return Ok(None);
62 };
63 let text = if self.cfg.prefix.is_empty() {
64 v.to_string()
65 } else {
66 format!("{}{v}", self.cfg.prefix)
67 };
68 Ok(Some(RenderedSegment::new(text).with_role(Role::Muted)))
69 }
70
71 fn defaults(&self) -> SegmentDefaults {
72 SegmentDefaults::with_priority(PRIORITY)
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use crate::input::{ModelInfo, StatusContext, Tool, WorkspaceInfo};
80 use std::path::PathBuf;
81 use std::sync::Arc;
82
83 fn rc() -> RenderContext {
84 RenderContext::new(80)
85 }
86
87 fn ctx(version: Option<String>) -> DataContext {
88 DataContext::new(StatusContext {
89 tool: Tool::ClaudeCode,
90 model: Some(ModelInfo {
91 display_name: "X".into(),
92 }),
93 workspace: Some(WorkspaceInfo {
94 project_dir: PathBuf::from("/repo"),
95 git_worktree: None,
96 }),
97 context_window: None,
98 cost: None,
99 effort: None,
100 vim: None,
101 output_style: None,
102 agent_name: None,
103 version,
104 raw: Arc::new(serde_json::Value::Null),
105 })
106 }
107
108 #[test]
109 fn renders_with_default_v_prefix() {
110 assert_eq!(
111 VersionSegment::default()
112 .render(&ctx(Some("2.1.90".into())), &rc())
113 .unwrap(),
114 Some(RenderedSegment::new("v2.1.90").with_role(Role::Muted))
115 );
116 }
117
118 #[test]
119 fn empty_prefix_emits_raw_version() {
120 let mut extras = BTreeMap::new();
124 extras.insert("prefix".to_string(), toml::Value::String(String::new()));
125 let seg = VersionSegment::from_extras(&extras, &mut |_| {});
126 assert_eq!(
127 seg.render(&ctx(Some("2.1.90".into())), &rc()).unwrap(),
128 Some(RenderedSegment::new("2.1.90").with_role(Role::Muted))
129 );
130 }
131
132 #[test]
133 fn custom_prefix_passes_through() {
134 let mut extras = BTreeMap::new();
135 extras.insert("prefix".to_string(), toml::Value::String("CC ".to_string()));
136 let seg = VersionSegment::from_extras(&extras, &mut |_| {});
137 assert_eq!(
138 seg.render(&ctx(Some("2.1.90".into())), &rc()).unwrap(),
139 Some(RenderedSegment::new("CC 2.1.90").with_role(Role::Muted))
140 );
141 }
142
143 #[test]
144 fn hidden_when_absent() {
145 assert_eq!(
146 VersionSegment::default().render(&ctx(None), &rc()).unwrap(),
147 None
148 );
149 }
150
151 #[test]
152 fn from_extras_warns_on_non_string_prefix_and_keeps_default() {
153 let mut extras = BTreeMap::new();
154 extras.insert("prefix".to_string(), toml::Value::Integer(1));
155 let mut warnings = Vec::new();
156 let seg = VersionSegment::from_extras(&extras, &mut |m| warnings.push(m.to_string()));
157 assert_eq!(seg.cfg.prefix, DEFAULT_PREFIX);
158 assert_eq!(warnings.len(), 1);
159 assert!(warnings[0].contains("segments.version.prefix"));
160 }
161
162 #[test]
163 fn defaults_use_expected_priority() {
164 assert_eq!(VersionSegment::default().defaults().priority, PRIORITY);
165 }
166}