claude_code_statusline_core/modules/
claude_model.rs1use super::{Module, ModuleConfig};
7use crate::types::context::Context;
8
9pub struct ClaudeModelModule;
31
32impl ClaudeModelModule {
33 pub fn new() -> Self {
35 Self
36 }
37
38 pub fn from_context(_context: &Context) -> Self {
40 Self::new()
41 }
42}
43
44impl Default for ClaudeModelModule {
45 fn default() -> Self {
46 Self::new()
47 }
48}
49
50impl Module for ClaudeModelModule {
51 fn name(&self) -> &str {
52 "claude_model"
53 }
54
55 fn should_display(&self, context: &Context, config: &dyn ModuleConfig) -> bool {
56 if let Some(cfg) = config
58 .as_any()
59 .downcast_ref::<crate::types::config::ClaudeModelConfig>()
60 {
61 if cfg.disabled {
62 return false;
63 }
64 }
65 !context.model_display_name().trim().is_empty()
66 }
67
68 fn render(&self, context: &Context, config: &dyn ModuleConfig) -> String {
69 let model = context.model_display_name();
70
71 let compacted_model = {
74 let s = model;
75 let mut out = String::with_capacity(s.len());
76 let chars: Vec<char> = s.chars().collect();
77 let len = chars.len();
78 let mut i = 0;
79 while i < len {
80 let c = chars[i];
81 if c == ' ' && i + 1 < len && chars[i + 1].is_ascii_digit() {
82 i += 1;
84 continue;
85 }
86 out.push(c);
87 i += 1;
88 }
89 out
90 };
91
92 if let Some(cfg) = config
93 .as_any()
94 .downcast_ref::<crate::types::config::ClaudeModelConfig>()
95 {
96 use std::collections::HashMap;
97 let mut tokens = HashMap::new();
98 tokens.insert("model", compacted_model);
99 tokens.insert("symbol", cfg.symbol.clone());
100 return crate::style::render_with_style_template(cfg.format(), &tokens, cfg.style());
101 }
102
103 compacted_model
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use crate::config::Config;
111 use crate::types::claude::{ClaudeInput, ModelInfo, WorkspaceInfo};
112 use crate::types::context::Context;
113 use rstest::*;
114
115 fn context_with_model(model_name: &str) -> Context {
117 let input = ClaudeInput {
118 hook_event_name: None,
119 session_id: "test-session".to_string(),
120 transcript_path: None,
121 cwd: "/test/dir".to_string(),
122 model: ModelInfo {
123 id: format!("claude-{}", model_name.to_lowercase()),
124 display_name: model_name.to_string(),
125 },
126 workspace: Some(WorkspaceInfo {
127 current_dir: "/test/dir".to_string(),
128 project_dir: Some("/test".to_string()),
129 }),
130 version: Some("1.0.0".to_string()),
131 output_style: None,
132 };
133 Context::new(input, Config::default())
134 }
135
136 #[rstest]
137 #[case("Opus")]
138 #[case("Sonnet")]
139 #[case("Haiku")]
140 #[case("Claude-3.5")]
141 fn test_model_rendering(#[case] model_name: &str) {
142 let module = ClaudeModelModule::new();
143 let context = context_with_model(model_name);
144
145 assert_eq!(module.name(), "claude_model");
146 assert!(module.should_display(&context, &context.config.claude_model));
147 let rendered = module.render(&context, &context.config.claude_model);
148 assert!(rendered.contains(model_name));
149 assert!(rendered.starts_with("\u{1b}[") && rendered.ends_with("\u{1b}[0m"));
151 }
152
153 #[rstest]
154 #[case("", false)]
155 #[case(" ", false)]
156 #[case("\t\n", false)]
157 #[case("Opus", true)]
158 fn test_should_display_with_different_model_names(
159 #[case] model_name: &str,
160 #[case] should_display: bool,
161 ) {
162 let module = ClaudeModelModule::new();
163 let context = context_with_model(model_name);
164
165 assert_eq!(
166 module.should_display(&context, &context.config.claude_model),
167 should_display
168 );
169 }
170
171 #[rstest]
172 fn test_module_metadata() {
173 let module = ClaudeModelModule::new();
174 assert_eq!(module.name(), "claude_model");
175 }
176
177 #[rstest]
178 fn test_from_context_constructor() {
179 let context = context_with_model("Opus");
180 let module = ClaudeModelModule::from_context(&context);
181 assert_eq!(module.name(), "claude_model");
182 }
183
184 #[rstest]
185 fn compacts_space_before_digits() {
186 let module = ClaudeModelModule::new();
187 let context = context_with_model("Sonnet 4");
188 let rendered = module.render(&context, &context.config.claude_model);
189 let plain = String::from_utf8(strip_ansi_escapes::strip(rendered)).unwrap();
191 assert!(plain.contains("Sonnet4"));
192 assert!(!plain.contains("Sonnet 4"));
193 }
194}