mirror of
https://github.com/starship/starship.git
synced 2024-12-24 16:18:53 +01:00
feat(directory): add ellipsis to truncated paths (#1563)
Adds ellipsis in front of truncated paths: …/ Configurable through new config option: directory.truncation_symbol Fixes #1162, #1626
This commit is contained in:
parent
2693d125a8
commit
1673d565f4
@ -651,6 +651,7 @@ it would have been `nixpkgs/pkgs`.
|
||||
| `disabled` | `false` | Disables the `directory` module. |
|
||||
| `read_only` | `"🔒"` | The symbol indicating current directory is read only. |
|
||||
| `read_only_style` | `"red"` | The style for the read only symbol. |
|
||||
| `truncation_symbol` | `""` | The symbol to prefix to truncated paths. eg: "…/" |
|
||||
|
||||
<details>
|
||||
<summary>This module has a few advanced configuration options that control how the directory is displayed.</summary>
|
||||
@ -694,6 +695,7 @@ a single character. For `fish_style_pwd_dir_length = 2`, it would be `/bu/th/ci/
|
||||
|
||||
[directory]
|
||||
truncation_length = 8
|
||||
truncation_symbol = "…/"
|
||||
```
|
||||
|
||||
## Docker Context
|
||||
|
@ -15,6 +15,7 @@ pub struct DirectoryConfig<'a> {
|
||||
pub disabled: bool,
|
||||
pub read_only: &'a str,
|
||||
pub read_only_style: &'a str,
|
||||
pub truncation_symbol: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> RootModuleConfig<'a> for DirectoryConfig<'a> {
|
||||
@ -30,6 +31,7 @@ impl<'a> RootModuleConfig<'a> for DirectoryConfig<'a> {
|
||||
disabled: false,
|
||||
read_only: "🔒",
|
||||
read_only_style: "red",
|
||||
truncation_symbol: "",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ use crate::config::RootModuleConfig;
|
||||
use crate::configs::directory::DirectoryConfig;
|
||||
use crate::formatter::StringFormatter;
|
||||
|
||||
const HOME_SYMBOL: &str = "~";
|
||||
|
||||
/// Creates a module with the current directory
|
||||
///
|
||||
/// Will perform path contraction, substitution, and truncation.
|
||||
@ -31,41 +33,15 @@ use crate::formatter::StringFormatter;
|
||||
/// **Truncation**
|
||||
/// Paths will be limited in length to `3` path components by default.
|
||||
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
const HOME_SYMBOL: &str = "~";
|
||||
|
||||
let mut module = context.new_module("directory");
|
||||
let config: DirectoryConfig = DirectoryConfig::try_load(module.config);
|
||||
|
||||
// Using environment PWD is the standard approach for determining logical path
|
||||
// If this is None for any reason, we fall back to reading the os-provided path
|
||||
let physical_current_dir = if config.use_logical_path {
|
||||
match context.get_env("PWD") {
|
||||
Some(x) => Some(PathBuf::from(x)),
|
||||
None => {
|
||||
log::debug!("Error getting PWD environment variable!");
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match std::env::current_dir() {
|
||||
Ok(x) => Some(x),
|
||||
Err(e) => {
|
||||
log::debug!("Error getting physical current directory: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
let current_dir = Path::new(
|
||||
physical_current_dir
|
||||
.as_ref()
|
||||
.unwrap_or_else(|| &context.current_dir),
|
||||
);
|
||||
let current_dir = &get_current_dir(&context, &config);
|
||||
|
||||
let home_dir = dirs_next::home_dir().unwrap();
|
||||
log::debug!("Current directory: {:?}", current_dir);
|
||||
|
||||
let repo = &context.get_repo().ok()?;
|
||||
|
||||
let dir_string = match &repo.root {
|
||||
Some(repo_root) if config.truncate_to_repo && (repo_root != &home_dir) => {
|
||||
log::debug!("Repo root: {:?}", repo_root);
|
||||
@ -83,20 +59,25 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
// Truncate the dir string to the maximum number of path components
|
||||
let truncated_dir_string = truncate(substituted_dir, config.truncation_length as usize);
|
||||
|
||||
// Substitutions could have changed the prefix, so don't allow them and
|
||||
// fish-style path contraction together
|
||||
let fish_prefix = if config.fish_style_pwd_dir_length > 0 && config.substitutions.is_empty() {
|
||||
// If user is using fish style path, we need to add the segment first
|
||||
let contracted_home_dir = contract_path(¤t_dir, &home_dir, HOME_SYMBOL);
|
||||
to_fish_style(
|
||||
config.fish_style_pwd_dir_length as usize,
|
||||
contracted_home_dir,
|
||||
&truncated_dir_string,
|
||||
)
|
||||
let prefix = if is_truncated(&truncated_dir_string) {
|
||||
// Substitutions could have changed the prefix, so don't allow them and
|
||||
// fish-style path contraction together
|
||||
if config.fish_style_pwd_dir_length > 0 && config.substitutions.is_empty() {
|
||||
// If user is using fish style path, we need to add the segment first
|
||||
let contracted_home_dir = contract_path(¤t_dir, &home_dir, HOME_SYMBOL);
|
||||
to_fish_style(
|
||||
config.fish_style_pwd_dir_length as usize,
|
||||
contracted_home_dir,
|
||||
&truncated_dir_string,
|
||||
)
|
||||
} else {
|
||||
String::from(config.truncation_symbol)
|
||||
}
|
||||
} else {
|
||||
String::from("")
|
||||
};
|
||||
let final_dir_string = format!("{}{}", fish_prefix, truncated_dir_string);
|
||||
|
||||
let displayed_path = prefix + &truncated_dir_string;
|
||||
let lock_symbol = String::from(config.read_only);
|
||||
|
||||
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
|
||||
@ -107,7 +88,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
_ => None,
|
||||
})
|
||||
.map(|variable| match variable {
|
||||
"path" => Some(Ok(&final_dir_string)),
|
||||
"path" => Some(Ok(&displayed_path)),
|
||||
"read_only" => {
|
||||
if is_readonly_dir(&context.current_dir) {
|
||||
Some(Ok(&lock_symbol))
|
||||
@ -131,6 +112,35 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
Some(module)
|
||||
}
|
||||
|
||||
fn is_truncated(path: &str) -> bool {
|
||||
!(path.starts_with(HOME_SYMBOL)
|
||||
|| PathBuf::from(path).has_root()
|
||||
|| (cfg!(target_os = "windows") && PathBuf::from(String::from(path) + r"\").has_root()))
|
||||
}
|
||||
|
||||
fn get_current_dir(context: &Context, config: &DirectoryConfig) -> PathBuf {
|
||||
// Using environment PWD is the standard approach for determining logical path
|
||||
// If this is None for any reason, we fall back to reading the os-provided path
|
||||
let physical_current_dir = if config.use_logical_path {
|
||||
match context.get_env("PWD") {
|
||||
Some(x) => Some(PathBuf::from(x)),
|
||||
None => {
|
||||
log::debug!("Error getting PWD environment variable!");
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match std::env::current_dir() {
|
||||
Ok(x) => Some(x),
|
||||
Err(e) => {
|
||||
log::debug!("Error getting physical current directory: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
physical_current_dir.unwrap_or_else(|| PathBuf::from(&context.current_dir))
|
||||
}
|
||||
|
||||
fn is_readonly_dir(path: &Path) -> bool {
|
||||
match directory_utils::is_write_allowed(path) {
|
||||
Ok(res) => !res,
|
||||
@ -1166,4 +1176,182 @@ mod tests {
|
||||
assert_eq!(expected, actual);
|
||||
tmp_dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncation_symbol_truncated_root() -> io::Result<()> {
|
||||
let actual = ModuleRenderer::new("directory")
|
||||
.config(toml::toml! {
|
||||
[directory]
|
||||
truncation_length = 3
|
||||
truncation_symbol = "…/"
|
||||
})
|
||||
.path(Path::new("/a/four/element/path"))
|
||||
.collect();
|
||||
let expected = Some(format!(
|
||||
"{} ",
|
||||
Color::Cyan.bold().paint("…/four/element/path")
|
||||
));
|
||||
assert_eq!(expected, actual);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncation_symbol_not_truncated_root() -> io::Result<()> {
|
||||
let actual = ModuleRenderer::new("directory")
|
||||
.config(toml::toml! {
|
||||
[directory]
|
||||
truncation_length = 4
|
||||
truncation_symbol = "…/"
|
||||
})
|
||||
.path(Path::new("/a/four/element/path"))
|
||||
.collect();
|
||||
let expected = Some(format!(
|
||||
"{} ",
|
||||
Color::Cyan.bold().paint("/a/four/element/path")
|
||||
));
|
||||
assert_eq!(expected, actual);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncation_symbol_truncated_home() -> io::Result<()> {
|
||||
let (tmp_dir, name) = make_known_tempdir(home_dir().unwrap().as_path())?;
|
||||
let dir = tmp_dir.path().join("a/subpath");
|
||||
fs::create_dir_all(&dir)?;
|
||||
|
||||
let actual = ModuleRenderer::new("directory")
|
||||
.config(toml::toml! {
|
||||
[directory]
|
||||
truncation_length = 3
|
||||
truncation_symbol = "…/"
|
||||
})
|
||||
.path(dir)
|
||||
.collect();
|
||||
let expected = Some(format!(
|
||||
"{} ",
|
||||
Color::Cyan.bold().paint(format!("…/{}/a/subpath", name))
|
||||
));
|
||||
assert_eq!(expected, actual);
|
||||
tmp_dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncation_symbol_not_truncated_home() -> io::Result<()> {
|
||||
let (tmp_dir, name) = make_known_tempdir(home_dir().unwrap().as_path())?;
|
||||
let dir = tmp_dir.path().join("a/subpath");
|
||||
fs::create_dir_all(&dir)?;
|
||||
|
||||
let actual = ModuleRenderer::new("directory")
|
||||
.config(toml::toml! {
|
||||
[directory]
|
||||
truncate_to_repo = false // Necessary if homedir is a git repo
|
||||
truncation_length = 4
|
||||
truncation_symbol = "…/"
|
||||
})
|
||||
.path(dir)
|
||||
.collect();
|
||||
let expected = Some(format!(
|
||||
"{} ",
|
||||
Color::Cyan.bold().paint(format!("~/{}/a/subpath", name))
|
||||
));
|
||||
assert_eq!(expected, actual);
|
||||
tmp_dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncation_symbol_truncated_in_repo() -> io::Result<()> {
|
||||
let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?;
|
||||
let repo_dir = tmp_dir.path().join("above").join("repo");
|
||||
let dir = repo_dir.join("src/sub/path");
|
||||
fs::create_dir_all(&dir)?;
|
||||
init_repo(&repo_dir).unwrap();
|
||||
|
||||
let actual = ModuleRenderer::new("directory")
|
||||
.config(toml::toml! {
|
||||
[directory]
|
||||
truncation_length = 3
|
||||
truncation_symbol = "…/"
|
||||
})
|
||||
.path(dir)
|
||||
.collect();
|
||||
let expected = Some(format!("{} ", Color::Cyan.bold().paint("…/src/sub/path")));
|
||||
assert_eq!(expected, actual);
|
||||
tmp_dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncation_symbol_not_truncated_in_repo() -> io::Result<()> {
|
||||
let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?;
|
||||
let repo_dir = tmp_dir.path().join("above").join("repo");
|
||||
let dir = repo_dir.join("src/sub/path");
|
||||
fs::create_dir_all(&dir)?;
|
||||
init_repo(&repo_dir).unwrap();
|
||||
|
||||
let actual = ModuleRenderer::new("directory")
|
||||
.config(toml::toml! {
|
||||
[directory]
|
||||
truncation_length = 5
|
||||
truncation_symbol = "…/"
|
||||
truncate_to_repo = true
|
||||
})
|
||||
.path(dir)
|
||||
.collect();
|
||||
let expected = Some(format!(
|
||||
"{} ",
|
||||
Color::Cyan.bold().paint("…/repo/src/sub/path")
|
||||
));
|
||||
assert_eq!(expected, actual);
|
||||
tmp_dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn truncation_symbol_windows_root_not_truncated() -> io::Result<()> {
|
||||
let dir = Path::new("C:\\temp");
|
||||
let actual = ModuleRenderer::new("directory")
|
||||
.config(toml::toml! {
|
||||
[directory]
|
||||
truncation_length = 2
|
||||
truncation_symbol = "…/"
|
||||
})
|
||||
.path(dir)
|
||||
.collect();
|
||||
let expected = Some(format!("{} ", Color::Cyan.bold().paint("C:/temp")));
|
||||
assert_eq!(expected, actual);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn truncation_symbol_windows_root_truncated() -> io::Result<()> {
|
||||
let dir = Path::new("C:\\temp");
|
||||
let actual = ModuleRenderer::new("directory")
|
||||
.config(toml::toml! {
|
||||
[directory]
|
||||
truncation_length = 1
|
||||
truncation_symbol = "…/"
|
||||
})
|
||||
.path(dir)
|
||||
.collect();
|
||||
let expected = Some(format!("{} ", Color::Cyan.bold().paint("…/temp")));
|
||||
assert_eq!(expected, actual);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn truncation_symbol_windows_root_truncated_backslash() -> io::Result<()> {
|
||||
let dir = Path::new("C:\\temp");
|
||||
let actual = ModuleRenderer::new("directory")
|
||||
.config(toml::toml! {
|
||||
[directory]
|
||||
truncation_length = 1
|
||||
truncation_symbol = r"…\"
|
||||
})
|
||||
.path(dir)
|
||||
.collect();
|
||||
let expected = Some(format!("{} ", Color::Cyan.bold().paint("…\\temp")));
|
||||
assert_eq!(expected, actual);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user