From d2366ddb9cf6d3ec288fc6aafd64edf2cef4d06d Mon Sep 17 00:00:00 2001 From: David Knaack Date: Mon, 7 Mar 2022 04:18:23 +0100 Subject: perf(git_status): add option to use windows starship to render in wsl (#2146) --- docs/config/README.md | 51 ++++++++++++++-------- src/config.rs | 28 ++++++------ src/configs/git_status.rs | 3 ++ src/modules/git_status.rs | 108 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 161 insertions(+), 29 deletions(-) diff --git a/docs/config/README.md b/docs/config/README.md index b56a01ead..7ceb20c74 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -1480,25 +1480,33 @@ format = '[+$added]($added_style)/[-$deleted]($deleted_style) ' The `git_status` module shows symbols representing the state of the repo in your current directory. +::: tip + +The Git Status module is very slow in Windows directories (for example under `/mnt/c/`) when in a WSL environment. +You can disable the module or use the `windows_starship` option to use a Windows-native Starship executable to compute `git_status` for those paths. + +::: + ### Options -| Option | Default | Description | -| ------------------- | --------------------------------------------- | ----------------------------------- | -| `format` | `'([\[$all_status$ahead_behind\]]($style) )'` | The default format for `git_status` | -| `conflicted` | `"="` | This branch has merge conflicts. | -| `ahead` | `"⇡"` | The format of `ahead` | -| `behind` | `"⇣"` | The format of `behind` | -| `diverged` | `"⇕"` | The format of `diverged` | -| `up_to_date` | `""` | The format of `up_to_date` | -| `untracked` | `"?"` | The format of `untracked` | -| `stashed` | `"$"` | The format of `stashed` | -| `modified` | `"!"` | The format of `modified` | -| `staged` | `"+"` | The format of `staged` | -| `renamed` | `"»"` | The format of `renamed` | -| `deleted` | `"✘"` | The format of `deleted` | -| `style` | `"bold red"` | The style for the module. | -| `ignore_submodules` | `false` | Ignore changes to submodules. | -| `disabled` | `false` | Disables the `git_status` module. | +| Option | Default | Description | +| ------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| `format` | `'([\[$all_status$ahead_behind\]]($style) )'` | The default format for `git_status` | +| `conflicted` | `"="` | This branch has merge conflicts. | +| `ahead` | `"⇡"` | The format of `ahead` | +| `behind` | `"⇣"` | The format of `behind` | +| `diverged` | `"⇕"` | The format of `diverged` | +| `up_to_date` | `""` | The format of `up_to_date` | +| `untracked` | `"?"` | The format of `untracked` | +| `stashed` | `"$"` | The format of `stashed` | +| `modified` | `"!"` | The format of `modified` | +| `staged` | `"+"` | The format of `staged` | +| `renamed` | `"»"` | The format of `renamed` | +| `deleted` | `"✘"` | The format of `deleted` | +| `style` | `"bold red"` | The style for the module. | +| `ignore_submodules` | `false` | Ignore changes to submodules. | +| `disabled` | `false` | Disables the `git_status` module. | +| `windows_starship` | | Use this (Linux) path to a Windows Starship executable to render `git_status` when on Windows paths in WSL. | ### Variables @@ -1562,6 +1570,15 @@ diverged = "⇕⇡${ahead_count}⇣${behind_count}" behind = "⇣${count}" ``` +Use Windows Starship executable on Windows paths in WSL + +```toml +# ~/.config/starship.toml + +[git_status] +windows_starship = '/mnt/c/Users/username/scoop/apps/starship/current/starship.exe' +``` + ## Go The `golang` module shows the currently installed version of [Go](https://golang.org/). diff --git a/src/config.rs b/src/config.rs index 83bccb7e8..bd2eae4ff 100644 --- a/src/config.rs +++ b/src/config.rs @@ -225,6 +225,21 @@ pub struct StarshipConfig { pub config: Option, } +pub fn get_config_path() -> Option { + if let Ok(path) = env::var("STARSHIP_CONFIG") { + // Use $STARSHIP_CONFIG as the config path if available + log::debug!("STARSHIP_CONFIG is set: {}", &path); + Some(path) + } else { + // Default to using ~/.config/starship.toml + log::debug!("STARSHIP_CONFIG is not set"); + let config_path = utils::home_dir()?.join(".config/starship.toml"); + let config_path_str = config_path.to_str()?.to_owned(); + log::debug!("Using default config path: {}", config_path_str); + Some(config_path_str) + } +} + impl StarshipConfig { /// Initialize the Config struct pub fn initialize() -> Self { @@ -241,18 +256,7 @@ impl StarshipConfig { /// Create a config from a starship configuration file fn config_from_file() -> Option { - let file_path = if let Ok(path) = env::var("STARSHIP_CONFIG") { - // Use $STARSHIP_CONFIG as the config path if available - log::debug!("STARSHIP_CONFIG is set: {}", &path); - path - } else { - // Default to using ~/.config/starship.toml - log::debug!("STARSHIP_CONFIG is not set"); - let config_path = utils::home_dir()?.join(".config/starship.toml"); - let config_path_str = config_path.to_str()?.to_owned(); - log::debug!("Using default config path: {}", config_path_str); - config_path_str - }; + let file_path = get_config_path()?; let toml_content = match utils::read_file(&file_path) { Ok(content) => { diff --git a/src/configs/git_status.rs b/src/configs/git_status.rs index 8a0140445..78186db3d 100644 --- a/src/configs/git_status.rs +++ b/src/configs/git_status.rs @@ -20,6 +20,8 @@ pub struct GitStatusConfig<'a> { pub untracked: &'a str, pub ignore_submodules: bool, pub disabled: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub windows_starship: Option<&'a str>, } impl<'a> Default for GitStatusConfig<'a> { @@ -40,6 +42,7 @@ impl<'a> Default for GitStatusConfig<'a> { untracked: "?", ignore_submodules: false, disabled: false, + windows_starship: None, } } } diff --git a/src/modules/git_status.rs b/src/modules/git_status.rs index db02083f5..c7291e101 100644 --- a/src/modules/git_status.rs +++ b/src/modules/git_status.rs @@ -34,6 +34,13 @@ pub fn module<'a>(context: &'a Context) -> Option> { //Return None if not in git repository context.get_repo().ok()?; + if let Some(git_status) = git_status_wsl(context, &config) { + if git_status.is_empty() { + return None; + } + module.set_segments(Segment::from_text(None, git_status)); + return Some(module); + } let parsed = StringFormatter::new(config.format).and_then(|formatter| { formatter @@ -354,6 +361,107 @@ fn format_symbol(format_str: &str, config_path: &str, context: &Context) -> Opti format_text(format_str, config_path, context, |_variable| None) } +#[cfg(target_os = "linux")] +fn git_status_wsl(context: &Context, conf: &GitStatusConfig) -> Option { + use crate::utils::create_command; + use nix::sys::utsname::uname; + use std::env; + use std::io::ErrorKind; + + let starship_exe = conf.windows_starship?; + + // Ensure this is WSL + // This is lowercase in WSL1 and uppercase in WSL2, just skip the first letter + if !uname().release().contains("icrosoft") { + return None; + } + + log::trace!("Using WSL mode"); + + // Get Windows path + let winpath = match create_command("wslpath") + .map(|mut c| { + c.arg("-w").arg(&context.current_dir); + c + }) + .and_then(|mut c| c.output()) + { + Ok(r) => r, + Err(e) => { + // Not found might means this might not be WSL after all + let level = if e.kind() == ErrorKind::NotFound { + log::Level::Debug + } else { + log::Level::Error + }; + + log::log!(level, "Failed to get Windows path:\n{:?}", e); + + return None; + } + }; + + let winpath = match std::str::from_utf8(&winpath.stdout) { + Ok(r) => r.trim_end(), + Err(e) => { + log::error!("Failed to parse Windows path:\n{:?}", e); + + return None; + } + }; + + log::trace!("Windows path: {}", winpath); + + // In Windows or Linux dir? + if winpath.starts_with(r"\\wsl") { + log::trace!("Not a Windows path"); + return None; + } + + // Get foreign starship to use WSL config + // https://devblogs.microsoft.com/commandline/share-environment-vars-between-wsl-and-windows/ + let wslenv = env::var("WSLENV") + .map(|e| e + ":STARSHIP_CONFIG/wp") + .unwrap_or_else(|_| "STARSHIP_CONFIG/wp".to_string()); + + let out = match create_command(starship_exe) + .map(|mut c| { + c.env( + "STARSHIP_CONFIG", + crate::config::get_config_path().unwrap_or_else(|| "/dev/null".to_string()), + ) + .env("WSLENV", wslenv) + .args(&["module", "git_status", "--path", winpath]); + c + }) + .and_then(|mut c| c.output()) + { + Ok(r) => r, + Err(e) => { + log::error!("Failed to run Git Status module on Windows:\n{}", e); + + return None; + } + }; + + match String::from_utf8(out.stdout) { + Ok(r) => Some(r), + Err(e) => { + log::error!( + "Failed to parse Windows Git Status module status output:\n{}", + e + ); + + None + } + } +} + +#[cfg(not(target_os = "linux"))] +fn git_status_wsl(_context: &Context, _conf: &GitStatusConfig) -> Option { + None +} + #[cfg(test)] mod tests { use ansi_term::{ANSIStrings, Color}; -- cgit v1.2.3