1use crate::segment;
2use crate::segment::{FillSegment, Segment};
3use nu_ansi_term::{AnsiString, AnsiStrings, Style as AnsiStyle};
4use std::fmt;
5use std::time::Duration;
6
7pub const ALL_MODULES: &[&str] = &[
10 "aws",
11 "azure",
12 #[cfg(feature = "battery")]
13 "battery",
14 "buf",
15 "bun",
16 "c",
17 "character",
18 "cmake",
19 "cmd_duration",
20 "cobol",
21 "conda",
22 "container",
23 "cpp",
24 "crystal",
25 "daml",
26 "dart",
27 "deno",
28 "directory",
29 "direnv",
30 "docker_context",
31 "dotnet",
32 "elixir",
33 "elm",
34 "erlang",
35 "fennel",
36 "fill",
37 "fortran",
38 "fossil_branch",
39 "fossil_metrics",
40 "gcloud",
41 "git_branch",
42 "git_commit",
43 "git_metrics",
44 "git_state",
45 "git_status",
46 "gleam",
47 "golang",
48 "gradle",
49 "guix_shell",
50 "haskell",
51 "haxe",
52 "helm",
53 "hg_branch",
54 "hg_state",
55 "hostname",
56 "java",
57 "jobs",
58 "julia",
59 "kotlin",
60 "kubernetes",
61 "line_break",
62 "localip",
63 "lua",
64 "memory_usage",
65 "meson",
66 "mise",
67 "mojo",
68 "nats",
69 "netns",
70 "nim",
71 "nix_shell",
72 "nodejs",
73 "ocaml",
74 "odin",
75 "opa",
76 "openstack",
77 "os",
78 "package",
79 "perl",
80 "php",
81 "pijul_channel",
82 "pixi",
83 "pulumi",
84 "purescript",
85 "python",
86 "quarto",
87 "raku",
88 "red",
89 "rlang",
90 "ruby",
91 "rust",
92 "scala",
93 "shell",
94 "shlvl",
95 "singularity",
96 "solidity",
97 "spack",
98 "status",
99 "sudo",
100 "swift",
101 "terraform",
102 "time",
103 "typst",
104 "username",
105 "vagrant",
106 "vcsh",
107 "vlang",
108 "xmake",
109 "zig",
110];
111
112pub struct Module<'a> {
115 pub config: Option<&'a toml::Value>,
117
118 name: String,
120
121 description: String,
123
124 pub segments: Vec<Segment>,
126
127 pub duration: Duration,
129}
130
131impl<'a> Module<'a> {
132 pub fn new(
134 name: impl Into<String>,
135 desc: impl Into<String>,
136 config: Option<&'a toml::Value>,
137 ) -> Self {
138 Self {
139 config,
140 name: name.into(),
141 description: desc.into(),
142 segments: Vec::new(),
143 duration: Duration::default(),
144 }
145 }
146
147 pub fn set_segments(&mut self, segments: Vec<Segment>) {
149 self.segments = segments;
150 }
151
152 pub fn get_name(&self) -> &String {
154 &self.name
155 }
156
157 pub fn get_description(&self) -> &String {
159 &self.description
160 }
161
162 pub fn is_empty(&self) -> bool {
164 self.segments
165 .iter()
166 .all(|segment| segment.value().is_empty())
168 }
169
170 pub fn get_segments(&self) -> Vec<&str> {
172 self.segments.iter().map(segment::Segment::value).collect()
173 }
174
175 pub fn ansi_strings(&self) -> Vec<AnsiString<'_>> {
178 self.ansi_strings_for_width(None)
179 }
180
181 pub fn ansi_strings_for_width(&self, width: Option<usize>) -> Vec<AnsiString<'_>> {
182 let mut iter = self.segments.iter().peekable();
183 let mut ansi_strings: Vec<AnsiString> = Vec::new();
184 while iter.peek().is_some() {
185 ansi_strings.extend(ansi_line(&mut iter, width));
186 }
187 ansi_strings
188 }
189}
190
191impl fmt::Display for Module<'_> {
192 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
193 let ansi_strings = self.ansi_strings();
194 write!(f, "{}", AnsiStrings(&ansi_strings))
195 }
196}
197
198fn ansi_line<'a, I>(segments: &mut I, term_width: Option<usize>) -> Vec<AnsiString<'a>>
199where
200 I: Iterator<Item = &'a Segment>,
201{
202 let mut used = 0usize;
203 let mut current: Vec<AnsiString> = Vec::new();
204 let mut chunks: Vec<(Vec<AnsiString>, &FillSegment)> = Vec::new();
205 let mut prev_style: Option<AnsiStyle> = None;
206
207 for segment in segments {
208 match segment {
209 Segment::Fill(fs) => {
210 chunks.push((current, fs));
211 current = Vec::new();
212 prev_style = None;
213 }
214 _ => {
215 used += segment.width_graphemes();
216 let current_segment_string = segment.ansi_string(prev_style.as_ref());
217
218 prev_style = Some(*current_segment_string.style_ref());
219 current.push(current_segment_string);
220 }
221 }
222
223 if matches!(segment, Segment::LineTerm) {
224 break;
225 }
226 }
227
228 if chunks.is_empty() {
229 current
230 } else {
231 let fill_size = term_width
232 .and_then(|tw| if tw > used { Some(tw - used) } else { None })
233 .map(|remaining| remaining / chunks.len());
234 chunks
235 .into_iter()
236 .flat_map(|(strs, fill)| {
237 let fill_string = fill.ansi_string(
238 fill_size,
239 strs.last().map(nu_ansi_term::AnsiGenericString::style_ref),
240 );
241 strs.into_iter().chain(std::iter::once(fill_string))
242 })
243 .chain(current)
244 .collect::<Vec<AnsiString>>()
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_all_modules_is_in_alphabetical_order() {
254 let mut sorted_modules: Vec<&str> = ALL_MODULES.to_vec();
255 sorted_modules.sort_unstable();
256 assert_eq!(sorted_modules.as_slice(), ALL_MODULES);
257 }
258
259 #[test]
260 fn test_module_is_empty_with_no_segments() {
261 let name = "unit_test";
262 let desc = "This is a unit test";
263 let module = Module {
264 config: None,
265 name: name.to_string(),
266 description: desc.to_string(),
267 segments: Vec::new(),
268 duration: Duration::default(),
269 };
270
271 assert!(module.is_empty());
272 }
273
274 #[test]
275 fn test_module_is_empty_with_all_empty_segments() {
276 let name = "unit_test";
277 let desc = "This is a unit test";
278 let module = Module {
279 config: None,
280 name: name.to_string(),
281 description: desc.to_string(),
282 segments: Segment::from_text(None, ""),
283 duration: Duration::default(),
284 };
285
286 assert!(module.is_empty());
287 }
288
289 #[test]
290 fn test_module_is_not_empty_with_linebreak_only() {
291 let name = "unit_test";
292 let desc = "This is a unit test";
293 let module = Module {
294 config: None,
295 name: name.to_string(),
296 description: desc.to_string(),
297 segments: Segment::from_text(None, "\n"),
298 duration: Duration::default(),
299 };
300
301 assert!(!module.is_empty());
302 }
303
304 #[test]
305 fn test_module_is_not_empty_with_space_only() {
306 let name = "unit_test";
307 let desc = "This is a unit test";
308 let module = Module {
309 config: None,
310 name: name.to_string(),
311 description: desc.to_string(),
312 segments: Segment::from_text(None, " "),
313 duration: Duration::default(),
314 };
315
316 assert!(!module.is_empty());
317 }
318}