1
0
mirror of https://github.com/Llewellynvdm/starship.git synced 2024-12-12 06:07:56 +00:00
starship/src/module.rs

305 lines
7.5 KiB
Rust
Raw Normal View History

2022-05-23 10:58:27 +00:00
use crate::segment;
use crate::segment::{FillSegment, Segment};
use nu_ansi_term::{AnsiString, AnsiStrings, Style as AnsiStyle};
2019-05-01 20:34:24 +00:00
use std::fmt;
use std::time::Duration;
2019-05-01 20:34:24 +00:00
// List of all modules
// Default ordering is handled in configs/starship_root.rs
pub const ALL_MODULES: &[&str] = &[
"aws",
"azure",
#[cfg(feature = "battery")]
"battery",
"buf",
2022-08-01 10:59:36 +00:00
"bun",
"c",
"character",
"cmake",
"cmd_duration",
"cobol",
2019-10-05 18:25:25 +00:00
"conda",
"container",
"crystal",
2022-05-26 14:42:31 +00:00
"daml",
2020-07-29 15:38:23 +00:00
"dart",
"deno",
"directory",
"direnv",
"docker_context",
"dotnet",
2020-03-02 03:29:27 +00:00
"elixir",
"elm",
"erlang",
"fennel",
"fill",
"fossil_branch",
"fossil_metrics",
"gcloud",
"git_branch",
2019-12-06 16:57:42 +00:00
"git_commit",
"git_metrics",
"git_state",
"git_status",
"gleam",
"golang",
"gradle",
"guix_shell",
"haskell",
"haxe",
"helm",
2019-12-02 22:37:18 +00:00
"hg_branch",
"hostname",
"java",
"jobs",
"julia",
"kotlin",
"kubernetes",
"line_break",
"localip",
"lua",
"memory_usage",
"meson",
"nats",
"nim",
"nix_shell",
"nodejs",
"ocaml",
"odin",
"opa",
"openstack",
feat: Add operating system module (#4109) * docs(os): Add os module documentation * docs(os): Add os to Default Prompt Format * chore(os): Update config file schema * feat(os): Add os entries and declarations * feat(os): Add os module and config * fix(os): Obey config.disabled * feat(os): make variables 'Unknown'-aware refactor(os): calculate variables in dedicated functions * test(os): Add os module tests * feat(os): make 'name' variable less 'Unknown'-aware * docs(os): Add Preset configurations docs(os): Use emoji as default * feat(os): Use emoji as default test(os): Use emoji as default * fix(os): Add spaces after emoji symbols * chore(os): Update config schema * feat(os): Remove `bitness` variable docs(os): Remove `bitness` variable test(os): Remove `bitness` test * feat(os): Add Cargo.toml upgrade caution for os_info * refactor(os): Clarify get_symbol function * docs(os): Mention supported operating systems and feature requests * docs(os): Mention os_info inacurracy * test(os): Remove `bitness` leftovers * refactor(os): use nu_ansi_term * refactor(os): add cfg_attr(schemars(deny_unknown_fields)) * chore(os): update config schema * docs(os): expose details block * feat(os): add garuda linux * chore(os): update config schema * feat(os): add case insensitivity * feat(os): add symbols `IndexMap` use `os_info::Type` instead of `String` * test(os): add clippy warn on new os_info::Type case * leave missing case to test github tests * test(os): re-add missing test case * style(os): fix formatting * docs(os): update to match os_info::Type serialization - docs(os): add missing garuda to config - test(os): mention docs updates in warn_on_os_info_update
2022-11-06 21:37:58 +00:00
"os",
"package",
"perl",
"php",
"pijul_channel",
2021-10-05 23:27:25 +00:00
"pulumi",
"purescript",
"python",
"quarto",
"raku",
"red",
"rlang",
"ruby",
"rust",
"scala",
"shell",
"shlvl",
2020-02-26 16:18:19 +00:00
"singularity",
"solidity",
"spack",
"status",
"sudo",
"swift",
"terraform",
"time",
"typst",
"username",
"vagrant",
"vcsh",
"vlang",
2020-05-21 16:49:49 +00:00
"zig",
];
2019-05-01 20:34:24 +00:00
/// A module is a collection of segments showing data for a single integration
/// (e.g. The git module shows the current git branch and status)
pub struct Module<'a> {
/// The module's configuration map if available
pub config: Option<&'a toml::Value>,
2019-05-01 20:34:24 +00:00
/// The module's name, to be used in configuration and logging.
name: String,
2019-05-01 20:34:24 +00:00
/// The module's description
description: String,
2019-05-01 20:34:24 +00:00
/// The collection of segments that compose this module.
pub segments: Vec<Segment>,
/// the time it took to compute this module
pub duration: Duration,
2019-05-01 20:34:24 +00:00
}
impl<'a> Module<'a> {
2019-05-01 20:34:24 +00:00
/// Creates a module with no segments.
pub fn new(name: &str, desc: &str, config: Option<&'a toml::Value>) -> Self {
2019-05-01 20:34:24 +00:00
Module {
config,
name: name.to_string(),
description: desc.to_string(),
2019-05-01 20:34:24 +00:00
segments: Vec::new(),
duration: Duration::default(),
2019-05-01 20:34:24 +00:00
}
}
/// Set segments in module
2020-04-07 09:58:10 +00:00
pub fn set_segments(&mut self, segments: Vec<Segment>) {
self.segments = segments;
}
/// Get module's name
pub fn get_name(&self) -> &String {
&self.name
}
/// Get module's description
pub fn get_description(&self) -> &String {
&self.description
}
/// Whether a module has non-empty segments
2019-05-14 04:43:11 +00:00
pub fn is_empty(&self) -> bool {
self.segments
.iter()
// no trim: if we add spaces/linebreaks it's not "empty" as we change the final output
.all(|segment| segment.value().is_empty())
2019-05-14 04:43:11 +00:00
}
/// Get values of the module's segments
pub fn get_segments(&self) -> Vec<&str> {
2022-05-23 10:58:27 +00:00
self.segments.iter().map(segment::Segment::value).collect()
}
/// Returns a vector of colored `AnsiString` elements to be later used with
/// `AnsiStrings()` to optimize ANSI codes
pub fn ansi_strings(&self) -> Vec<AnsiString> {
fix: combine ANSI color codes before wrapping them (#5762) * combine ANSI color codes before wrapping them The existing code wraps each individual module's output for `context.shell`, concatenates all that output together and passes it to `AnsiStrings` to merge ANSI color codes. However, the wrapping obscures ANSI color codes, meaning that no merging is possible. This commit changes the shell-specific wrapping to happen right before output, once all modules' output has been concatenated together. This results in ANSI color codes being correctly merged, as well as reducing the number of calls to `wrap_colorseq_for_shell` to one. With a minimal `starship.toml`: ``` format = """$directory""" [directory] format = '[a]($style)[b]($style)' ``` The current code produces[0]: ``` \n%{\x1b[31m%}a%{\x1b[0m%}%{\x1b[31m%}b%{\x1b[0m% ``` And this commit's code: ``` \n%{\x1b[31m%}ab%{\x1b[0m%} ``` You can see that the current code emits an additional reset and repeated color code between "a" and "b" compared to the new code. [0] Produced in a Python shell with: ``` subprocess.check_output( "./target/debug/starship prompt", shell=True, env={"STARSHIP_CONFIG": "./starship.toml", "STARSHIP_SHELL": "zsh"} ) ``` * utils: return early from wrap_seq_for_shell unless wrapping required * refactor(utils): simplify wrap_seq_for_shell This commit modifies wrap_seq_for_shell to (a) return early for shells with no wrapping required, and (b) determine the wrapping characters once at the start of the function (rather than inline in the map function for every character).
2024-04-06 13:28:26 +00:00
self.ansi_strings_for_width(None)
}
fix: combine ANSI color codes before wrapping them (#5762) * combine ANSI color codes before wrapping them The existing code wraps each individual module's output for `context.shell`, concatenates all that output together and passes it to `AnsiStrings` to merge ANSI color codes. However, the wrapping obscures ANSI color codes, meaning that no merging is possible. This commit changes the shell-specific wrapping to happen right before output, once all modules' output has been concatenated together. This results in ANSI color codes being correctly merged, as well as reducing the number of calls to `wrap_colorseq_for_shell` to one. With a minimal `starship.toml`: ``` format = """$directory""" [directory] format = '[a]($style)[b]($style)' ``` The current code produces[0]: ``` \n%{\x1b[31m%}a%{\x1b[0m%}%{\x1b[31m%}b%{\x1b[0m% ``` And this commit's code: ``` \n%{\x1b[31m%}ab%{\x1b[0m%} ``` You can see that the current code emits an additional reset and repeated color code between "a" and "b" compared to the new code. [0] Produced in a Python shell with: ``` subprocess.check_output( "./target/debug/starship prompt", shell=True, env={"STARSHIP_CONFIG": "./starship.toml", "STARSHIP_SHELL": "zsh"} ) ``` * utils: return early from wrap_seq_for_shell unless wrapping required * refactor(utils): simplify wrap_seq_for_shell This commit modifies wrap_seq_for_shell to (a) return early for shells with no wrapping required, and (b) determine the wrapping characters once at the start of the function (rather than inline in the map function for every character).
2024-04-06 13:28:26 +00:00
pub fn ansi_strings_for_width(&self, width: Option<usize>) -> Vec<AnsiString> {
let mut iter = self.segments.iter().peekable();
let mut ansi_strings: Vec<AnsiString> = Vec::new();
while iter.peek().is_some() {
ansi_strings.extend(ansi_line(&mut iter, width));
}
fix: combine ANSI color codes before wrapping them (#5762) * combine ANSI color codes before wrapping them The existing code wraps each individual module's output for `context.shell`, concatenates all that output together and passes it to `AnsiStrings` to merge ANSI color codes. However, the wrapping obscures ANSI color codes, meaning that no merging is possible. This commit changes the shell-specific wrapping to happen right before output, once all modules' output has been concatenated together. This results in ANSI color codes being correctly merged, as well as reducing the number of calls to `wrap_colorseq_for_shell` to one. With a minimal `starship.toml`: ``` format = """$directory""" [directory] format = '[a]($style)[b]($style)' ``` The current code produces[0]: ``` \n%{\x1b[31m%}a%{\x1b[0m%}%{\x1b[31m%}b%{\x1b[0m% ``` And this commit's code: ``` \n%{\x1b[31m%}ab%{\x1b[0m%} ``` You can see that the current code emits an additional reset and repeated color code between "a" and "b" compared to the new code. [0] Produced in a Python shell with: ``` subprocess.check_output( "./target/debug/starship prompt", shell=True, env={"STARSHIP_CONFIG": "./starship.toml", "STARSHIP_SHELL": "zsh"} ) ``` * utils: return early from wrap_seq_for_shell unless wrapping required * refactor(utils): simplify wrap_seq_for_shell This commit modifies wrap_seq_for_shell to (a) return early for shells with no wrapping required, and (b) determine the wrapping characters once at the start of the function (rather than inline in the map function for every character).
2024-04-06 13:28:26 +00:00
ansi_strings
2019-05-01 20:34:24 +00:00
}
}
impl<'a> fmt::Display for Module<'a> {
2019-05-01 20:34:24 +00:00
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let ansi_strings = self.ansi_strings();
write!(f, "{}", AnsiStrings(&ansi_strings))
2019-05-01 20:34:24 +00:00
}
}
fn ansi_line<'a, I>(segments: &mut I, term_width: Option<usize>) -> Vec<AnsiString<'a>>
where
I: Iterator<Item = &'a Segment>,
{
let mut used = 0usize;
let mut current: Vec<AnsiString> = Vec::new();
let mut chunks: Vec<(Vec<AnsiString>, &FillSegment)> = Vec::new();
let mut prev_style: Option<AnsiStyle> = None;
for segment in segments {
match segment {
Segment::Fill(fs) => {
chunks.push((current, fs));
current = Vec::new();
prev_style = None;
}
_ => {
used += segment.width_graphemes();
let current_segment_string = segment.ansi_string(prev_style.as_ref());
prev_style = Some(*current_segment_string.style_ref());
current.push(current_segment_string);
}
}
if matches!(segment, Segment::LineTerm) {
break;
}
}
if chunks.is_empty() {
current
} else {
let fill_size = term_width
.and_then(|tw| if tw > used { Some(tw - used) } else { None })
.map(|remaining| remaining / chunks.len());
chunks
.into_iter()
.flat_map(|(strs, fill)| {
let fill_string =
fill.ansi_string(fill_size, strs.last().map(|segment| segment.style_ref()));
strs.into_iter().chain(std::iter::once(fill_string))
})
.chain(current)
.collect::<Vec<AnsiString>>()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_all_modules_is_in_alphabetical_order() {
let mut sorted_modules: Vec<&str> = ALL_MODULES.to_vec();
sorted_modules.sort_unstable();
assert_eq!(sorted_modules.as_slice(), ALL_MODULES);
}
#[test]
fn test_module_is_empty_with_no_segments() {
let name = "unit_test";
let desc = "This is a unit test";
let module = Module {
config: None,
name: name.to_string(),
description: desc.to_string(),
segments: Vec::new(),
duration: Duration::default(),
};
assert!(module.is_empty());
}
#[test]
fn test_module_is_empty_with_all_empty_segments() {
let name = "unit_test";
let desc = "This is a unit test";
let module = Module {
config: None,
name: name.to_string(),
description: desc.to_string(),
segments: Segment::from_text(None, ""),
duration: Duration::default(),
};
assert!(module.is_empty());
}
#[test]
fn test_module_is_not_empty_with_linebreak_only() {
let name = "unit_test";
let desc = "This is a unit test";
let module = Module {
config: None,
name: name.to_string(),
description: desc.to_string(),
segments: Segment::from_text(None, "\n"),
duration: Duration::default(),
};
assert!(!module.is_empty());
}
#[test]
fn test_module_is_not_empty_with_space_only() {
let name = "unit_test";
let desc = "This is a unit test";
let module = Module {
config: None,
name: name.to_string(),
description: desc.to_string(),
segments: Segment::from_text(None, " "),
duration: Duration::default(),
};
assert!(!module.is_empty());
}
}