From e034253a5eea764b20ab601f71bf84903d80e8d6 Mon Sep 17 00:00:00 2001 From: Neil Kistner Date: Mon, 26 Aug 2019 20:52:45 -0500 Subject: [PATCH] feat: Add ability to use an alternate directory truncation style (#239) * Add ability to use an alternate directory truncation style --- docs/config/README.md | 17 +++-- src/modules/directory.rs | 95 ++++++++++++++++++++++--- tests/testsuite/directory.rs | 133 +++++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+), 14 deletions(-) diff --git a/docs/config/README.md b/docs/config/README.md index acc91e77e..766ac7870 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -178,13 +178,22 @@ The `directory` module shows the path to your current directory, truncated to three parent folders. Your directory will also be truncated to the root of the git repo that you're currently in. +When using the fish style pwd option, instead of hiding the path that is +truncated, you will see a shortened name of each directory based on the number +you enable for the option. + +For example, given `~/Dev/Nix/nixpkgs/pkgs` where `nixpkgs` is the repo root, +and the option set to `1`. You will now see `~/D/N/nixpkgs/pkgs`, whereas before +it would have been `nixpkgs/pkgs`. + ### Options | Variable | Default | Description | | ------------------- | ------- | -------------------------------------------------------------------------------- | -| `truncation_length` | `3` | The number of parent folders that the current directory should be truncated to. | -| `truncate_to_repo` | `true` | Whether or not to truncate to the root of the git repo that you're currently in. | -| `disabled` | `false` | Disables the `directory` module. | +| `truncation_length` | `3` | The number of parent folders that the current directory should be truncated to. | +| `truncate_to_repo` | `true` | Whether or not to truncate to the root of the git repo that you're currently in. | +| `disabled` | `false` | Disables the `directory` module. | +| `fish_style_pwd_dir_length` | `0` | The number of characters to use when applying fish shell pwd path logic. | ### Example @@ -515,4 +524,4 @@ disabled = true use_name = true impure_msg = "impure shell" pure_msg = "pure shell" -``` \ No newline at end of file +``` diff --git a/src/modules/directory.rs b/src/modules/directory.rs index ee3924b98..8c12db59a 100644 --- a/src/modules/directory.rs +++ b/src/modules/directory.rs @@ -16,6 +16,7 @@ use super::{Context, Module}; pub fn module<'a>(context: &'a Context) -> Option> { const HOME_SYMBOL: &str = "~"; const DIR_TRUNCATION_LENGTH: i64 = 3; + const FISH_STYLE_PWD_DIR_LENGTH: i64 = 0; let module_color = Color::Cyan.bold(); let mut module = context.new_module("directory")?; @@ -25,28 +26,40 @@ pub fn module<'a>(context: &'a Context) -> Option> { .config_value_i64("truncation_length") .unwrap_or(DIR_TRUNCATION_LENGTH); let truncate_to_repo = module.config_value_bool("truncate_to_repo").unwrap_or(true); + let fish_style_pwd_dir_length = module + .config_value_i64("fish_style_pwd_dir_length") + .unwrap_or(FISH_STYLE_PWD_DIR_LENGTH); + let home_dir = dirs::home_dir().unwrap(); let current_dir = &context.current_dir; log::debug!("Current directory: {:?}", current_dir); - let dir_string; - match &context.repo_root { + let dir_string = match &context.repo_root { Some(repo_root) if truncate_to_repo => { - // Contract the path to the git repo root let repo_folder_name = repo_root.file_name().unwrap().to_str().unwrap(); - dir_string = contract_path(current_dir, repo_root, repo_folder_name); - } - _ => { - // Contract the path to the home directory - let home_dir = dirs::home_dir().unwrap(); - - dir_string = contract_path(current_dir, &home_dir, HOME_SYMBOL); + // Contract the path to the git repo root + contract_path(current_dir, repo_root, repo_folder_name) } + // Contract the path to the home directory + _ => contract_path(current_dir, &home_dir, HOME_SYMBOL), }; // Truncate the dir string to the maximum number of path components let truncated_dir_string = truncate(dir_string, truncation_length as usize); + + if fish_style_pwd_dir_length > 0 { + // If user is using fish style path, we need to add the segment first + let contracted_home_dir = contract_path(current_dir, &home_dir, HOME_SYMBOL); + let fish_style_dir = to_fish_style( + fish_style_pwd_dir_length as usize, + contracted_home_dir, + &truncated_dir_string, + ); + + module.new_segment("path", &fish_style_dir); + } + module.new_segment("path", &truncated_dir_string); module.get_prefix().set_value("in "); @@ -115,6 +128,38 @@ fn truncate(dir_string: String, length: usize) -> String { truncated_components.join("/") } +/// Takes part before contracted path and replaces it with fish style path +/// +/// Will take the first letter of each directory before the contracted path and +/// use that in the path instead. See the following example. +/// +/// Absolute Path: `/Users/Bob/Projects/work/a_repo` +/// Contracted Path: `a_repo` +/// With Fish Style: `~/P/w/a_repo` +/// +/// Absolute Path: `/some/Path/not/in_a/repo/but_nested` +/// Contracted Path: `in_a/repo/but_nested` +/// With Fish Style: `/s/P/n/in_a/repo/but_nested` +fn to_fish_style(pwd_dir_length: usize, dir_string: String, truncated_dir_string: &str) -> String { + let replaced_dir_string = dir_string.replace(truncated_dir_string, ""); + let components = replaced_dir_string.split('/').collect::>(); + + if components.is_empty() { + return replaced_dir_string; + } + + components + .into_iter() + .map(|word| match word { + "" => "", + _ if word.len() <= pwd_dir_length => word, + _ if word.starts_with('.') => &word[..=pwd_dir_length], + _ => &word[..pwd_dir_length], + }) + .collect::>() + .join("/") +} + #[cfg(test)] mod tests { use super::*; @@ -204,4 +249,34 @@ mod tests { let output = truncate(path.to_string(), 3); assert_eq!(output, "engines/booster/rocket") } + + #[test] + fn fish_style_with_user_home_contracted_path() { + let path = "~/starship/engines/booster/rocket"; + let output = to_fish_style(1, path.to_string(), "engines/booster/rocket"); + assert_eq!(output, "~/s/"); + } + + #[test] + fn fish_style_with_user_home_contracted_path_and_dot_dir() { + let path = "~/.starship/engines/booster/rocket"; + let output = to_fish_style(1, path.to_string(), "engines/booster/rocket"); + assert_eq!(output, "~/.s/"); + } + + #[test] + fn fish_style_with_no_contracted_path() { + // `truncatation_length = 2` + let path = "/absolute/Path/not/in_a/repo/but_nested"; + let output = to_fish_style(1, path.to_string(), "repo/but_nested"); + assert_eq!(output, "/a/P/n/i/"); + } + + #[test] + fn fish_style_with_pwd_dir_len_no_contracted_path() { + // `truncatation_length = 2` + let path = "/absolute/Path/not/in_a/repo/but_nested"; + let output = to_fish_style(2, path.to_string(), "repo/but_nested"); + assert_eq!(output, "/ab/Pa/no/in/"); + } } diff --git a/tests/testsuite/directory.rs b/tests/testsuite/directory.rs index 702a6b516..dce7f7983 100644 --- a/tests/testsuite/directory.rs +++ b/tests/testsuite/directory.rs @@ -61,6 +61,28 @@ fn truncated_directory_in_home() -> io::Result<()> { Ok(()) } +#[test] +#[ignore] +fn fish_directory_in_home() -> io::Result<()> { + let dir = home_dir().unwrap().join("starship/engine/schematics"); + fs::create_dir_all(&dir)?; + + let output = common::render_module("directory") + .use_config(toml::toml! { + [directory] + truncation_length = 1 + fish_style_pwd_dir_length = 2 + }) + .arg("--path") + .arg(dir) + .output()?; + let actual = String::from_utf8(output.stdout).unwrap(); + + let expected = format!("in {} ", Color::Cyan.bold().paint("~/st/en/schematics")); + assert_eq!(expected, actual); + Ok(()) +} + #[test] fn root_directory() -> io::Result<()> { let output = common::render_module("directory") @@ -141,6 +163,31 @@ fn truncated_directory_config_large() -> io::Result<()> { Ok(()) } +#[test] +#[ignore] +fn fish_style_directory_config_large() -> io::Result<()> { + let dir = Path::new("/tmp/starship/thrusters/rocket"); + fs::create_dir_all(&dir)?; + + let output = common::render_module("directory") + .use_config(toml::toml! { + [directory] + truncation_length = 1 + fish_style_pwd_dir_length = 100 + }) + .arg("--path") + .arg(dir) + .output()?; + let actual = String::from_utf8(output.stdout).unwrap(); + + let expected = format!( + "in {} ", + Color::Cyan.bold().paint("/tmp/starship/thrusters/rocket") + ); + assert_eq!(expected, actual); + Ok(()) +} + #[test] #[ignore] fn truncated_directory_config_small() -> io::Result<()> { @@ -162,6 +209,28 @@ fn truncated_directory_config_small() -> io::Result<()> { Ok(()) } +#[test] +#[ignore] +fn fish_directory_config_small() -> io::Result<()> { + let dir = Path::new("/tmp/starship/thrusters/rocket"); + fs::create_dir_all(&dir)?; + + let output = common::render_module("directory") + .use_config(toml::toml! { + [directory] + truncation_length = 2 + fish_style_pwd_dir_length = 1 + }) + .arg("--path") + .arg(dir) + .output()?; + let actual = String::from_utf8(output.stdout).unwrap(); + + let expected = format!("in {} ", Color::Cyan.bold().paint("/t/s/thrusters/rocket")); + assert_eq!(expected, actual); + Ok(()) +} + #[test] #[ignore] fn git_repo_root() -> io::Result<()> { @@ -255,6 +324,70 @@ fn directory_in_git_repo_truncate_to_repo_false() -> io::Result<()> { Ok(()) } +#[test] +#[ignore] +fn fish_path_directory_in_git_repo_truncate_to_repo_false() -> io::Result<()> { + let tmp_dir = TempDir::new_in(dirs::home_dir().unwrap())?; + let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); + let dir = repo_dir.join("src/meters/fuel-gauge"); + fs::create_dir_all(&dir)?; + Repository::init(&repo_dir).unwrap(); + + let output = common::render_module("directory") + .use_config(toml::toml! { + [directory] + // Don't truncate the path at all. + truncation_length = 5 + truncate_to_repo = false + fish_style_pwd_dir_length = 1 + }) + .arg("--path") + .arg(dir) + .output()?; + let actual = String::from_utf8(output.stdout).unwrap(); + + let expected = format!( + "in {} ", + Color::Cyan + .bold() + .paint("~/.t/above-repo/rocket-controls/src/meters/fuel-gauge") + ); + assert_eq!(expected, actual); + Ok(()) +} + +#[test] +#[ignore] +fn fish_path_directory_in_git_repo_truncate_to_repo_true() -> io::Result<()> { + let tmp_dir = TempDir::new_in(dirs::home_dir().unwrap())?; + let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); + let dir = repo_dir.join("src/meters/fuel-gauge"); + fs::create_dir_all(&dir)?; + Repository::init(&repo_dir).unwrap(); + + let output = common::render_module("directory") + .use_config(toml::toml! { + [directory] + // `truncate_to_repo = true` should display the truncated path + truncation_length = 5 + truncate_to_repo = true + fish_style_pwd_dir_length = 1 + }) + .arg("--path") + .arg(dir) + .output()?; + let actual = String::from_utf8(output.stdout).unwrap(); + + let expected = format!( + "in {} ", + Color::Cyan + .bold() + .paint("~/.t/a/rocket-controls/src/meters/fuel-gauge") + ); + assert_eq!(expected, actual); + Ok(()) +} + #[test] #[ignore] fn directory_in_git_repo_truncate_to_repo_true() -> io::Result<()> {