fix(custom): improve handling of Powershell prompts (#1237)
To improve overall support of PowerShell in custom modules, the ability to pass arguments to the shell was also added. Additionally, the arguments `-NoProfile -Command -` will be automatically passed to PowerShell, unless other arguments are given by the user.
This commit is contained in:
parent
5731d6674e
commit
09996159f4
|
@ -1420,7 +1420,7 @@ will simply show all custom modules in the order they were defined.
|
|||
| ------------- | ------------------- | ---------------------------------------------------------------------------- |
|
||||
| `command` | | The command whose output should be printed. |
|
||||
| `when` | | A shell command used as a condition to show the module. The module will be shown if the command returns a `0` status code. |
|
||||
| `shell` | | The path to the shell to use to execute the command. If unset, it will fallback to STARSHIP_SHELL and then to "sh". |
|
||||
| `shell` | | [See below](#custom-command-shell) |
|
||||
| `description` | `"<custom module>"` | The description of the module that is shown when running `starship explain`. |
|
||||
| `files` | `[]` | The files that will be searched in the working directory for a match. |
|
||||
| `directories` | `[]` | The directories that will be searched in the working directory for a match. |
|
||||
|
@ -1431,6 +1431,22 @@ will simply show all custom modules in the order they were defined.
|
|||
| `suffix` | `""` | Suffix to display immediately after the command output. |
|
||||
| `disabled` | `false` | Disables this `custom` module. |
|
||||
|
||||
#### Custom command shell
|
||||
|
||||
`shell` accepts a non-empty list of strings, where:
|
||||
- The first string is the path to the shell to use to execute the command.
|
||||
- Other following arguments are passed to the shell.
|
||||
|
||||
If unset, it will fallback to STARSHIP_SHELL and then to "sh" on Linux, and "cmd /C" on Windows.
|
||||
|
||||
If `shell` is not given or only contains one element and Starship detects PowerShell will be used,
|
||||
the following arguments will automatically be added: `-NoProfile -Command -`.
|
||||
This behavior can be avoided by explicitly passing arguments to the shell, e.g.
|
||||
|
||||
```toml
|
||||
shell = ["pwsh", "-Command", "-"]
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
```toml
|
||||
|
|
|
@ -153,6 +153,30 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// A wrapper around `Vec<T>` that implements `ModuleConfig`, and either
|
||||
/// accepts a value of type `T` or a list of values of type `T`.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct VecOr<T>(pub Vec<T>);
|
||||
|
||||
impl<'a, T> ModuleConfig<'a> for VecOr<T>
|
||||
where
|
||||
T: ModuleConfig<'a> + Sized,
|
||||
{
|
||||
fn from_config(config: &'a Value) -> Option<Self> {
|
||||
if let Some(item) = T::from_config(config) {
|
||||
return Some(VecOr(vec![item]));
|
||||
}
|
||||
|
||||
let vec = config
|
||||
.as_array()?
|
||||
.iter()
|
||||
.map(|value| T::from_config(value))
|
||||
.collect::<Option<Vec<T>>>()?;
|
||||
|
||||
Some(VecOr(vec))
|
||||
}
|
||||
}
|
||||
|
||||
/// Root config of starship.
|
||||
pub struct StarshipConfig {
|
||||
pub config: Option<Value>,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig};
|
||||
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig, VecOr};
|
||||
|
||||
use ansi_term::Style;
|
||||
use starship_module_config_derive::ModuleConfig;
|
||||
|
@ -17,7 +17,7 @@ pub struct CustomConfig<'a> {
|
|||
pub symbol: Option<SegmentConfig<'a>>,
|
||||
pub command: &'a str,
|
||||
pub when: Option<&'a str>,
|
||||
pub shell: Option<&'a str>,
|
||||
pub shell: VecOr<&'a str>,
|
||||
pub description: &'a str,
|
||||
pub style: Option<Style>,
|
||||
pub disabled: bool,
|
||||
|
@ -34,7 +34,7 @@ impl<'a> RootModuleConfig<'a> for CustomConfig<'a> {
|
|||
symbol: None,
|
||||
command: "",
|
||||
when: None,
|
||||
shell: None,
|
||||
shell: VecOr::default(),
|
||||
description: "<custom config>",
|
||||
style: None,
|
||||
disabled: false,
|
||||
|
|
|
@ -35,7 +35,7 @@ pub fn module<'a>(name: &'a str, context: &'a Context) -> Option<Module<'a>> {
|
|||
|
||||
if !is_match {
|
||||
if let Some(when) = config.when {
|
||||
is_match = exec_when(when, config.shell);
|
||||
is_match = exec_when(when, &config.shell.0);
|
||||
}
|
||||
|
||||
if !is_match {
|
||||
|
@ -57,7 +57,7 @@ pub fn module<'a>(name: &'a str, context: &'a Context) -> Option<Module<'a>> {
|
|||
module.create_segment("symbol", &symbol);
|
||||
}
|
||||
|
||||
if let Some(output) = exec_command(config.command, config.shell) {
|
||||
if let Some(output) = exec_command(config.command, &config.shell.0) {
|
||||
let trimmed = output.trim();
|
||||
|
||||
if trimmed.is_empty() {
|
||||
|
@ -77,26 +77,31 @@ pub fn module<'a>(name: &'a str, context: &'a Context) -> Option<Module<'a>> {
|
|||
|
||||
/// Return the invoking shell, using `shell` and fallbacking in order to STARSHIP_SHELL and "sh"
|
||||
#[cfg(not(windows))]
|
||||
fn get_shell(shell: Option<&str>) -> std::borrow::Cow<str> {
|
||||
if let Some(forced_shell) = shell {
|
||||
forced_shell.into()
|
||||
fn get_shell<'a, 'b>(shell_args: &'b [&'a str]) -> (std::borrow::Cow<'a, str>, &'b [&'a str]) {
|
||||
if !shell_args.is_empty() {
|
||||
(shell_args[0].into(), &shell_args[1..])
|
||||
} else if let Ok(env_shell) = std::env::var("STARSHIP_SHELL") {
|
||||
env_shell.into()
|
||||
(env_shell.into(), &[] as &[&str])
|
||||
} else {
|
||||
"sh".into()
|
||||
("sh".into(), &[] as &[&str])
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to run the given command in a shell by passing it as `stdin` to `get_shell()`
|
||||
#[cfg(not(windows))]
|
||||
fn shell_command(cmd: &str, shell: Option<&str>) -> Option<Output> {
|
||||
let command = Command::new(get_shell(shell).as_ref())
|
||||
fn shell_command(cmd: &str, shell_args: &[&str]) -> Option<Output> {
|
||||
let (shell, shell_args) = get_shell(shell_args);
|
||||
let mut command = Command::new(shell.as_ref());
|
||||
|
||||
command
|
||||
.args(shell_args)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn();
|
||||
.stderr(Stdio::piped());
|
||||
|
||||
let mut child = match command {
|
||||
handle_powershell(&mut command, &shell, shell_args);
|
||||
|
||||
let mut child = match command.spawn() {
|
||||
Ok(command) => command,
|
||||
Err(_) => {
|
||||
log::debug!(
|
||||
|
@ -120,23 +125,30 @@ fn shell_command(cmd: &str, shell: Option<&str>) -> Option<Output> {
|
|||
/// Attempt to run the given command in a shell by passing it as `stdin` to `get_shell()`,
|
||||
/// or by invoking cmd.exe /C.
|
||||
#[cfg(windows)]
|
||||
fn shell_command(cmd: &str, shell: Option<&str>) -> Option<Output> {
|
||||
let shell = if let Some(shell) = shell {
|
||||
Some(std::borrow::Cow::Borrowed(shell))
|
||||
fn shell_command(cmd: &str, shell_args: &[&str]) -> Option<Output> {
|
||||
let (shell, shell_args) = if !shell_args.is_empty() {
|
||||
(
|
||||
Some(std::borrow::Cow::Borrowed(shell_args[0])),
|
||||
&shell_args[1..],
|
||||
)
|
||||
} else if let Ok(env_shell) = std::env::var("STARSHIP_SHELL") {
|
||||
Some(std::borrow::Cow::Owned(env_shell))
|
||||
(Some(std::borrow::Cow::Owned(env_shell)), &[] as &[&str])
|
||||
} else {
|
||||
None
|
||||
(None, &[] as &[&str])
|
||||
};
|
||||
|
||||
if let Some(forced_shell) = shell {
|
||||
let command = Command::new(forced_shell.as_ref())
|
||||
let mut command = Command::new(forced_shell.as_ref());
|
||||
|
||||
command
|
||||
.args(shell_args)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn();
|
||||
.stderr(Stdio::piped());
|
||||
|
||||
if let Ok(mut child) = command {
|
||||
handle_powershell(&mut command, &forced_shell, shell_args);
|
||||
|
||||
if let Ok(mut child) = command.spawn() {
|
||||
child.stdin.as_mut()?.write_all(cmd.as_bytes()).ok()?;
|
||||
|
||||
return child.wait_with_output().ok();
|
||||
|
@ -159,10 +171,10 @@ fn shell_command(cmd: &str, shell: Option<&str>) -> Option<Output> {
|
|||
}
|
||||
|
||||
/// Execute the given command capturing all output, and return whether it return 0
|
||||
fn exec_when(cmd: &str, shell: Option<&str>) -> bool {
|
||||
fn exec_when(cmd: &str, shell_args: &[&str]) -> bool {
|
||||
log::trace!("Running '{}'", cmd);
|
||||
|
||||
if let Some(output) = shell_command(cmd, shell) {
|
||||
if let Some(output) = shell_command(cmd, shell_args) {
|
||||
if !output.status.success() {
|
||||
log::trace!("non-zero exit code '{:?}'", output.status.code());
|
||||
log::trace!(
|
||||
|
@ -184,10 +196,10 @@ fn exec_when(cmd: &str, shell: Option<&str>) -> bool {
|
|||
}
|
||||
|
||||
/// Execute the given command, returning its output on success
|
||||
fn exec_command(cmd: &str, shell: Option<&str>) -> Option<String> {
|
||||
fn exec_command(cmd: &str, shell_args: &[&str]) -> Option<String> {
|
||||
log::trace!("Running '{}'", cmd);
|
||||
|
||||
if let Some(output) = shell_command(cmd, shell) {
|
||||
if let Some(output) = shell_command(cmd, shell_args) {
|
||||
if !output.status.success() {
|
||||
log::trace!("Non-zero exit code '{:?}'", output.status.code());
|
||||
log::trace!(
|
||||
|
@ -207,14 +219,27 @@ fn exec_command(cmd: &str, shell: Option<&str>) -> Option<String> {
|
|||
}
|
||||
}
|
||||
|
||||
/// If the specified shell refers to PowerShell, adds the arguments "-Command -" to the
|
||||
/// given command.
|
||||
fn handle_powershell(command: &mut Command, shell: &str, shell_args: &[&str]) {
|
||||
let is_powershell = shell.ends_with("pwsh.exe")
|
||||
|| shell.ends_with("powershell.exe")
|
||||
|| shell.ends_with("pwsh")
|
||||
|| shell.ends_with("powershell");
|
||||
|
||||
if is_powershell && shell_args.is_empty() {
|
||||
command.arg("-NoProfile").arg("-Command").arg("-");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
const SHELL: Option<&'static str> = Some("/bin/sh");
|
||||
const SHELL: &[&str] = &["/bin/sh"];
|
||||
#[cfg(windows)]
|
||||
const SHELL: Option<&'static str> = None;
|
||||
const SHELL: &[&str] = &[];
|
||||
|
||||
#[cfg(not(windows))]
|
||||
const FAILING_COMMAND: &str = "false";
|
||||
|
|
Loading…
Reference in New Issue