use std::ffi::OsStr; use std::path::Path; /* We need to send execution time to the prompt for the cmd_duration module. For fish, this is fairly straightforward. For bash and zsh, we'll need to use several shell utilities to get the time, as well as render the prompt */ pub fn init(shell_name: &str) { log::debug!("Shell name: {}", shell_name); let shell_basename = Path::new(shell_name).file_stem().and_then(OsStr::to_str); let setup_script = match shell_basename { Some("bash") => { let script = BASH_INIT; Some(script) } Some("zsh") => { let script = ZSH_INIT; Some(script) } Some("fish") => { let script = FISH_INIT; Some(script) } None => { println!( "Invalid shell name provided: {}\\n\ If this issue persists, please open an \ issue in the starship repo: \\n\ https://github.com/starship/starship/issues/new\\n\"", shell_name ); None } _ => { /* Calling unwrap() here is fine because the None case will have already matched on the previous arm */ println!( "printf \"\\n{0} is not yet supported by starship.\\n\ For the time being, we support bash, zsh, and fish.\\n\ Please open an issue in the starship repo if you would like to \ see support for {0}:\\nhttps://github.com/starship/starship/issues/new\"\\n\\n", shell_basename.unwrap() ); None } }; if let Some(script) = setup_script { print!("{}", script); } } /* For bash: we need to manually hook functions ourself: PROMPT_COMMAND will exec right before the prompt is drawn, and any function trapped by DEBUG will exec before a command is run. There is a preexec/precmd framework for bash out there: if we find the appropriate variables set, assume we are using that framework: https://github.com/rcaloras/bash-preexec Bash quirk: DEBUG is triggered whenever a command is executed, even if that command is part of a pipeline. To avoid only timing the last part of a pipeline, we only start the timer if no timer has been started since the last prompt draw, tracked by the variable PREEXEC_READY. Similarly, only draw timing info if STARSHIP_START_TIME is defined, in case preexec was interrupted. Finally, to work around existing DEBUG traps in the absence of a preexec-like, we parse out the name of the old DEBUG hook, then make a new function which calls both that function and our starship hooks. We don't do this for PROMPT_COMMAND because that would probably result in two prompts. We need to quote the output of `$(jobs -p | wc -l)` since MacOS `wc` leaves giant spaces in front of the number (e.g. " 3"), which messes up the word-splitting. Instead, quote the whole thing, then let Rust do the whitespace trimming within the jobs module */ /* Note to programmers: this and the zsh init will be evaluated on a single line. Use semicolons, avoid comments, and generally think like all newlines will be deleted. */ const BASH_INIT: &str = r##" starship_preexec() { if [ "$PREEXEC_READY" = "true" ]; then PREEXEC_READY=false; STARSHIP_START_TIME=$(date +%s); fi }; starship_precmd() { STATUS=$?; if [[ $STARSHIP_START_TIME ]]; then STARSHIP_END_TIME=$(date +%s); STARSHIP_DURATION=$((STARSHIP_END_TIME - STARSHIP_START_TIME)); PS1="$(starship prompt --status=$STATUS --jobs="$(jobs -p | wc -l)" --cmd-duration=$STARSHIP_DURATION)"; unset STARSHIP_START_TIME; else PS1="$(starship prompt --status=$STATUS --jobs="$(jobs -p | wc -l)")"; fi; PREEXEC_READY=true; }; if [[ $preexec_functions ]]; then preexec_functions+=(starship_preexec); precmd_functions+=(starship_precmd); STARSHIP_START_TIME=$(date +%s); else dbg_trap="$(trap -p DEBUG | cut -d' ' -f3 | tr -d \')"; if [[ -z "$dbg_trap" ]]; then trap starship_preexec DEBUG; elif [[ "$dbg_trap" != "starship_preexec" && "$dbg_trap" != "starship_preexec_all" ]]; then function starship_preexec_all(){ $dbg_trap; starship_preexec; }; trap starship_preexec_all DEBUG; fi; PROMPT_COMMAND=starship_precmd; STARSHIP_START_TIME=$(date +%s); fi; "##; /* For zsh: preexec_functions and precmd_functions provide preexec/precmd in a way that lets us avoid clobbering them. Zsh quirk: preexec() is only fired if a command is actually run (unlike in bash, where spamming empty commands still triggers DEBUG). This means a user spamming ENTER at an empty command line will see increasing runtime (since preexec never actually fires to reset the start time). To fix this, only pass the time if STARSHIP_START_TIME is defined, and unset it after passing the time, so that we only measure actual commands. We need to quote the output of the jobs command for the same reason as bash. */ const ZSH_INIT: &str = r##" starship_precmd() { STATUS=$?; if [[ $STARSHIP_START_TIME ]]; then STARSHIP_END_TIME="$(date +%s)"; STARSHIP_DURATION=$((STARSHIP_END_TIME - STARSHIP_START_TIME)); PROMPT="$(starship prompt --status=$STATUS --cmd-duration=$STARSHIP_DURATION --jobs="$(jobs | wc -l)")"; unset STARSHIP_START_TIME; else PROMPT="$(starship prompt --status=$STATUS --jobs="$(jobs | wc -l)")"; fi }; starship_preexec(){ STARSHIP_START_TIME="$(date +%s)" }; if [[ ${precmd_functions[(ie)starship_precmd]} -gt ${#precmd_functions} ]]; then precmd_functions+=(starship_precmd); fi; if [[ ${preexec_functions[(ie)starship_preexec]} -gt ${#preexec_functions} ]]; then preexec_functions+=(starship_preexec); fi; STARSHIP_START_TIME="$(date +%s)"; "##; /* Fish setup is simple because they give us CMD_DURATION. Just account for name changes between 2.7/3.0 and do some math to convert ms->s and we can use it */ const FISH_INIT: &str = r##" function fish_prompt; set -l exit_code $status; set -l CMD_DURATION "$CMD_DURATION$cmd_duration"; set -l starship_duration (math --scale=0 "$CMD_DURATION / 1000"); starship prompt --status=$exit_code --cmd-duration=$starship_duration --jobs=(count (jobs -p)); end; "##;