feat: add support for cmd (#3277)

* feat: add support for cmd

* add preprompt and precmd support

* add keymap support

* add info about minimum Clink version

* simplify escaping

* add handling for cmd custom commands

* add support for transient_prompt and transient_rprompt

* Revert 914057952508e81e20086fcb707ba2a0be85fdd3

This reverts commit "add support for transient_prompt and transient_rprompt"

* Apply suggestions from code review

* disable cmd shell custom commands

* any shell other than cmd can be used

* better error and correct script location

* move shell check in `map_no_escaping`
This commit is contained in:
Rashil Gandhi 2022-01-10 11:17:53 +05:30 committed by GitHub
parent 20a78c1153
commit c335b4267b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 255 additions and 10 deletions

View File

@ -287,6 +287,16 @@ shown below. Can't see yours? Have a look at the [extra platform instructions](h
execx($(starship init xonsh))
```
#### Cmd
You need to use [Clink](https://chrisant996.github.io/clink/clink.html) (v1.2.30+) with Cmd. Add the following to a file `starship.lua` and place this file in Clink scripts directory:
```lua
-- starship.lua
load(io.popen('starship init cmd'):read("*a"))()
```
#### Nushell
**Warning** This will change in the future. Only nu version v0.33 or higher is supported.

View File

@ -65,13 +65,13 @@ module.exports = {
head: [
["link", { rel: "icon", href: "/icon.png" }],
["meta", { property: "og:title", content: "Starship: Cross-Shell Prompt" }],
["meta", { property: "og:description", content: "Starship is the minimal, blazing fast, and extremely customizable prompt for any shell! Shows the information you need, while staying sleek and minimal. Quick installation available for Bash, Fish, ZSH, Ion, Tcsh, Elvish, Nu, Xonsh, and Powershell."}],
["meta", { property: "og:description", content: "Starship is the minimal, blazing fast, and extremely customizable prompt for any shell! Shows the information you need, while staying sleek and minimal. Quick installation available for Bash, Fish, ZSH, Ion, Tcsh, Elvish, Nu, Xonsh, Cmd, and Powershell."}],
["meta", { property: "og:type", content: "website" }],
["meta", { property: "og:url", content: "https://starship.rs/" }],
["meta", { property: "og:image", content: "https://starship.rs/icon.png" }],
["meta", { name: "twitter:card", content: "summary"}],
["meta", { name: "twitter:title", content: "Starship: Cross-Shell Prompt"}],
["meta", { name: "twitter:description", content: "Starship is the minimal, blazing fast, and extremely customizable prompt for any shell! Shows the information you need, while staying sleek and minimal. Quick installation available for Bash, Fish, ZSH, Ion, Tcsh, Elvish, Nu, Xonsh, and Powershell."}],
["meta", { name: "twitter:description", content: "Starship is the minimal, blazing fast, and extremely customizable prompt for any shell! Shows the information you need, while staying sleek and minimal. Quick installation available for Bash, Fish, ZSH, Ion, Tcsh, Elvish, Nu, Xonsh, Cmd, and Powershell."}],
["meta", { name: "twitter:image", content: "https://starship.rs/icon.png"}],
["meta", { name: "twitter:alt", content: "Starship: Cross-Shell Prompt"}],
],

View File

@ -16,7 +16,7 @@ footer: ISC Licensed | Copyright © 2019-present Starship Contributors
# Used for the description meta tag, for SEO
metaTitle: "Starship: Cross-Shell Prompt"
description: Starship is the minimal, blazing fast, and extremely customizable prompt for any shell! Shows the information you need, while staying sleek and minimal. Quick installation available for Bash, Fish, ZSH, Ion, Tcsh, Elvish, Nu, Xonsh, and PowerShell.
description: Starship is the minimal, blazing fast, and extremely customizable prompt for any shell! Shows the information you need, while staying sleek and minimal. Quick installation available for Bash, Fish, ZSH, Ion, Tcsh, Elvish, Nu, Xonsh, Cmd, and PowerShell.
---
<div class="center">
@ -158,3 +158,13 @@ description: Starship is the minimal, blazing fast, and extremely customizable p
execx($(starship init xonsh))
```
#### Cmd
You need to use [Clink](https://chrisant996.github.io/clink/clink.html) (v1.2.30+) with Cmd. Add the following to a file `starship.lua` and place this file in Clink scripts directory:
```lua
-- starship.lua
load(io.popen('starship init cmd'):read("*a"))()
```

View File

@ -10,6 +10,38 @@ The configurations in this section are subject to change in future releases of S
:::
## Custom pre-prompt and pre-execution Commands in Cmd
Clink provides extremely flexible APIs to run pre-prompt and pre-exec commands
in Cmd shell. It is fairly simple to use with Starship. Make the following changes
to your `starship.lua` file as per your requirements:
- To run a custom function right before the prompt is drawn, define a new
function called `starship_preprompt_user_func`. This function receives
the current prompt as a string that you can utilize. For example, to
draw a rocket before the prompt, you would do
```lua
function starship_preprompt_user_func(prompt)
print("🚀")
end
load(io.popen('starship init cmd'):read("*a"))()
```
- To run a custom function right before a command is executed, define a new
function called `starship_precmd_user_func`. This function receives
the current commandline as a string that you can utilize. For example, to
print the command that's about to be executed, you would do
```lua
function starship_precmd_user_func(line)
print("Executing: "..line)
end
load(io.popen('starship init cmd'):read("*a"))()
```
## Custom pre-prompt and pre-execution Commands in Bash
Bash does not have a formal preexec/precmd framework like most other shells.
@ -62,7 +94,7 @@ function Invoke-Starship-PreCommand {
Some shell prompts will automatically change the window title for you (e.g. to
reflect your working directory). Fish even does it by default.
Starship does not do this, but it's fairly straightforward to add this
functionality to `bash` or `zsh`.
functionality to `bash`, `zsh`, `cmd` or `powershell`.
First, define a window title change function (identical in bash and zsh):
@ -100,6 +132,16 @@ function set_win_title(){
starship_precmd_user_func="set_win_title"
```
For Cmd, you can change the window title using the `starship_preprompt_user_func` function.
```lua
function starship_preprompt_user_func(prompt)
console.settitle(os.getenv('USERNAME').."@"..os.getenv('COMPUTERNAME')..": "..os.getcwd())
end
load(io.popen('starship init cmd'):read("*a"))()
```
You can also set a similar output with PowerShell by creating a function named `Invoke-Starship-PreCommand`.
```powershell
@ -121,7 +163,7 @@ not explicitly used in either `format` or `right_format`.
Note: The right prompt is a single line following the input location. To right align modules above
the input line in a multi-line prompt, see the [fill module](/config/#fill).
`right_format` is currently supported for the following shells: elvish, fish, zsh, xonsh.
`right_format` is currently supported for the following shells: elvish, fish, zsh, xonsh, cmd.
### Example

View File

@ -33,6 +33,12 @@ Equivalently in PowerShell (Windows) would be adding this line to your `$PROFILE
$ENV:STARSHIP_CONFIG = "$HOME\.starship\config.toml"
```
Or for Cmd (Windows) would be adding this line to your `starship.lua`:
```lua
os.setenv('STARSHIP_CONFIG', 'C:\\Users\\user\\.starship\\config.toml')
```
### Logging
By default starship logs warnings and errors into a file named `~/.cache/starship/session_${STARSHIP_SESSION_KEY}.log`, where the session key is corresponding to a instance of your terminal.
@ -48,6 +54,12 @@ Equivalently in PowerShell (Windows) would be adding this line to your `$PROFILE
$ENV:STARSHIP_CACHE = "$HOME\AppData\Local\Temp"
```
Or for Cmd (Windows) would be adding this line to your `starship.lua`:
```lua
os.setenv('STARSHIP_CACHE', 'C:\\Users\\user\\AppData\\Local\\Temp')
```
### Terminology
**Module**: A component in the prompt giving information based on contextual information from your OS. For example, the "nodejs" module shows the version of Node.js that is currently installed on your computer, if your current directory is a Node.js project.
@ -453,7 +465,7 @@ look at [this example](#with-custom-error-shape).
::: warning
`vicmd_symbol` is only supported in fish and zsh.
`vicmd_symbol` is only supported in cmd, fish and zsh.
:::
@ -2807,6 +2819,7 @@ To enable it, set `disabled` to `false` in your configuration file.
| `elvish_indicator` | `esh` | A format string used to represent elvish. |
| `tcsh_indicator` | `tsh` | A format string used to represent tcsh. |
| `xonsh_indicator` | `xsh` | A format string used to represent xonsh. |
| `cmd_indicator` | `cmd` | A format string used to represent cmd. |
| `nu_indicator` | `nu` | A format string used to represent nu. |
| `unknown_indicator` | | The default value to be displayed when the shell is unknown. |
| `format` | `"[$indicator]($style) "` | The format for the module. |

View File

@ -347,6 +347,11 @@ print_install() {
Typically the path is ~\Documents\PowerShell\Microsoft.PowerShell_profile.ps1 or ~/.config/powershell/Microsoft.PowerShell_profile.ps1 on -Nix." \
"Invoke-Expression (&starship init powershell)"
printf " %s\n You need to use Clink (v1.2.30+) with Cmd. Add the following to a file %s and place this file in Clink scripts directory:\n\n\t%s\n\n" \
"${BOLD}${UNDERLINE}Cmd${NO_COLOR}" \
"${BOLD}starship.lua${NO_COLOR}" \
"load(io.popen('starship init cmd'):read(\"*a\"))()"
printf "\n"
}

View File

@ -213,6 +213,7 @@ fn get_config_path(shell: &str) -> Option<PathBuf> {
"elvish" => Some(".elvish/rc.elv"),
"tcsh" => Some(".tcshrc"),
"xonsh" => Some(".xonshrc"),
"cmd" => Some("AppData/Local/clink/starship.lua"),
_ => None,
}
.map(|path| home_dir.join(path))

View File

@ -15,6 +15,7 @@ pub struct ShellConfig<'a> {
pub tcsh_indicator: &'a str,
pub nu_indicator: &'a str,
pub xonsh_indicator: &'a str,
pub cmd_indicator: &'a str,
pub unknown_indicator: &'a str,
pub style: &'a str,
pub disabled: bool,
@ -33,6 +34,7 @@ impl<'a> Default for ShellConfig<'a> {
tcsh_indicator: "tsh",
nu_indicator: "nu",
xonsh_indicator: "xsh",
cmd_indicator: "cmd",
unknown_indicator: "",
style: "white bold",
disabled: true,

View File

@ -292,6 +292,7 @@ impl<'a> Context<'a> {
"tcsh" => Shell::Tcsh,
"nu" => Shell::Nu,
"xonsh" => Shell::Xonsh,
"cmd" => Shell::Cmd,
_ => Shell::Unknown,
}
}
@ -561,6 +562,7 @@ pub enum Shell {
Tcsh,
Nu,
Xonsh,
Cmd,
Unknown,
}
@ -582,7 +584,7 @@ pub struct Properties {
/// The status code of the previously run command
#[clap(short = 's', long = "status")]
pub status_code: Option<i32>,
/// Bash and Zsh support returning codes for each process in a pipeline.
/// Bash, Fish and Zsh support returning codes for each process in a pipeline.
#[clap(long)]
pipestatus: Option<Vec<String>>,
/// The width of the current interactive terminal.
@ -598,7 +600,7 @@ pub struct Properties {
/// The execution duration of the last command, in milliseconds
#[clap(short = 'd', long)]
pub cmd_duration: Option<String>,
/// The keymap of fish/zsh
/// The keymap of fish/zsh/cmd
#[clap(short = 'k', long, default_value = "viins")]
pub keymap: String,
/// The number of currently running jobs

View File

@ -46,6 +46,10 @@ impl StarshipPath {
.map(|s| s.replace("'", "''"))
.map(|s| format!("'{}'", s))
}
/// Command Shell specific path escaping
fn sprint_cmdexe(&self) -> io::Result<String> {
self.str_path().map(|s| format!("\"{}\"", s))
}
fn sprint_posix(&self) -> io::Result<String> {
// On non-Windows platform, return directly.
if cfg!(not(target_os = "windows")) {
@ -172,6 +176,7 @@ pub fn init_stub(shell_name: &str) -> io::Result<()> {
r#"execx($({} init xonsh --print-full-init))"#,
starship.sprint_posix()?
),
"cmd" => print_script(CMDEXE_INIT, &StarshipPath::init()?.sprint_cmdexe()?),
_ => {
eprintln!(
"{0} is not yet supported by starship.\n\
@ -185,6 +190,7 @@ pub fn init_stub(shell_name: &str) -> io::Result<()> {
* zsh\n\
* nu\n\
* xonsh\n\
* cmd\n\
\n\
Please open an issue in the starship repo if you would like to \
see support for {0}:\n\
@ -259,6 +265,8 @@ const NU_INIT: &str = include_str!("starship.nu");
const XONSH_INIT: &str = include_str!("starship.xsh");
const CMDEXE_INIT: &str = include_str!("starship.lua");
#[cfg(test)]
mod tests {
use super::*;
@ -279,4 +287,25 @@ mod tests {
assert_eq!(starship_path.sprint_pwsh()?, r"'C:\''starship.exe'");
Ok(())
}
#[test]
fn escape_cmdexe() -> io::Result<()> {
let starship_path = StarshipPath {
native_path: PathBuf::from(r"C:\starship.exe"),
};
assert_eq!(starship_path.sprint_cmdexe()?, r#""C:\starship.exe""#);
Ok(())
}
#[test]
fn escape_space_cmdexe() -> io::Result<()> {
let starship_path = StarshipPath {
native_path: PathBuf::from(r"C:\Cool Tools\starship.exe"),
};
assert_eq!(
starship_path.sprint_cmdexe()?,
r#""C:\Cool Tools\starship.exe""#
);
Ok(())
}
}

61
src/init/starship.lua Normal file
View File

@ -0,0 +1,61 @@
if (clink.version_encoded or 0) < 10020030 then
error("Starship requires a newer version of Clink; please upgrade to Clink v1.2.30 or later.")
end
local starship_prompt = clink.promptfilter(5)
start_time = os.clock()
end_time = 0
curr_duration = 0
is_line_empty = true
clink.onbeginedit(function ()
end_time = os.clock()
if not is_line_empty then
curr_duration = end_time - start_time
end
end)
clink.onendedit(function (curr_line)
if starship_precmd_user_func ~= nil then
starship_precmd_user_func(curr_line)
end
start_time = os.clock()
if string.len(string.gsub(curr_line, '^%s*(.-)%s*$', '%1')) == 0 then
is_line_empty = true
else
is_line_empty = false
end
end)
function starship_prompt:filter(prompt)
if starship_preprompt_user_func ~= nil then
starship_preprompt_user_func(prompt)
end
return io.popen([[::STARSHIP::]].." prompt"
.." --status="..os.geterrorlevel()
.." --cmd-duration="..math.floor(curr_duration*1000)
.." --terminal-width="..console.getwidth()
.." --keymap="..rl.getvariable('keymap')
):read("*a")
end
function starship_prompt:rightfilter(prompt)
return io.popen([[::STARSHIP::]].." prompt --right"
.." --status="..os.geterrorlevel()
.." --cmd-duration="..math.floor(curr_duration*1000)
.." --terminal-width="..console.getwidth()
.." --keymap="..rl.getvariable('keymap')
):read("*a")
end
local characterset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
local randomkey = ""
math.randomseed(os.time())
for i = 1, 16 do
local rand = math.random(#characterset)
randomkey = randomkey..string.sub(characterset, rand, rand)
end
os.setenv('STARSHIP_SHELL', 'cmd')
os.setenv('STARSHIP_SESSION_KEY', randomkey)

View File

@ -32,7 +32,9 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
// We do some environment detection in src/init.rs to translate.
// The result: in non-vi fish, keymap is always reported as "insert"
let mode = match (&context.shell, keymap) {
(Shell::Fish, "default") | (Shell::Zsh, "vicmd") => ShellEditMode::Normal,
(Shell::Fish, "default") | (Shell::Zsh, "vicmd") | (Shell::Cmd, "vi") => {
ShellEditMode::Normal
}
_ => ASSUMED_MODE,
};
@ -193,4 +195,36 @@ mod test {
.collect();
assert_eq!(expected_other, actual);
}
#[test]
fn cmd_keymap() {
let expected_vicmd = Some(format!("{} ", Color::Green.bold().paint("")));
let expected_specified = Some(format!("{} ", Color::Green.bold().paint("V")));
let expected_other = Some(format!("{} ", Color::Green.bold().paint("")));
// cmd keymap is vi
let actual = ModuleRenderer::new("character")
.shell(Shell::Cmd)
.keymap("vi")
.collect();
assert_eq!(expected_vicmd, actual);
// specified vicmd character
let actual = ModuleRenderer::new("character")
.config(toml::toml! {
[character]
vicmd_symbol = "[V](bold green)"
})
.shell(Shell::Cmd)
.keymap("vi")
.collect();
assert_eq!(expected_specified, actual);
// cmd keymap is other
let actual = ModuleRenderer::new("character")
.shell(Shell::Cmd)
.keymap("visual")
.collect();
assert_eq!(expected_other, actual);
}
}

View File

@ -3,7 +3,7 @@ use std::io::Write;
use std::process::{Command, Output, Stdio};
use std::time::Instant;
use super::{Context, Module, RootModuleConfig};
use super::{Context, Module, RootModuleConfig, Shell};
use crate::{configs::custom::CustomConfig, formatter::StringFormatter, utils::create_command};
@ -58,6 +58,11 @@ pub fn module<'a>(name: &str, context: &'a Context) -> Option<Module<'a>> {
})
.map_no_escaping(|variable| match variable {
"output" => {
if context.shell == Shell::Cmd && config.shell.0.is_empty() {
log::error!("Executing custom commands with cmd shell is not currently supported. Please set a different shell with the \"shell\" option.");
return None;
}
let output = exec_command(config.command, &config.shell.0)?;
let trimmed = output.trim();

View File

@ -26,6 +26,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
Shell::Tcsh => Some(config.tcsh_indicator),
Shell::Nu => Some(config.nu_indicator),
Shell::Xonsh => Some(config.xonsh_indicator),
Shell::Cmd => Some(config.cmd_indicator),
Shell::Unknown => Some(config.unknown_indicator),
},
_ => None,
@ -43,6 +44,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"elvish_indicator" => Some(Ok(config.elvish_indicator)),
"tcsh_indicator" => Some(Ok(config.tcsh_indicator)),
"xonsh_indicator" => Some(Ok(config.xonsh_indicator)),
"cmd_indicator" => Some(Ok(config.cmd_indicator)),
"unknown_indicator" => Some(Ok(config.unknown_indicator)),
_ => None,
})
@ -314,6 +316,35 @@ mod tests {
assert_eq!(expected, actual);
}
#[test]
fn test_cmd_default_format() {
let expected = Some(format!("{} ", Color::White.bold().paint("cmd")));
let actual = ModuleRenderer::new("shell")
.shell(Shell::Cmd)
.config(toml::toml! {
[shell]
disabled = false
})
.collect();
assert_eq!(expected, actual);
}
#[test]
fn test_cmd_custom_format() {
let expected = Some(format!("{} ", Color::Cyan.bold().paint("cmd")));
let actual = ModuleRenderer::new("shell")
.shell(Shell::Cmd)
.config(toml::toml! {
[shell]
cmd_indicator = "[cmd](bold cyan)"
disabled = false
})
.collect();
assert_eq!(expected, actual);
}
#[test]
fn test_custom_format_conditional_indicator_match() {
let expected = Some(format!("{} ", "B"));