diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..86e55ee4
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,14 @@
+ZSH := $(shell command -v zsh 2> /dev/null)
+
+all:
+
+zwc:
+ $(MAKE) -C gitstatus zwc
+ $(or $(ZSH),:) -fc 'for f in *.zsh-theme internal/*.zsh; do zcompile -R -- $$f.zwc $$f || exit; done'
+
+minify:
+ $(MAKE) -C gitstatus minify
+ rm -rf -- .git .gitattributes .gitignore LICENSE Makefile README.md font.md powerlevel10k.png
+
+pkg: zwc
+ $(MAKE) -C gitstatus pkg
diff --git a/README.md b/README.md
index 5e5b16e9..22c68d62 100644
--- a/README.md
+++ b/README.md
@@ -2,44 +2,59 @@
[](
https://gitter.im/powerlevel10k/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
+- **THE PROJECT HAS VERY LIMITED SUPPORT**
+- **NO NEW FEATURES ARE IN THE WORKS**
+- **MOST BUGS WILL GO UNFIXED**
+- **HELP REQUESTS WILL BE IGNORED**
+
Powerlevel10k is a theme for Zsh. It emphasizes [speed](#uncompromising-performance),
[flexibility](#extremely-customizable) and [out-of-the-box experience](#configuration-wizard).

-To see what Powerlevel10k is about, scroll through [features](#features).
+- [Getting started](#getting-started)
+- [Features](#features)
+- [Installation](#installation)
+- [Configuration](#configuration)
+- [Fonts](#fonts)
+- [Try it in Docker](#try-it-in-docker)
+- [License](#license)
+- [FAQ](#faq)
+- [Troubleshooting](#troubleshooting)
-Powerlevel9k users, go [here](#powerlevel9k-compatibility).
+## Getting started
-Ready to give Powerlevel10k a try?
-
-1. Install [the recommended font](#meslo-nerd-font-patched-for-powerlevel10k). *Optional but highly
+1. [Install the recommended font](#meslo-nerd-font-patched-for-powerlevel10k). *Optional but highly
recommended.*
-1. Install Powerlevel10k for your plugin manager.
- - [Manual](#manual) ๐ **choose this if confused or uncertain**
- - [Oh My Zsh](#oh-my-zsh)
- - [Prezto](#prezto)
- - [Zim](#zim)
- - [Antigen](#antigen)
- - [Zplug](#zplug)
- - [Zgen](#zgen)
- - [Zplugin](#zplugin)
- - [Zinit](#zinit)
- - [Homebrew](#homebrew)
-1. Restart Zsh.
+1. [Install Powerlevel10k](#installation) itself.
+1. Restart Zsh with `exec zsh`.
1. Type `p10k configure` if the configuration wizard doesn't start automatically.
-The full [table of contents](#table-of-contents) is at the bottom.
-
## Features
+- [Configuration wizard](#configuration-wizard)
+- [Uncompromising performance](#uncompromising-performance)
+- [Powerlevel9k compatibility](#powerlevel9k-compatibility)
+- [Pure compatibility](#pure-compatibility)
+- [Instant prompt](#instant-prompt)
+- [Show on command](#show-on-command)
+- [Transient prompt](#transient-prompt)
+- [Current directory that just works](#current-directory-that-just-works)
+- [Extremely customizable](#extremely-customizable)
+- [Batteries included](#batteries-included)
+- [Extensible](#extensible)
+
### Configuration wizard
Type `p10k configure` to access the builtin configuration wizard right from your terminal.
-
+
+ Screen recording
+
+ 
+
All styles except [Pure](#pure-compatibility) are functionally equivalent. They display the same
information and differ only in presentation.
@@ -78,17 +93,21 @@ segments to fill four prompt lines on both sides of the screen... wait, that's j
one ever does that. Probably impossible, too. The point is, Powerlevel10k prompt is always fast, no
matter what you do!
-
+
+ Screen recording
+
+ 
+
Note how the effect of every command is instantly reflected by the very next prompt.
| Command | Prompt Indicator | Meaning |
|-------------------------------|:----------------:|----------------------------------------------------------------------:|
-| `timew start hack linux` | `๐ก๏ธ hack linux` | time tracking enabled in [timewarrior](https://timewarrior.net/) |
+| `timew start hack linux` | `โ hack linux` | time tracking enabled in [timewarrior](https://timewarrior.net/) |
| `touch x y` | `?2` | 2 untracked files in the Git repo |
| `rm COPYING` | `!1` | 1 unstaged change in the Git repo |
-| `echo 2.7.3 >.python-version` | `๐ 2.7.3` | the current python version in [pyenv](https://github.com/pyenv/pyenv) |
+| `echo 3.7.3 >.python-version` | `๐ 3.7.3` | the current python version in [pyenv](https://github.com/pyenv/pyenv) |
Other Zsh themes capable of displaying the same information either produce prompt lag or print
prompt that doesn't reflect the current state of the system and then refresh it later. With
@@ -101,8 +120,12 @@ Powerlevel10k you get fast prompt *and* up-to-date information.
Powerlevel10k understands all [Powerlevel9k](https://github.com/Powerlevel9k/powerlevel9k)
configuration parameters.
-
+
+ Screen recording
+
+ 
+
[Migration](#installation) from Powerlevel9k to Powerlevel10k is a straightforward process. All
your `POWERLEVEL9K` configuration parameters will still work. Prompt will look the same as before
@@ -124,8 +147,12 @@ but it will be [much faster](#uncompromising-performance) ([certainly](#is-it-re
Powerlevel10k can produce the same prompt as [Pure](https://github.com/sindresorhus/pure). Type
`p10k configure` and select *Pure* style.
-
+
+ Screen recording
+
+ 
+
You can still use Powerlevel10k features such as [transient prompt](#transient-prompt) or
[instant prompt](#instant-prompt) when sporting Pure style.
@@ -144,22 +171,33 @@ If your `~/.zshrc` loads many plugins, or perhaps just a few slow ones
(for example, [pyenv](https://github.com/pyenv/pyenv) or [nvm](https://github.com/nvm-sh/nvm)), you
may have noticed that it takes some time for Zsh to start.
-
+
+ Screen recording
+
+ 
+
Powerlevel10k can remove Zsh startup lag **even if it's not caused by a theme**.
-
+
+ Screen recording
+
+ 
+
This feature is called *Instant Prompt*. You need to explicitly enable it through `p10k configure`
-or [manually](#how-do-i-enable-instant-prompt). It does what it says on the tin -- prints prompt
+or [manually](#how-do-i-configure-instant-prompt). It does what it says on the tin -- prints prompt
instantly upon Zsh startup allowing you to start typing while plugins are still loading.
Other themes *increase* Zsh startup lag -- some by a lot, others by a just a little. Powerlevel10k
*removes* it outright.
-*FAQ:* [How do I enable instant prompt?](#how-do-i-enable-instant-prompt)
+If you are curious about how *Instant Prompt* works, see
+[this section in zsh-bench](https://github.com/romkatv/zsh-bench#instant-prompt).
+
+*FAQ:* [How do I configure instant prompt?](#how-do-i-configure-instant-prompt)
### Show on command
@@ -171,16 +209,19 @@ likewise for AWS, Azure and Google Cloud credentials, prompt will get pretty cro
Enter *Show On Command*. This feature makes prompt segments appear only when they are relevant to
the command you are currently typing.
-
+
+ Screen recording
+
+ 
+
Configs created by `p10k configure` enable show on command for several prompt segments by default.
Here's the relevant parameter for kubernetes context:
```zsh
-# Show prompt segment "kubecontext" only when the command you are typing
-# invokes kubectl, helm, kubens, kubectx, oc, istioctl or kogito.
-typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito'
+# Show prompt segment "kubecontext" only when the command you are typing invokes one of these tools.
+typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens'
```
To customize when different prompt segments are shown, open `~/.p10k.zsh`, search for
@@ -192,8 +233,12 @@ or change their values.
When *Transient Prompt* is enabled through `p10k configure`, Powerlevel10k will trim down every
prompt when accepting a command line.
-
+
+ Screen recording
+
+ 
+
Transient prompt makes it much easier to copy-paste series of commands from the terminal scrollback.
@@ -207,8 +252,12 @@ The current working directory is perhaps the most important prompt segment. Powe
great length to highlight its important parts and to truncate it with the least loss of information
when horizontal space gets scarce.
-
+
+ Screen recording
+
+ 
+
When the full directory doesn't fit, the leftmost segment gets truncated to its shortest unique
prefix. In the screencast, `~/work` becomes `~/wo`. It couldn't be truncated to `~/w` because it
@@ -231,8 +280,12 @@ Directory segments are shown in one of three colors:
Powerlevel10k can be configured to look like any other Zsh theme out there.
-
+
+ Screen recording
+
+ 
+
[Pure](#pure-compatibility), [Powerlevel9k](#powerlevel9k-compatibility) and [robbyrussell](
#how-to-make-powerlevel10k-look-like-robbyrussell-oh-my-zsh-theme) emulations are built-in.
@@ -252,80 +305,97 @@ To ~~ridiculous~~ extravagant:
### Batteries included
-Powerlevel10k comes with dozens of built-in high quality segments. When you run `p10k configure`
-and choose any style except [Pure](#pure-compatibility), many of these segments get enabled by
-default while others be manually enabled by opening `~/.p10k.zsh` and uncommenting them. You can
-enable as many segments as you like. It won't slow down your prompt or Zsh startup.
+Powerlevel10k comes with dozens of built-in high quality prompt segments that can display
+information from a variety of sources. When you run `p10k configure` and choose any style
+except [Pure](#pure-compatibility), many of these segments get enabled by
+default while others can be manually enabled by opening `~/.p10k.zsh` and uncommenting them.
+You can enable as many segments as you like. It won't slow down your prompt or Zsh startup.
| Segment | Meaning |
|--------:|---------|
-| `os_icon` | your OS logo (apple for macOS, swirl for debian, etc.) |
-| `dir` | current working directory |
-| `vcs` | Git repository status |
-| `prompt_char` | multi-functional prompt symbol; changes depending on vi mode: `โฏ`, `โฎ`, `โ
ค`, `โถ` for insert, command, visual and replace mode respectively; turns red on error |
-| `context` | user@hostname |
-| `status` | exit code of the last command |
-| `command_execution_time` | duration (wall time) of the last command |
-| `background_jobs` | presence of background jobs |
-| `time` | current time |
-| `direnv` | [direnv](https://direnv.net/) status |
-| `asdf` | tool versions from [asdf](https://github.com/asdf-vm/asdf) |
-| `virtualenv` | python environment from [venv](https://docs.python.org/3/library/venv.html) |
| `anaconda` | virtual environment from [conda](https://conda.io/) |
-| `pyenv` | python environment from [pyenv](https://github.com/pyenv/pyenv) |
-| `goenv` | go environment from [goenv](https://github.com/syndbg/goenv) |
-| `nodenv` | node.js environment from [nodenv](https://github.com/nodenv/nodenv) |
-| `nvm` | node.js environment from [nvm](https://github.com/nvm-sh/nvm) |
-| `nodeenv` | node.js environment from [nodeenv](https://github.com/ekalinin/nodeenv) |
-| `rbenv` | ruby environment from [rbenv](https://github.com/rbenv/rbenv) |
-| `rvm` | ruby environment from [rvm](https://rvm.io) |
-| `fvm` | flutter environment from [fvm](https://github.com/leoafarias/fvm) |
-| `luaenv` | lua environment from [luaenv](https://github.com/cehoffman/luaenv) |
-| `jenv` | java environment from [jenv](https://github.com/jenv/jenv) |
-| `plenv` | perl environment from [plenv](https://github.com/tokuhirom/plenv) |
-| `phpenv` | php environment from [phpenv](https://github.com/phpenv/phpenv) |
-| `haskell_stack` | haskell version from [stack](https://haskellstack.org/) |
-| `node_version` | [node.js](https://nodejs.org/) version |
-| `go_version` | [go](https://golang.org) version |
-| `rust_version` | [rustc](https://www.rust-lang.org) version |
-| `dotnet_version` | [dotnet](https://dotnet.microsoft.com) version |
-| `php_version` | [php](https://www.php.net/) version |
-| `laravel_version` | [laravel php framework](https://laravel.com/) version |
-| `package` | `name@version` from [package.json](https://docs.npmjs.com/files/package.json) |
-| `kubecontext` | current [kubernetes](https://kubernetes.io/) context |
-| `terraform` | [terraform](https://www.terraform.io) workspace |
+| `asdf` | tool versions from [asdf](https://github.com/asdf-vm/asdf) |
| `aws` | [aws profile](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) |
| `aws_eb_env` | [aws elastic beanstalk](https://aws.amazon.com/elasticbeanstalk/) environment |
| `azure` | [azure](https://docs.microsoft.com/en-us/cli/azure) account name |
+| `background_jobs` | presence of background jobs |
+| `battery` | internal battery state and charge level (yep, batteries *literally* included) |
+| `command_execution_time` | duration (wall time) of the last command |
+| `context` | user@hostname |
+| `cpu_arch` | CPU architecture |
+| `dir` | current working directory |
+| `direnv` | [direnv](https://direnv.net/) status |
+| `disk_usage` | disk usage |
+| `dotnet_version` | [dotnet](https://dotnet.microsoft.com) version |
+| `fvm` | flutter environment from [fvm](https://github.com/leoafarias/fvm) |
| `gcloud` | [google cloud](https://cloud.google.com/) cli account and project |
+| `goenv` | go environment from [goenv](https://github.com/syndbg/goenv) |
| `google_app_cred` | [google application credentials](https://cloud.google.com/docs/authentication/production) |
-| `nordvpn` | [nordvpn](https://nordvpn.com/) connection status |
-| `ranger` | [ranger](https://github.com/ranger/ranger) shell |
-| `nnn` | [nnn](https://github.com/jarun/nnn) shell |
-| `vim_shell` | [vim](https://www.vim.org/) shell (`:sh`) |
+| `go_version` | [go](https://golang.org) version |
+| `haskell_stack` | haskell version from [stack](https://haskellstack.org/) |
+| `ip` | IP address and bandwidth usage for a specified network interface |
+| `java_version` | [java](https://www.java.com/) version |
+| `jenv` | java environment from [jenv](https://github.com/jenv/jenv) |
+| `kubecontext` | current [kubernetes](https://kubernetes.io/) context |
+| `laravel_version` | [laravel php framework](https://laravel.com/) version |
+| `load` | CPU load |
+| `luaenv` | lua environment from [luaenv](https://github.com/cehoffman/luaenv) |
| `midnight_commander` | [midnight commander](https://midnight-commander.org/) shell |
| `nix_shell` | [nix shell](https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html) indicator |
-| `todo` | [todo](https://github.com/todotxt/todo.txt-cli) items |
-| `timewarrior` | [timewarrior](https://timewarrior.net/) tracking status |
-| `taskwarrior` | [taskwarrior](https://taskwarrior.org/) task count |
-| `vpn_ip` | virtual private network indicator |
-| `ip` | IP address and bandwidth usage for a specified network interface |
-| `load` | CPU load |
-| `disk_usage` | disk usage |
-| `ram` | free RAM |
-| `swap` | used swap |
-| `public_ip` | public IP address |
+| `nnn` | [nnn](https://github.com/jarun/nnn) shell |
+| `lf` | [lf](https://github.com/gokcehan/lf) shell |
+| `chezmoi_shell` | [chezmoi](https://www.chezmoi.io/) shell |
+| `nodeenv` | node.js environment from [nodeenv](https://github.com/ekalinin/nodeenv) |
+| `nodenv` | node.js environment from [nodenv](https://github.com/nodenv/nodenv) |
+| `node_version` | [node.js](https://nodejs.org/) version |
+| `nordvpn` | [nordvpn](https://nordvpn.com/) connection status |
+| `nvm` | node.js environment from [nvm](https://github.com/nvm-sh/nvm) |
+| `os_icon` | your OS logo (apple for macOS, swirl for debian, etc.) |
+| `package` | `name@version` from [package.json](https://docs.npmjs.com/files/package.json) |
+| `per_directory_history` | Oh My Zsh [per-directory-history](https://github.com/jimhester/per-directory-history) local/global indicator |
+| `perlbrew` | perl version from [perlbrew](https://github.com/gugod/App-perlbrew) |
+| `phpenv` | php environment from [phpenv](https://github.com/phpenv/phpenv) |
+| `php_version` | [php](https://www.php.net/) version |
+| `plenv` | perl environment from [plenv](https://github.com/tokuhirom/plenv) |
+| `prompt_char` | multi-functional prompt symbol; changes depending on vi mode: `โฏ`, `โฎ`, `V`, `โถ` for insert, command, visual and replace mode respectively; turns red on error |
| `proxy` | system-wide http/https/ftp proxy |
+| `public_ip` | public IP address |
+| `pyenv` | python environment from [pyenv](https://github.com/pyenv/pyenv) |
+| `ram` | free RAM |
+| `ranger` | [ranger](https://github.com/ranger/ranger) shell |
+| `yazi` | [yazi](https://github.com/sxyazi/yazi) shell |
+| `rbenv` | ruby environment from [rbenv](https://github.com/rbenv/rbenv) |
+| `rust_version` | [rustc](https://www.rust-lang.org) version |
+| `rvm` | ruby environment from [rvm](https://rvm.io) |
+| `scalaenv` | scala version from [scalaenv](https://github.com/scalaenv/scalaenv) |
+| `status` | exit code of the last command |
+| `swap` | used swap |
+| `taskwarrior` | [taskwarrior](https://taskwarrior.org/) task count |
+| `terraform` | [terraform](https://www.terraform.io) workspace |
+| `terraform_version` | [terraform](https://www.terraform.io) version |
+| `time` | current time |
+| `timewarrior` | [timewarrior](https://timewarrior.net/) tracking status |
+| `todo` | [todo](https://github.com/todotxt/todo.txt-cli) items |
+| `toolbox` | [toolbox](https://github.com/containers/toolbox) name |
+| `vcs` | Git repository status |
+| `vim_shell` | [vim](https://www.vim.org/) shell (`:sh`) |
+| `virtualenv` | python environment from [venv](https://docs.python.org/3/library/venv.html) |
+| `vi_mode` | vi mode (you don't need this if you've enabled prompt_char) |
+| `vpn_ip` | virtual private network indicator |
| `wifi` | WiFi speed |
-| `battery` | internal battery state and charge level (yep, batteries *literally* included) |
+| `xplr` | [xplr](https://github.com/sayanarijit/xplr) shell |
### Extensible
If there is no prompt segment that does what you need, implement your own. Powerlevel10k provides
public API for defining segments that are as fast and as flexible as built-in ones.
-
+
+ Screen recording
+
+ 
+
On Linux you can fetch current CPU temperature by reading `/sys/class/thermal/thermal_zone0/temp`.
The screencast shows how to define a prompt segment to display this value. Once the segment is
@@ -334,16 +404,47 @@ it out of the box.
Type `p10k help segment` for reference.
+*Note*: If you modify `POWERLEVEL9K_*` parameters in an already initialized interactive shell (as
+opposed to editing `~/.p10k.zsh`), the changes might not be immediately effective. To apply the
+modifications, invoke `p10k reload`. Setting `POWERLEVEL9K_DISABLE_HOT_RELOAD=false` eliminates the
+necessity for `p10k reload` but results in a marginally slower prompt.
+
*Tip*: Prefix names of your own segments with `my_` to avoid clashes with future versions of
Powerlevel10k.
## Installation
+- [Manual](#manual) ๐ **choose this if confused or uncertain**
+- [Oh My Zsh](#oh-my-zsh)
+- [Prezto](#prezto)
+- [Zim](#zim)
+- [Antibody](#antibody)
+- [Antidote](#antidote)
+- [Antigen](#antigen)
+- [Zplug](#zplug)
+- [Zgen](#zgen)
+- [Zplugin](#zplugin)
+- [Zinit](#zinit)
+- [Zi](#zi)
+- [Zap](#zap)
+- [Homebrew](#homebrew)
+- [Arch Linux](#arch-linux)
+- [Alpine Linux](#alpine-linux)
+- [Fig](#fig)
+
### Manual
```zsh
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ~/powerlevel10k
-echo 'source ~/powerlevel10k/powerlevel10k.zsh-theme' >>! ~/.zshrc
+echo 'source ~/powerlevel10k/powerlevel10k.zsh-theme' >>~/.zshrc
+```
+
+Users in China can use the official mirror on gitee.com for faster download.
+ไธญๅฝ็จๆทๅฏไปฅไฝฟ็จ gitee.com ไธ็ๅฎๆน้ๅๅ ้ไธ่ฝฝ.
+
+```zsh
+git clone --depth=1 https://gitee.com/romkatv/powerlevel10k.git ~/powerlevel10k
+echo 'source ~/powerlevel10k/powerlevel10k.zsh-theme' >>~/.zshrc
```
This is the simplest kind of installation and it works even if you are using a plugin manager. Just
@@ -352,11 +453,17 @@ make sure to disable the current theme in your plugin manager. See
### Oh My Zsh
-```zsh
-git clone --depth=1 https://github.com/romkatv/powerlevel10k.git $ZSH_CUSTOM/themes/powerlevel10k
-```
+1. Clone the repository:
+ ```zsh
+ git clone --depth=1 https://github.com/romkatv/powerlevel10k.git "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k"
+ ```
+ Users in China can use the official mirror on gitee.com for faster download.
+ ไธญๅฝ็จๆทๅฏไปฅไฝฟ็จ gitee.com ไธ็ๅฎๆน้ๅๅ ้ไธ่ฝฝ.
-Set `ZSH_THEME="powerlevel10k/powerlevel10k"` in `~/.zshrc`.
+ ```zsh
+ git clone --depth=1 https://gitee.com/romkatv/powerlevel10k.git "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k"
+ ```
+2. Open `~/.zshrc`, find the line that sets `ZSH_THEME`, and change its value to `"powerlevel10k/powerlevel10k"`.
### Prezto
@@ -364,7 +471,15 @@ Add `zstyle :prezto:module:prompt theme powerlevel10k` to `~/.zpreztorc`.
### Zim
-Add `zmodule romkatv/powerlevel10k` to `~/.zimrc` and run `zimfw install`.
+Add `zmodule romkatv/powerlevel10k --use degit` to `~/.zimrc` and run `zimfw install`.
+
+### Antibody
+
+Add `antibody bundle romkatv/powerlevel10k` to `~/.zshrc`.
+
+### Antidote
+
+Add `romkatv/powerlevel10k` to `~/.zsh_plugins.txt`.
### Antigen
@@ -393,15 +508,52 @@ Add `zinit ice depth=1; zinit light romkatv/powerlevel10k` to `~/.zshrc`.
The use of `depth=1` ice is optional. Other types of ice are neither recommended nor officially
supported by Powerlevel10k.
+### Zi
+
+Add `zi ice depth=1; zi light romkatv/powerlevel10k` to `~/.zshrc`.
+
+The use of `depth=1` ice is optional. Other types of ice are neither recommended nor officially
+supported by Powerlevel10k.
+
+### Zap
+
+Add `plug "romkatv/powerlevel10k"` to `~/.zshrc`.
+
### Homebrew
```zsh
-brew install romkatv/powerlevel10k/powerlevel10k
-echo 'source /usr/local/opt/powerlevel10k/powerlevel10k.zsh-theme' >>! ~/.zshrc
+brew install powerlevel10k
+echo "source $(brew --prefix)/share/powerlevel10k/powerlevel10k.zsh-theme" >>~/.zshrc
```
+### Arch Linux
+
+```zsh
+yay -S --noconfirm zsh-theme-powerlevel10k-git
+echo 'source /usr/share/zsh-theme-powerlevel10k/powerlevel10k.zsh-theme' >>~/.zshrc
+```
+
+[zsh-theme-powerlevel10k-git](https://aur.archlinux.org/packages/zsh-theme-powerlevel10k-git/)
+referenced above is the official Powerlevel10k package.
+
+### Alpine Linux
+
+```zsh
+apk add zsh zsh-theme-powerlevel10k
+mkdir -p ~/.local/share/zsh/plugins
+ln -s /usr/share/zsh/plugins/powerlevel10k ~/.local/share/zsh/plugins/
+```
+
+### Fig
+
+Follow the instructions on
+[this page](https://fig.io/plugins/other/powerlevel10k).
+
## Configuration
+- [For new users](#for-new-users)
+- [For Powerlevel9k users](#for-powerlevel9k-users)
+
### For new users
On the first run, Powerlevel10k [configuration wizard](#configuration-wizard) will ask you a few
@@ -461,12 +613,15 @@ the default system fonts. The full choice of style options is available only whe
๐ **Recommended font**: Meslo Nerd Font patched for Powerlevel10k. ๐
-### Meslo Nerd Font patched for Powerlevel10k
+### Meslo Nerd Font patched for Powerlevel10k
-Gorgeous monospace font designed by Jim Lyles for Apple, customized by Andrรฉ Berg, patched by Ryan
-L McIntyre of Nerd Fonts, and hand-edited in FontForge by yours truly. Contains all glyphs and
-symbols that Powerlevel10k may need. Battle-tested in dozens of different terminals on all major
-operating systems.
+Gorgeous monospace font designed by Jim Lyles for Bitstream, customized by the same for Apple,
+further customized by Andrรฉ Berg, and finally patched by yours truly with customized scripts
+originally developed by Ryan L McIntyre of Nerd Fonts. Contains all glyphs and symbols that
+Powerlevel10k may need. Battle-tested in dozens of different terminals on all major operating
+systems.
+
+*FAQ*: [How was the recommended font created?](#how-was-the-recommended-font-created)
#### Automatic font installation
@@ -477,45 +632,164 @@ If you are using a different terminal, proceed with manual font installation.
#### Manual font installation
-Download these four ttf files:
-
-- [MesloLGS NF Regular.ttf](
- https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Regular.ttf)
-- [MesloLGS NF Bold.ttf](
- https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Bold.ttf)
-- [MesloLGS NF Italic.ttf](
- https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Italic.ttf)
-- [MesloLGS NF Bold Italic.ttf](
- https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Bold%20Italic.ttf)
-
-Double-click on each file and click "Install". This will make `MesloLGS NF` font available to all
-applications on your system. Configure your terminal to use this font:
-
-- **iTerm2**: Open *iTerm2 โ Preferences โ Profiles โ Text* and set *Font* to `MesloLGS NF`.
- Alternatively, type `p10k configure` and answer `Yes` when asked whether to install
- *Meslo Nerd Font*.
-- **Apple Terminal** Open *Terminal โ Preferences โ Profiles โ Text*, click *Change* under *Font*
- and select `MesloLGS NF` family.
-- **Hyper**: Open *Hyper โ Edit โ Preferences* and change the value of `fontFamily` under
- `module.exports.config` to `MesloLGS NF`.
-- **Visual Studio Code**: Open *File โ Preferences โ Settings*, enter
- `terminal.integrated.fontFamily` in the search box and set the value to `MesloLGS NF`.
-- **GNOME Terminal** (the default Ubuntu terminal): Open *Terminal โ Preferences* and click on the
- selected profile under *Profiles*. Check *Custom font* under *Text Appearance* and select
- `MesloLGS NF Regular`.
-- **Konsole**: Open *Settings โ Edit Current Profile โ Appearance*, click *Select Font* and select
- `MesloLGS NF Regular`.
-- **Tilix**: Open *Tilix โ Preferences* and click on the selected profile under *Profiles*. Check
- *Custom font* under *Text Appearance* and select `MesloLGS NF Regular`.
-- **Windows Console Host** (the old thing): Click the icon in the top left corner, then
- *Properties โ Font* and set *Font* to `MesloLGS NF`.
-- **Windows Terminal** (the new thing): Open *Settings* (`Ctrl+,`), search for `fontFace` and set
- value to `MesloLGS NF` for every profile.
-- **Termux**: Type `p10k configure` and answer `Yes` when asked whether to install
- *Meslo Nerd Font*.
-
-**IMPORTANT:** Run `p10k configure` after changing terminal font. The old `~/.p10k.zsh` may work
-incorrectly with the new font.
+1. Download these four ttf files:
+ - [MesloLGS NF Regular.ttf](
+ https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Regular.ttf)
+ - [MesloLGS NF Bold.ttf](
+ https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Bold.ttf)
+ - [MesloLGS NF Italic.ttf](
+ https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Italic.ttf)
+ - [MesloLGS NF Bold Italic.ttf](
+ https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Bold%20Italic.ttf)
+1. Double-click on each file and click "Install". This will make `MesloLGS NF` font available to all
+ applications on your system.
+1. Configure your terminal to use this font:
+ - **iTerm2**: Type `p10k configure` and answer `Yes` when asked whether to install
+ *Meslo Nerd Font*. Alternatively, open *iTerm2 โ Preferences โ Profiles โ Text* and set *Font* to
+ `MesloLGS NF`.
+ - **Apple Terminal**: Open *Terminal โ Preferences โ Profiles โ Text*, click *Change* under *Font*
+ and select `MesloLGS NF` family.
+ - **Hyper**: Open *Hyper โ Edit โ Preferences* and change the value of `fontFamily` under
+ `module.exports.config` to `MesloLGS NF`.
+ - **Visual Studio Code**: Open *File โ Preferences โ Settings* (PC) or
+ *Code โ Preferences โ Settings* (Mac), enter `terminal.integrated.fontFamily` in the search box at
+ the top of *Settings* tab and set the value below to `MesloLGS NF`.
+ Consult [this screenshot](
+ https://raw.githubusercontent.com/romkatv/powerlevel10k-media/389133fb8c9a2347929a23702ce3039aacc46c3d/visual-studio-code-font-settings.jpg)
+ to see how it should look like or see [this issue](
+ https://github.com/romkatv/powerlevel10k/issues/671) for extra information.
+ - **GNOME Terminal** (the default Ubuntu terminal): Open *Terminal โ Preferences* and click on the
+ selected profile under *Profiles*. Check *Custom font* under *Text Appearance* and select
+ `MesloLGS NF Regular`.
+ - **Konsole**: Open *Settings โ Edit Current Profile โ Appearance*, click *Select Font* and select
+ `MesloLGS NF Regular`.
+ - **Tilix**: Open *Tilix โ Preferences* and click on the selected profile under *Profiles*. Check
+ *Custom font* under *Text Appearance* and select `MesloLGS NF Regular`.
+ - **Windows Console Host** (the old thing): Click the icon in the top left corner, then
+ *Properties โ Font* and set *Font* to `MesloLGS NF`.
+ - **Windows Terminal** by Microsoft (the new thing): Open *Settings* (Ctrl+,), click
+ either on the selected profile under *Profiles* or on *Defaults*, click *Appearance* and set
+ *Font face* to `MesloLGS NF`.
+ - **Conemu**: Open *Setup โ General โ Fonts* and set *Main console font* to `MesloLGS NF`.
+ - **IntelliJ** (and other IDEs by Jet Brains): Open *IDE โ Edit โ Preferences โ Editor โ
+ Color Scheme โ Console Font*. Select *Use console font instead of the default* and set the font
+ name to `MesloLGS NF`.
+ - **Termux**: Type `p10k configure` and answer `Yes` when asked whether to install
+ *Meslo Nerd Font*.
+ - **Blink**: Type `config`, go to *Appearance*, tap *Add a new font*, tap *Open Gallery*, select
+ *MesloLGS NF.css*, tap *import* and type `exit` in the home view to reload the font.
+ - **Tabby** (formerly **Terminus**): Open *Settings โ Appearance* and set *Font* to `MesloLGS NF`.
+ - **Terminator**: Open *Preferences* using the context menu. Under *Profiles* select the *General*
+ tab (should be selected already), uncheck *Use the system fixed width font* (if not already)
+ and select `MesloLGS NF Regular`. Exit the Preferences dialog by clicking *Close*.
+ - **Guake**: Right Click on an open terminal and open *Preferences*. Under *Appearance*
+ tab, uncheck *Use the system fixed width font* (if not already) and select `MesloLGS NF Regular`.
+ Exit the Preferences dialog by clicking *Close*.
+ - **MobaXterm**: Open *Settings* โ *Configuration* โ *Terminal* โ (under *Terminal look and feel*)
+ and change *Font* to `MesloLGS NF`. If you have *sessions*, you need to change the font in each
+ of them through *Settings* โ right click on an individual session โ *Edit Session* โ *Terminal
+ Settings* โ *Font settings*.
+ - **Asbrรบ Connection Manager**: Open *Preferences โ Local Shell Options โ Look and Feel*, enable
+ *Use these personal options* and change *Font:* under *Terminal UI* to `MesloLGS NF Regular`.
+ To change the font for the remote host connections, go to *Preferences โ Terminal Options โ
+ Look and Feel* and change *Font:* under *Terminal UI* to `MesloLGS NF Regular`.
+ - **Warp**: Open Warp and Navigate to *Settings* then *Appearance*. Scroll down to *Text* Section
+ and under *"Terminal Font"*, select the `MesloLGS NF` font.
+ - **WSLtty**: Right click on an open terminal and then on *Options*. In the *Text* section, under
+ *Font*, click *"Select..."* and set Font to `MesloLGS NF Regular`.
+ - **Yakuake**: Click *โก* โ *Manage Profiles* โ *New* โ *Appearance*. Click *Choose* next to the
+ *Font* dropdown, select `MesloLGS NF` and click *OK*. Click *OK* to save the profile. Select the
+ new profile and click *Set as Default*.
+ - **Alacritty**: Create or open `~/.config/alacritty/alacritty.toml` and add the following
+ section to it:
+ ```toml
+ [font.normal]
+ family = "MesloLGS NF"
+ ```
+ - **foot**: Create or open `~/.config/foot/foot.ini` and add the following section to it:
+ ```ini
+ font=MesloLGS NF:size=12
+ ```
+ - **kitty**: Create or open `~/.config/kitty/kitty.conf` and add the following line to it:
+ ```text
+ font_family MesloLGS NF
+ ```
+ Restart kitty by closing all sessions and opening a new session.
+ - **puTTY**: Set *Window* โ *Appearance* โ *Font* to `MesloLGS NF`. Requires puTTY
+ version >= 0.75.
+ - **WezTerm**: Create or open `$HOME/.config/wezterm/wezterm.lua` and add the following:
+ ```lua
+ local wezterm = require 'wezterm';
+ return {
+ font = wezterm.font("MesloLGS NF"),
+ }
+ ```
+ If the file already exists, only add the line with the font to the existing return.
+ Also add the first line if it is not already present.
+ - **urxvt**: Create or open `~/.Xresources` and add the following line to it:
+ ```text
+ URxvt.font: xft:MesloLGS NF:size=11
+ ```
+ You can adjust the font size to your preference. After changing the config run
+ `xrdb ~/.Xresources` to reload it. The new config is applied to all new terminals.
+ - **xterm**: Create or open `~/.Xresources` and add the following line to it:
+ ```text
+ xterm*faceName: MesloLGS NF
+ ```
+ After changing the config run `xrdb ~/.Xresources` to reload it. The new config is applied to
+ all new terminals.
+ - **Zed**: Open `~/.config/zed/settings.json` and set `terminal.font_family` to `"MesloLGS NF"`.
+ ```jsonc
+ {
+ "terminal": {
+ "font_family": "MesloLGS NF"
+ },
+ // Other settings.
+ }
+ ```
+ - Crostini (Linux on Chrome OS): Open
+ chrome-untrusted://terminal/html/nassh_preferences_editor.html, set *Text font family* to
+ `'MesloLGS NF'` (including the quotes) and *Custom CSS (inline text)* to the following:
+ ```css
+ @font-face {
+ font-family: "MesloLGS NF";
+ src: url("https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/MesloLGS%20NF%20Regular.ttf");
+ font-weight: normal;
+ font-style: normal;
+ }
+ @font-face {
+ font-family: "MesloLGS NF";
+ src: url("https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/MesloLGS%20NF%20Bold.ttf");
+ font-weight: bold;
+ font-style: normal;
+ }
+ @font-face {
+ font-family: "MesloLGS NF";
+ src: url("https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/MesloLGS%20NF%20Italic.ttf");
+ font-weight: normal;
+ font-style: italic;
+ }
+ @font-face {
+ font-family: "MesloLGS NF";
+ src: url("https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/MesloLGS%20NF%20Bold%20Italic.ttf");
+ font-weight: bold;
+ font-style: italic;
+ }
+ ```
+ **_CAVEAT_**: If you open the normal terminal preferences these settings will be overwritten.
+ - **Deepin Terminal**: Create or open `~/.config/deepin/deepin-terminal/config.conf` and add the following section
+ to it:
+ ```ini
+ [basic.interface.font]
+ value = "MesloLGS NF"
+ ```
+ - **Ghostty**: Open *Menu โ Open Configuration* (Linux) or *Ghostty โ Settings...* (Mac) and add
+ the following line:
+ ```text
+ font-family = "MesloLGS NF"
+ ```
+1. Run `p10k configure` to generate a new `~/.p10k.zsh`. The old config may work
+ incorrectly with the new font.
_Using a different terminal and know how to set the font for it? Share your knowledge by sending a
PR to expand the list!_
@@ -523,12 +797,11 @@ PR to expand the list!_
## Try it in Docker
Try Powerlevel10k in Docker. You can safely make any changes to the file system while trying out
-the theme. Once you exit Zsh, the image is deleted.
+the theme. Once you exit Zsh, the container is deleted.
```zsh
-docker run -e TERM -e COLORTERM -it --rm debian:buster bash -uec '
- apt update
- apt install -y git zsh nano vim
+docker run -e TERM -e COLORTERM -e LC_ALL=C.UTF-8 -it --rm alpine sh -uec '
+ apk add git zsh nano vim
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ~/powerlevel10k
echo "source ~/powerlevel10k/powerlevel10k.zsh-theme" >>~/.zshrc
cd ~/powerlevel10k
@@ -547,26 +820,144 @@ Powerlevel10k is released under the
## FAQ
+- [How do I update Powerlevel10k?](#how-do-i-update-powerlevel10k)
+- [How do I uninstall Powerlevel10k?](#how-do-i-uninstall-powerlevel10k)
+- [How do I install Powerlevel10k on a machine without Internet access?](#how-do-i-install-powerlevel10k-on-a-machine-without-internet-access)
+- [Where can I ask for help and report bugs?](#where-can-i-ask-for-help-and-report-bugs)
+- [Which aspects of shell and terminal does Powerlevel10k affect?](#which-aspects-of-shell-and-terminal-does-powerlevel10k-affect)
+- [I'm using Powerlevel9k with Oh My Zsh. How do I migrate?](#im-using-powerlevel9k-with-oh-my-zsh-how-do-i-migrate)
+- [Is it really fast?](#is-it-really-fast)
+- [How do I configure instant prompt?](#how-do-i-configure-instant-prompt)
+- [How do I initialize direnv when using instant prompt?](#how-do-i-initialize-direnv-when-using-instant-prompt)
+- [How do I export GPG_TTY when using instant prompt?](#how-do-i-export-gpg_tty-when-using-instant-prompt)
+- [What do different symbols in Git status mean?](#what-do-different-symbols-in-git-status-mean)
+- [How do I change the format of Git status?](#how-do-i-change-the-format-of-git-status)
+- [Why is Git status from `$HOME/.git` not displayed in prompt?](#why-is-git-status-from-homegit-not-displayed-in-prompt)
+- [Why does Git status sometimes appear grey and then gets colored after a short period of time?](#why-does-git-status-sometimes-appear-grey-and-then-gets-colored-after-a-short-period-of-time)
+- [How do I add username and/or hostname to prompt?](#how-do-i-add-username-andor-hostname-to-prompt)
+- [Why some prompt segments appear and disappear as I'm typing?](#why-some-prompt-segments-appear-and-disappear-as-im-typing)
+- [How do I change prompt colors?](#how-do-i-change-prompt-colors)
+- [Why does Powerlevel10k spawn extra processes?](#why-does-powerlevel10k-spawn-extra-processes)
+- [Are there configuration options that make Powerlevel10k slow?](#are-there-configuration-options-that-make-powerlevel10k-slow)
+- [Is Powerlevel10k fast to load?](#is-powerlevel10k-fast-to-load)
+- [What is the relationship between Powerlevel9k and Powerlevel10k?](#what-is-the-relationship-between-powerlevel9k-and-powerlevel10k)
+- [Does Powerlevel10k always render exactly the same prompt as Powerlevel9k given the same config?](#does-powerlevel10k-always-render-exactly-the-same-prompt-as-powerlevel9k-given-the-same-config)
+- [What is the best prompt style in the configuration wizard?](#what-is-the-best-prompt-style-in-the-configuration-wizard)
+- [How to make Powerlevel10k look like robbyrussell Oh My Zsh theme?](#how-to-make-powerlevel10k-look-like-robbyrussell-oh-my-zsh-theme)
+- [Can prompts for completed commands display error status for *those* commands instead of the commands preceding them?](#can-prompts-for-completed-commands-display-error-status-for-those-commands-instead-of-the-commands-preceding-them)
+- [What is the minimum supported Zsh version?](#what-is-the-minimum-supported-zsh-version)
+- [How were these screenshots and animated gifs created?](#how-were-these-screenshots-and-animated-gifs-created)
+- [How was the recommended font created?](#how-was-the-recommended-font-created)
+- [How to package Powerlevel10k for distribution?](#how-to-package-powerlevel10k-for-distribution)
+
### How do I update Powerlevel10k?
The command to update Powerlevel10k depends on how it was installed.
-| Installation | Update command |
-|-------------------------|------------------------------------------------|
-| [Manual](#manual) | `git -C ~/powerlevel10k pull` |
-| [Oh My Zsh](#oh-my-zsh) | `git -C $ZSH_CUSTOM/themes/powerlevel10k pull` |
-| [Prezto](#prezto) | `zprezto-update` |
-| [Zim](#zim) | `zimfw update` |
-| [Antigen](#antigen) | `antigen update` |
-| [Zplug](#zplug) | `zplug update` |
-| [Zgen](#zgen) | `zgen update` |
-| [Zplugin](#zplugin) | `zplugin update` |
-| [Zinit](#zinit) | `zinit update` |
-| [Homebrew](#homebrew) | `brew update && brew upgrade` |
+| Installation | Update command |
+|-------------------------------|-------------------------------------------------------------|
+| [Manual](#manual) | `git -C ~/powerlevel10k pull` |
+| [Oh My Zsh](#oh-my-zsh) | `git -C "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k" pull` |
+| [Prezto](#prezto) | `zprezto-update` |
+| [Zim](#zim) | `zimfw update` |
+| [Antigen](#antigen) | `antigen update` |
+| [Antidote](#antidote) | `antidote update` |
+| [Zplug](#zplug) | `zplug update` |
+| [Zgen](#zgen) | `zgen update` |
+| [Zplugin](#zplugin) | `zplugin update` |
+| [Zinit](#zinit) | `zinit update` |
+| [Zi](#zi) | `zi update` |
+| [Zap](#zap) | `zap update` |
+| [Homebrew](#homebrew) | `brew update && brew upgrade` |
+| [Arch Linux](#arch-linux) | `yay -S --noconfirm zsh-theme-powerlevel10k-git` |
+| [Alpine Linux](#alpine-linux) | `apk update && apk upgrade` |
**IMPORTANT**: Restart Zsh after updating Powerlevel10k. [Do not use `source ~/.zshrc`](
#weird-things-happen-after-typing-source-zshrc).
+### How do I uninstall Powerlevel10k?
+
+1. Remove all references to "p10k" from `~/.zshrc`. You might have this snippet at the top:
+ ```zsh
+ if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
+ source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
+ fi
+ ```
+ And this at the bottom:
+ ```zsh
+ [[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh
+ ```
+ These are added by the [configuration wizard](#configuration-wizard). Remove them.
+2. Remove all references to "powerlevel10k" from `~/.zshrc`, `~/.zpreztorc` and `~/.zimrc` (some
+ of these files may be missing -- this is normal). These references have been added manually by
+ yourself when installing Powerlevel10k. Refer to the [installation instructions](#installation)
+ if you need a reminder.
+3. Verify that all references to "p10k" and "powerlevel10k" are gone from `~/.zshrc`, `~/.zpreztorc`
+ and `~/.zimrc`.
+ ```zsh
+ grep -E 'p10k|powerlevel10k' ~/.zshrc ~/.zpreztorc ~/.zimrc 2>/dev/null
+ ```
+ If this command produces output, there are still references to "p10k" or "powerlevel10k". You
+ need to remove them.
+4. Delete Powerlevel10k configuration file. This file is created by the
+ [configuration wizard](#configuration-wizard) and may contain manual edits by yourself.
+ ```zsh
+ rm -f ~/.p10k.zsh
+ ```
+5. Delete Powerlevel10k source files. These files have been downloaded when you've installed
+ Powerlevel10k. The command to delete them depends on which installation method you'd chosen.
+ Refer to the [installation instructions](#installation) if you need a reminder.
+
+ | Installation | Uninstall command |
+ |-------------------------------|------------------------------------------------------------------|
+ | [Manual](#manual) | `rm -rf ~/powerlevel10k` |
+ | [Oh My Zsh](#oh-my-zsh) | `rm -rf -- "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k"` |
+ | [Prezto](#prezto) | n/a |
+ | [Zim](#zim) | `zimfw uninstall` |
+ | [Antigen](#antigen) | `antigen purge romkatv/powerlevel10k` |
+ | [Antidote](#antidote) | `antidote purge romkatv/powerlevel10k` |
+ | [Zplug](#zplug) | `zplug clean` |
+ | [Zgen](#zgen) | `zgen reset` |
+ | [Zplugin](#zplugin) | `zplugin delete romkatv/powerlevel10k` |
+ | [Zinit](#zinit) | `zinit delete romkatv/powerlevel10k` |
+ | [Zi](#zi) | `zi delete romkatv/powerlevel10k` |
+ | [Zap](#zap) | `zsh -ic 'zap clean'` |
+ | [Homebrew](#homebrew) | `brew uninstall powerlevel10k` |
+ | [Arch Linux](#arch-linux) | `yay -R --noconfirm zsh-theme-powerlevel10k-git` |
+ | [Alpine Linux](#alpine-linux) | `apk del zsh-theme-powerlevel10k` |
+6. Restart Zsh. [Do not use `source ~/.zshrc`](#weird-things-happen-after-typing-source-zshrc).
+7. Delete Powerlevel10k cache files.
+ ```zsh
+ rm -rf -- "${XDG_CACHE_HOME:-$HOME/.cache}"/p10k-*(N) "${XDG_CACHE_HOME:-$HOME/.cache}"/gitstatus
+ ```
+
+### How do I install Powerlevel10k on a machine without Internet access?
+
+1. Run this command on the machine without Internet access:
+ ```sh
+ uname -sm | tr '[A-Z]' '[a-z]'
+ ```
+2. Run these commands on a machine connected to the Internet after replacing the value of
+ `target_uname` with the output of the previous command:
+ ```sh
+ target_uname="replace this with the output of the previous command"
+ git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ~/powerlevel10k
+ GITSTATUS_CACHE_DIR="$HOME"/powerlevel10k/gitstatus/usrbin ~/powerlevel10k/gitstatus/install -f -s "${target_uname% *}" -m "${target_uname#* }"
+ ```
+3. Copy `~/powerlevel10k` from the machine connected to the Internet to the one without Internet
+ access.
+4. Add `source ~/powerlevel10k/powerlevel10k.zsh-theme` to `~/.zshrc` on the machine without
+ Internet access:
+ ```zsh
+ echo 'source ~/powerlevel10k/powerlevel10k.zsh-theme' >>~/.zshrc
+ ```
+5. If `~/.zshrc` on the machine without Internet access sets `ZSH_THEME`, remove that line.
+ ```zsh
+ sed -i.bak '/^ZSH_THEME=/d' ~/.zshrc
+ ```
+
+To update, remove `~/powerlevel10k` on both machines and repeat steps 1-3.
+
### Where can I ask for help and report bugs?
The best way to ask for help and to report bugs is to [open an issue](
@@ -590,26 +981,31 @@ Powerlevel10k defines prompt and nothing else. It sets [prompt-related options](
https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/prompt-highlight.png)
Everything within the highlighted areas on the screenshot is produced by Powerlevel10k.
-Powerlevel10k has no control over the terminal content or color outside these areas.
+Powerlevel10k has no control over the terminal content or colors outside these areas.
Powerlevel10k does not affect:
-- Terminal window title.
+- Terminal window/tab title.
- Colors used by `ls`.
-- Content and style of command completions.
+- The behavior of `git` command.
+- The content and style of Tab completions.
- Command line colors (syntax highlighting, autosuggestions, etc.).
+- Key bindings.
+- Aliases.
- Prompt parameters other than `PS1` and `RPS1`.
- Zsh options other than those [related to prompt](
http://zsh.sourceforge.net/Doc/Release/Options.html#Prompting).
+- The set of available commands. Powerlevel10k does not install any new commands
+ with the only exception of `p10k`.
### I'm using Powerlevel9k with Oh My Zsh. How do I migrate?
1. Run this command:
```zsh
# Add powerlevel10k to the list of Oh My Zsh themes.
-git clone --depth=1 https://github.com/romkatv/powerlevel10k.git $ZSH_CUSTOM/themes/powerlevel10k
+git clone --depth=1 https://github.com/romkatv/powerlevel10k.git "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k"
# Replace ZSH_THEME="powerlevel9k/powerlevel9k" with ZSH_THEME="powerlevel10k/powerlevel10k".
-sed 's/powerlevel9k/powerlevel10k/g' -i ~/.zshrc
+sed -i.bak 's/powerlevel9k/powerlevel10k/g' ~/.zshrc
# Restart Zsh.
exec zsh
```
@@ -627,36 +1023,14 @@ exec zsh
### Is it really fast?
-Yes.
+Yes. See [zsh-bench](https://github.com/romkatv/zsh-bench) or a direct comparison with
+[Powerlevel9k](https://asciinema.org/a/NHRjK3BMePw66jtRVY2livHwZ) and
+[Spaceship](https://asciinema.org/a/253094).
-[](
- https://asciinema.org/a/NHRjK3BMePw66jtRVY2livHwZ)
-
-Benchmark results obtained with
-[zsh-prompt-benchmark](https://github.com/romkatv/zsh-prompt-benchmark) on an Intel i9-7900X
-running Ubuntu 18.04 with the config from the demo.
-
-| Theme | Prompt Latency |
-|---------------------|---------------:|
-| powerlevel9k/master | 1046 ms |
-| powerlevel9k/next | 1005 ms |
-| **powerlevel10k** | **8.7 ms** |
-
-Powerlevel10k is over 100 times faster than Powerlevel9k in this benchmark.
-
-In fairness, Powerlevel9k has acceptable latency when given a spartan configuration. If all you need
-is the current directory without truncation or shortening, Powerlevel9k can render it for you in
-17 ms. Powerlevel10k can do the same 30 times faster but it won't matter in practice because 17 ms
-is fast enough (the threshold where latency becomes noticeable is around 50 ms). You have to be
-careful with Powerlevel9k configuration as it's all too easy to make prompt frustratingly slow.
-Powerlevel10k, on the other hand, doesn't require trading latency for utility -- it's virtually
-instant with any configuration. It stays well below the 50 ms mark, leaving most of the latency
-budget for other plugins you might install.
-
-### How do I enable instant prompt?
+### How do I configure instant prompt?
See [instant prompt](#instant-prompt) to learn about instant prompt. This section explains how you
-can enable it and lists caveats that you should be aware of.
+can enable and configure it and lists caveats that you should be aware of.
Instant prompt can be enabled either through `p10k configure` or by manually adding the following
code snippet at the top of `~/.zshrc`:
@@ -722,25 +1096,79 @@ to move it above the instant prompt preamble or to suppress its output. You can
instant prompt with `POWERLEVEL9K_INSTANT_PROMPT=off`. Do this if instant prompt breaks Zsh
initialization and you don't know how to fix it.
+The value of `POWERLEVEL9K_INSTANT_PROMPT` can be changed by running `p10k configure` and selecting
+the appropriate option on the *Instant Prompt* screen. Alternatively, you can search for
+`POWERLEVEL9K_INSTANT_PROMPT` in the existing `~/.p10k.zsh` and change its value there.
+
*Note*: Instant prompt requires Zsh >= 5.4. It's OK to enable it even when using an older version of
Zsh but it won't do anything.
+*FAQ*:
+
+- [How do I initialize direnv when using instant prompt?](
+ #how-do-i-initialize-direnv-when-using-instant-prompt)
+- [How do I export GPG_TTY when using instant prompt?](
+ #how-do-i-export-gpg_tty-when-using-instant-prompt)
+
+### How do I initialize direnv when using instant prompt?
+
+If you've enabled [instant prompt](#instant-prompt), you should have these lines at the top of
+`~/.zshrc`:
+
+```zsh
+if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
+ source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
+fi
+```
+
+To initialize direnv you need to add one line above that block and one line below it.
+
+```zsh
+(( ${+commands[direnv]} )) && emulate zsh -c "$(direnv export zsh)"
+
+if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
+ source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
+fi
+
+(( ${+commands[direnv]} )) && emulate zsh -c "$(direnv hook zsh)"
+```
+
+*Related*: [How do I export GPG_TTY when using instant prompt?](
+ #how-do-i-export-gpg_tty-when-using-instant-prompt)
+
+### How do I export GPG_TTY when using instant prompt?
+
+You can export `GPG_TTY` like this anywhere in `~/.zshrc`:
+
+```zsh
+export GPG_TTY=$TTY
+```
+
+This works whether you are using [instant prompt](#instant-prompt) or not. It works even if you
+aren't using powerlevel10k. As an extra bonus, it's much faster than the commonly used
+`export GPG_TTY=$(tty)`.
+
+*Related*: [How do I initialize direnv when using instant prompt?](
+ #how-do-i-initialize-direnv-when-using-instant-prompt)
+
### What do different symbols in Git status mean?
When using Lean, Classic or Rainbow style, Git status may look like this:
```text
-feature:master โฃ42โก42 โ 42โข42 *42 merge ~42 +42 !42 ?42
+feature:master wip โฃ42โก42 โ 42โข42 *42 merge ~42 +42 !42 ?42
```
| Symbol | Meaning | Source |
| --------- | -------------------------------------------------------------------- | ------------------------------------------------------ |
| `feature` | current branch; replaced with `#tag` or `@commit` if not on a branch | `git status --ignore-submodules=dirty` |
-| `master` | remote tracking branch; only shown if different from local branch | `git rev-parse --abbrev-ref --symbolic-full-name @{u}` |
-| `โฃ42` | this many commits behind the remote | `git status --ignore-submodules=dirty` |
-| `โก42` | this many commits ahead of the remote | `git status --ignore-submodules=dirty` |
-| `โ 42` | this many commits behind the push remote | `git rev-list --left-right --count HEAD...@{push}` |
-| `โข42` | this many commits ahead of the push remote | `git rev-list --left-right --count HEAD...@{push}` |
+| `master` | remote tracking branch; only shown if different from local branch | `git rev-parse --abbrev-ref --symbolic-full-name @{upstream}` |
+| `wip` | the latest commit's summary contains "wip" or "WIP" | `git show --pretty=%s --no-patch HEAD` |
+| `=` | up to date with the remote (neither ahead nor behind) | `git rev-list --count HEAD...@{upstream}` |
+| `โฃ42` | this many commits behind the remote | `git rev-list --right-only --count HEAD...@{upstream}` |
+| `โก42` | this many commits ahead of the remote | `git rev-list --left-only --count HEAD...@{upstream}` |
+| `โ 42` | this many commits behind the push remote | `git rev-list --right-only --count HEAD...@{push}` |
+| `โข42` | this many commits ahead of the push remote | `git rev-list --left-only --count HEAD...@{push}` |
| `*42` | this many stashes | `git stash list` |
| `merge` | repository state | `git status --ignore-submodules=dirty` |
| `~42` | this many merge conflicts | `git status --ignore-submodules=dirty` |
@@ -766,7 +1194,7 @@ When using Lean, Classic or Rainbow style, `~/.p10k.zsh` contains the following
```zsh
# Don't show Git status in prompt for repositories whose workdir matches this pattern.
# For example, if set to '~', the Git repository at $HOME/.git will be ignored.
-# Multiple patterns can be combined with '|': '~|~/some/dir'.
+# Multiple patterns can be combined with '|': '~(|/foo)|/bar/baz/*'.
typeset -g POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN='~'
```
@@ -779,13 +1207,27 @@ tl;dr: When Git status in prompt is greyed out, it means Powerlevel10k is curren
up-to-date Git status in the background. Prompt will get automatically refreshed when this
computation completes.
-When your current directory is withing a Git repository, Powerlevel10k computes up-to-date Git
+When your current directory is within a Git repository, Powerlevel10k computes up-to-date Git
status after every command. If the repository is large, or the machine is slow, this computation
-can take quite a bit of time. If it takes longer than 20 milliseconds (configurable via
+can take quite a bit of time. If it takes longer than 10 milliseconds (configurable via
`POWERLEVEL9K_VCS_MAX_SYNC_LATENCY_SECONDS`), Powerlevel10k displays the last known Git status in
grey and continues to compute up-to-date Git status in the background. When the computation
completes, Powerlevel10k refreshes prompt with new information, this time with colored Git status.
+When using *Rainbow* style, Git status is displayed as black on grey while it's still being
+computed. Depending on the terminal color palette, this may be difficult to read. In this case you
+might want to change the background color to something lighter for more contrast. To do that, open
+`~/.p10k.zsh`, search for `POWERLEVEL9K_VCS_LOADING_BACKGROUND`, uncomment it if it's commented out,
+and change the value.
+
+```zsh
+typeset -g POWERLEVEL9K_VCS_LOADING_BACKGROUND=244
+```
+
+Type `source ~/.p10k.zsh` to apply your changes to the current Zsh session.
+
+*Related*: [How do I change prompt colors?](#how-do-i-change-prompt-colors)
+
### How do I add username and/or hostname to prompt?
When using Lean, Classic or Rainbow style, prompt shows `username@hostname` when you are logged in
@@ -827,9 +1269,9 @@ Prompt segments can be configured to be shown only when the current command you
a relevant tool.
```zsh
-# Show prompt segment "kubecontext" only when the command you are typing
-# invokes kubectl, helm, kubens, kubectx, oc, istioctl or kogito.
-typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito'
+# Show prompt segment "kubecontext" only when the command you are typing invokes
+# invokes kubectl, helm, or kubens.
+typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens'
```
Configs created by `p10k configure` may contain parameters of this kind. To customize when different
@@ -845,7 +1287,7 @@ function kube-toggle() {
if (( ${+POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND} )); then
unset POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND
else
- POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito'
+ POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens'
fi
p10k reload
if zle; then
@@ -898,12 +1340,29 @@ terminals. Many terminals also support customization of these colors through col
Type `source ~/.p10k.zsh` to apply your changes to the current Zsh session.
-To see how different colors look in your terminal, run the following command:
+To see how different numbered colors look in your terminal, run the following command:
```zsh
for i in {0..255}; do print -Pn "%K{$i} %k%F{$i}${(l:3::0:)i}%f " ${${(M)$((i%6)):#3}:+$'\n'}; done
```
+If your terminal supports truecolor, you can use 24-bit colors in the `#RRGGBB` format in addition
+to the numbered colors.
+
+```zsh
+typeset -g POWERLEVEL9K_TIME_FOREGROUND='#FF0000'
+```
+
+*Related:*
+ - [Directory is difficult to see in prompt when using Rainbow style.](
+ #directory-is-difficult-to-see-in-prompt-when-using-rainbow-style)
+ - [Incorrect foreground color in VSCode Terminal.](#incorrect-foreground-color-in-vscode-terminal)
+
+By default, VSCode Terminal may arbitrarily replace the foreground color of your choice with a
+different color. This behavior can be
+[turned off](https://code.visualstudio.com/docs/terminal/appearance#_minimum-contrast-ratio) in
+VSCode settings.
+
### Why does Powerlevel10k spawn extra processes?
Powerlevel10k uses [gitstatus](https://github.com/romkatv/gitstatus) as the backend behind `vcs`
@@ -921,26 +1380,13 @@ prompt latency when using Powerlevel10k, please
### Is Powerlevel10k fast to load?
-Yes, provided that you are using Zsh >= 5.4.
-
-Loading time, or time to first prompt, can be measured with the following benchmark:
-
-```zsh
-time (repeat 1000 zsh -dfis <<< 'source ~/powerlevel10k/powerlevel10k.zsh-theme')
-```
-
-*Note:* This measures time to first complete prompt. Powerlevel10k can also display a
-[limited prompt](#instant-prompt) before the full-featured prompt is ready.
-
-Running this command with `~/powerlevel10k` as the current directory on the same machine as in the
-[prompt benchmark](#is-it-really-fast) takes 29 seconds (29 ms per invocation). This is about 6
-times faster than powerlevel9k/master and 17 times faster than powerlevel9k/next.
+Yes. See [zsh-bench](https://github.com/romkatv/zsh-bench).
### What is the relationship between Powerlevel9k and Powerlevel10k?
Powerlevel10k was forked from Powerlevel9k in March 2019 after a week-long discussion in
[powerlevel9k#1170](https://github.com/Powerlevel9k/powerlevel9k/issues/1170). Powerlevel9k was
-already a mature project with large user base and release cycle measured in months. Powerlevel10k
+already a mature project with a large user base and a release cycle measured in months. Powerlevel10k
was spun off to iterate on performance improvements and new features at much higher pace.
Powerlevel9k and Powerlevel10k are independent projects. When using one, you shouldn't install the
@@ -1009,7 +1455,7 @@ Similarly, if you enable transient prompt, sparse prompt (with an empty line bef
great choice.
If you are using vi keymap, choose prompt with `prompt_char` in it (shown as green `โฏ` in the
-wizard). This symbol changes depending on vi mode: `โฏ`, `โฎ`, `โ
ค`, `โถ` for insert, command, visual
+wizard). This symbol changes depending on vi mode: `โฏ`, `โฎ`, `V`, `โถ` for insert, command, visual
and replace mode respectively. When a command fails, the symbol turns red. *Lean* style always has
`prompt_char` in it. *Rainbow* and *Classic* styles have it only in the two-line configuration
without left frame.
@@ -1044,16 +1490,9 @@ command is reflected in the *next* prompt.
For details, see [this post on /r/zsh](
https://www.reddit.com/r/zsh/comments/eg49ff/powerlevel10k_prompt_history_exit_code_colors/fc5huku).
-### Is there an AUR package for Powerlevel10k?
-
-There are two:
-[zsh-theme-powerlevel10k-git](https://aur.archlinux.org/packages/zsh-theme-powerlevel10k-git/) and
-[zsh-theme-powerlevel10k](https://aur.archlinux.org/packages/zsh-theme-powerlevel10k/). Both
-packages are owned by unaffiliated volunteers.
-
### What is the minimum supported Zsh version?
-Zsh 5.1 or newer should work. Fast startup requires Zsh >= 5.4.
+Zsh 5.3 or newer should work. Fast startup requires Zsh >= 5.4.
### How were these screenshots and animated gifs created?
@@ -1067,8 +1506,78 @@ custom background color (`#171A1B` instead of `#2E3436` -- twice as dark).
Syntax highlighting, where present, was provided by [zsh-syntax-highlighting](
https://github.com/zsh-users/zsh-syntax-highlighting).
+### How was the recommended font created?
+
+[The recommended font](#meslo-nerd-font-patched-for-powerlevel10k) is the product of many
+individuals. Its origin is *Bitstream Vera Sans Mono*, which has given birth to *Menlo*, which in
+turn has spawned *Meslo*. Finally, extra glyphs have been added to *Meslo* with scripts forked
+from Nerd Fonts. The final font is released under the terms of
+[Apache License](
+ https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/MesloLGS%20NF%20License.txt).
+
+MesloLGS NF font can be recreated with the following command (requires `git` and `docker`):
+
+```zsh
+git clone --depth=1 https://github.com/romkatv/nerd-fonts.git
+cd nerd-fonts
+./build 'Meslo/S/*'
+```
+
+If everything goes well, four `ttf` files will appear in `./out`.
+
+### How to package Powerlevel10k for distribution?
+
+It's currently neither easy nor recommended to package and distribute Powerlevel10k. There are no
+instructions you can follow that would allow you to easily update your package when new versions of
+Powerlevel10k are released. This may change in the future but not soon.
+
## Troubleshooting
+- [`[oh-my-zsh] theme 'powerlevel10k/powerlevel10k' not found`](#oh-my-zsh-theme-powerlevel10kpowerlevel10k-not-found)
+- [Question mark in prompt](#question-mark-in-prompt)
+- [Icons, glyphs or powerline symbols don't render](#icons-glyphs-or-powerline-symbols-dont-render)
+- [Sub-pixel imperfections around powerline symbols](#sub-pixel-imperfections-around-powerline-symbols)
+- [Error: character not in range](#error-character-not-in-range)
+- [Cursor is in the wrong place](#cursor-is-in-the-wrong-place)
+- [Prompt wrapping around in a weird way](#prompt-wrapping-around-in-a-weird-way)
+- [Right prompt is in the wrong place](#right-prompt-is-in-the-wrong-place)
+- [Configuration wizard runs automatically every time Zsh is started](#configuration-wizard-runs-automatically-every-time-zsh-is-started)
+- [Some prompt styles are missing from the configuration wizard](#some-prompt-styles-are-missing-from-the-configuration-wizard)
+- [Cannot install the recommended font](#cannot-install-the-recommended-font)
+- [Extra or missing spaces in prompt compared to Powerlevel9k](#extra-or-missing-spaces-in-prompt-compared-to-powerlevel9k)
+ - [Extra space without background on the right side of right prompt](#extra-space-without-background-on-the-right-side-of-right-prompt)
+ - [Extra or missing spaces around icons](#extra-or-missing-spaces-around-icons)
+- [Weird things happen after typing `source ~/.zshrc`](#weird-things-happen-after-typing-source-zshrc)
+- [Transient prompt stops working after some time](#transient-prompt-stops-working-after-some-time)
+- [Cannot make Powerlevel10k work with my plugin manager](#cannot-make-powerlevel10k-work-with-my-plugin-manager)
+- [Directory is difficult to see in prompt when using Rainbow style](#directory-is-difficult-to-see-in-prompt-when-using-rainbow-style)
+- [Incorrect foreground color in VSCode Terminal.](#incorrect-foreground-color-in-vscode-terminal)
+- [Horrific mess when resizing terminal window](#horrific-mess-when-resizing-terminal-window)
+- [Icons cut off in Konsole](#icons-cut-off-in-konsole)
+- [Arch Linux logo has a dot in the bottom right corner](#arch-linux-logo-has-a-dot-in-the-bottom-right-corner)
+- [Incorrect git status in prompt](#incorrect-git-status-in-prompt)
+
+### `[oh-my-zsh] theme 'powerlevel10k/powerlevel10k' not found`
+
+When opening a terminal, or starting zsh manually, you may encounter this error message:
+
+```text
+[oh-my-zsh] theme 'powerlevel10k/powerlevel10k' not found
+```
+
+1. First, run `typeset -p P9K_VERSION` to check whether Powerlevel10k has been loaded.
+ - If `typeset -p P9K_VERSION` succeeds and prints something like `typeset P9K_VERSION=1.19.14`
+ (the version could be different), remove the following line from `~/.zshrc`:
+ ```zsh
+ ZSH_THEME="powerlevel10k/powerlevel10k"
+ ```
+ - If `typeset -p P9K_VERSION` fails with the error `typeset: no such variable: P9K_VERSION`, run
+ the following command:
+ ```zsh
+ git clone --depth=1 https://github.com/romkatv/powerlevel10k.git "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k"
+ ```
+2. Restart Zsh with `exec zsh`.
+
### Question mark in prompt
If it looks like a regular `?`, that's normal. It means you have untracked files in the current Git
@@ -1116,6 +1625,7 @@ There are several things you can try to deal with these imperfections:
it, switching to another font may help but is unlikely.
- Change terminal font size one point up or down. For example, in iTerm2 powerline prompt looks
perfect at font sizes 11 and 13 but breaks down at 12.
+- Enable builtin powerline glyphs in terminal settings if your terminal supports it (iTerm2 does).
- Change font hinting and/or anti-aliasing mode in the terminal settings.
- Shift all text one pixel up/down/left/right if your terminal has an option to do so.
- Try a different terminal.
@@ -1251,16 +1761,16 @@ and make sure that `TERM` environment variable is set correctly. Verify with
If there is no UTF-8 locale on the system, configuration wizard won't offer prompt styles that use
Unicode characters. *Fix*: Install a UTF-8 locale. Verify with `locale -a`.
-When a UTF-8 locale is available, the first few questions asked by the configuration wizard assess
-capabilities of the terminal font. If your answers indicate that some glyphs don't render correctly,
-configuration wizard won't offer prompt styles that use them. *Fix*: Restart your terminal and
-install [the recommended font](#meslo-nerd-font-patched-for-powerlevel10k). Verify by running
-`p10k configure` and checking that all glyphs render correctly.
+Another case in which configuration wizard may not offer Unicode prompt styles is when the
+`MULTIBYTE` shell option is disabled. *Fix*: Enable the `MULTIBYTE` option, or rather don't disable
+it (this option is enabled in Zsh by default). Verify with `print -r -- ${options[MULTIBYTE]}`.
-The minimum screen size at which configuration wizard can function is 55 columns by 21 lines.
-However, not all prompt styles are offered at such small screen size as there is simply not enough
-space to present them. *Fix*: Resize your terminal to at least 80 columns by 25 lines prior to
-running `p10k configure`. Verify with `print ${COLUMNS}x${LINES}`.
+When `MULTIBYTE` is enabled and a UTF-8 locale is available, the first few questions asked by the
+configuration wizard assess capabilities of the terminal font. If your answers indicate that some
+glyphs don't render correctly, configuration wizard won't offer prompt styles that use them. *Fix*:
+Restart your terminal and install
+[the recommended font](#meslo-nerd-font-patched-for-powerlevel10k). Verify by running
+`p10k configure` and checking that all glyphs render correctly.
### Cannot install the recommended font
@@ -1283,11 +1793,11 @@ From [Zsh documentation](
http://zsh.sourceforge.net/Doc/Release/Parameters.html#index-ZLE_005fRPROMPT_005fINDENT):
> `ZLE_RPROMPT_INDENT `
->
+>
> If set, used to give the indentation between the right hand side of the right prompt in the line
> editor as given by `RPS1` or `RPROMPT` and the right hand side of the screen. If not set, the
> value `1` is used.
->
+>
> Typically this will be used to set the value to `0` so that the prompt appears flush with the
> right hand side of the screen.
@@ -1363,41 +1873,58 @@ theme (so that you end up with no theme) and then installing Powerlevel10k manua
```zsh
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ~/powerlevel10k
-echo 'source ~/powerlevel10k/powerlevel10k.zsh-theme' >>! ~/.zshrc
+echo 'source ~/powerlevel10k/powerlevel10k.zsh-theme' >>~/.zshrc
```
This method of installation won't make anything slower or otherwise sub-par.
### Directory is difficult to see in prompt when using Rainbow style
-In Classic style the current working directory is shown with bright white white text on blue
-background. The white is fixed and always looks the same but the appearance of "blue" is defined
-by your terminal color palette. If it's very light, it's difficult to see white text on it.
+In Rainbow style the current working directory is shown with bright white text on blue background.
+The white is fixed and always looks the same but the appearance of "blue" is defined by your
+terminal color palette. If it's very light, it may be difficult to see white text on it.
There are several ways to fix this.
- Type `p10k configure` and choose a more readable prompt style.
- [Change terminal color palette](#change-the-color-palette-used-by-your-terminal). Try Tango Dark
or Solarized Dark, or change just the "blue" color.
-- [Change directory background color](#set-colors-through-Powerlevel10k-configuration-parameters).
- The parameter you are looking for is called `POWERLEVEL9K_DIR_BACKGROUND`. You can find it in
- in `~/.p10k.zsh`. Uncomment it if it's commented out and try different values.
+- [Change directory background and/or foreground color](#set-colors-through-Powerlevel10k-configuration-parameters).
+ The parameters you are looking for are called `POWERLEVEL9K_DIR_BACKGROUND`,
+ `POWERLEVEL9K_DIR_FOREGROUND`, `POWERLEVEL9K_DIR_SHORTENED_FOREGROUND`,
+ `POWERLEVEL9K_DIR_ANCHOR_FOREGROUND` and `POWERLEVEL9K_DIR_ANCHOR_BOLD`. You can find them in
+ `~/.p10k.zsh`.
+
+*Related*: [Incorrect foreground color in VSCode Terminal.](#incorrect-foreground-color-in-vscode-terminal)
+
+### Incorrect foreground color in VSCode Terminal
+
+By default, VSCode Terminal may arbitrarily replace the foreground color of your choice with a
+different color. This behavior can be
+[turned off](https://code.visualstudio.com/docs/terminal/appearance#_minimum-contrast-ratio) in
+VSCode settings.
### Horrific mess when resizing terminal window
-When you resize terminal window horizontally back and forth a few times, you might see this ugly
+When you resize a terminal window horizontally back and forth a few times, you might see this ugly
picture.

-tl;dr: This is a bug in Zsh that isn't specific to Powerlevel10k. See [mitigation](#mitigation).
+tl;dr: This issue arises when a terminal reflows Zsh prompt upon resizing. It isn't specific to
+Powerlevel10k. See [mitigation](#mitigation).
-#### Zsh bug
+*Note: This section [used to say](
+ https://github.com/romkatv/powerlevel10k/blob/dce00cdb5daaa8a519df234a7012ba3257b644d4/README.md#horrific-mess-when-resizing-terminal-window)
+that the problem is caused by a bug in Zsh. While it's true that it's possible to avoid the problem
+in many circumstances by modifying Zsh, it cannot be completely resolved this way. Thus it's unfair
+to pin the blame on Zsh.*
-This issue is caused by a bug in Zsh that gets triggered when the vertical distance between the
-start of the current prompt and the cursor (henceforth `VD`) changes when the terminal window is
-resized. This bug is not specific to Powerlevel10k.
+#### The anatomy of the problem
+
+The issue is manifested when the vertical distance between the start of the current prompt and the
+cursor (henceforth `VD`) changes when the terminal window is resized.
When a terminal window gets shrunk horizontally, there are two ways for a terminal to handle long
lines that no longer fit: *reflow* or *truncate*.
@@ -1419,9 +1946,9 @@ Terminal truncates text when shrinking:
Reflowing strategy can change the height of terminal content. If such content happens to be between
the start of the current prompt and the cursor, Zsh will print prompt on the wrong line. Truncation
-strategy never changes the height of terminal content, so it doesn't trigger this bug in Zsh.
+strategy never changes the height of terminal content, so it doesn't trigger this issue.
-Let's see how the bug plays out in slow motion. We'll start by launching `zsh -df` and pasting
+Let's see how the issue plays out in slow motion. We'll start by launching `zsh -f` and pasting
the following code:
```zsh
@@ -1437,13 +1964,13 @@ PROMPT=$'${$((pause()))+}left>${(pl.$((COLUMNS-12))..-.)} '
When `PROMPT` gets expanded, it calls `pause` to let us observe the state of the terminal. Here's
the initial state:
-
Zsh keeps track of the cursor position relative to the start of the current prompt. In this case it
knows that the cursor is one line below. When we shrink the terminal window, it looks like this:
-
At this point the terminal sends `SIGWINCH` to Zsh to notify it about changes in the terminal
@@ -1455,7 +1982,7 @@ terminal content that follows and prints reexpanded prompt there. However, after
no longer one line above the cursor. It's two lines above! Zsh ends up printing new prompt one line
too low.
-
In this case we ended up with unwanted junk content because `VD` has *increased*. When you make
@@ -1464,26 +1991,30 @@ higher than intended, potentially erasing useful content in the process.
Here are a few more examples where shrinking terminal window increased `VD`.
-Simple one-line left prompt with right prompt. No `prompt_subst`. Note that the cursor is below the
-prompt line (hit *ESC-ENTER* to get it there).
-
-
-
-Simple one-line left prompt. No `prompt_subst`, no right prompt. Here `VD` is bound to increase
-upon terminal shrinking due to the command line wrapping around.
-
-
+- Simple one-line left prompt with right prompt. No `prompt_subst`. Note that the cursor is below
+ the prompt line (hit *ESC-ENTER* to get it there).
+ 
+- Simple one-line left prompt. No `prompt_subst`, no right prompt. Here `VD` is bound to increase
+ upon terminal shrinking due to the command line wrapping around.
+ 
#### Zsh patch
-The bug described above has been fixed in [this branch](
- https://github.com/romkatv/zsh/tree/fix-winchanged). The idea behind the fix is to use `sc` (save
-cursor) terminal capability before printing prompt and `rc` (restore cursor) to move cursor back
-to the same position when prompt needs to be refreshed.
+[This Zsh patch](https://github.com/romkatv/zsh/tree/fix-winchanged) fixes the issue on some
+terminals. The idea behind the patch is to use `sc` (save cursor) terminal capability before
+printing prompt and `rc` (restore cursor) to move cursor back to the original position when prompt
+needs to be refreshed.
-There are two alternative approaches to fixing the bug that may seem to work at fight glance but in
+The patch works only on terminals that reflow saved cursor position together with text when the
+terminal window is resized. The patch has no observable effect on terminals that don't reflow text
+on resize (both patched and unpatched Zsh behave correctly) and on terminals that reflow text but
+not the saved cursor position (both patched and unpatched Zsh redraw prompt at the same incorrect
+position). In other words, the patch fixes the resizing issue on some terminals while keeping the
+behavior unchanged on others.
+
+There are two alternative approaches to patching Zsh that may seem to work at first glance but in
fact don't:
- Instead of `sc`, use `u7` terminal capability to query the current cursor position and then `cup`
@@ -1502,20 +2033,25 @@ There is no ETA for the patch making its way into upstream Zsh. See [discussion]
There are a few mitigation options for this issue.
+- Use [kitty](https://sw.kovidgoyal.net/kitty/) terminal version >= 0.24.0 and enable terminal-shell
+ integration in Powerlevel10k by defining `POWERLEVEL9K_TERM_SHELL_INTEGRATION=true` in
+ `~/.p10k.zsh`.
- Apply [the patch](#zsh-patch) and [rebuild Zsh from source](
- https://github.com/zsh-users/zsh/blob/master/INSTALL).
+ https://github.com/zsh-users/zsh/blob/master/INSTALL). It won't help if you are using Alacritty,
+ kitty or some other terminal that reflows text on resize but doesn't reflow saved cursor position.
+ On such terminals the patch will have no visible effect.
- Disable text reflowing on window resize in terminal settings. If your terminal doesn't have this
setting, try a different terminal.
- Avoid long lines between the start of prompt and cursor.
1. Disable ruler with `POWERLEVEL9K_SHOW_RULER=false`.
- 1. Disable prompt connection with `POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR=' '`.
- 1. Disable right frame with `POWERLEVEL9K_MULTILINE_FIRST_PROMPT_SUFFIX=` and
- `POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_SUFFIX=` and
- `POWERLEVEL9K_MULTILINE_LAST_PROMPT_SUFFIX=`.
- 1. Remove all elements from `POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS`. Right prompt on the last prompt
- line will cause resizing issues only when the cursor is below it. This isn't very common, so
- you might want to keep some elements in `POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS` provided that
- none of them are succeeded by `newline`.
+ 2. Disable prompt connection with `POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR=' '`.
+ 3. Disable right frame with `POWERLEVEL9K_MULTILINE_FIRST_PROMPT_SUFFIX=''`,
+ `POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_SUFFIX=''` and
+ `POWERLEVEL9K_MULTILINE_LAST_PROMPT_SUFFIX=''`.
+ 4. Set `POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=()`. Right prompt on the last prompt line will cause
+ resizing issues only when the cursor is below it. This isn't very common, so you might want to
+ keep some elements in `POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS` provided that none of them are
+ succeeded by `newline`.
### Icons cut off in Konsole
@@ -1531,15 +2067,14 @@ The last line on the screenshot shows a cut off Arch Linux logo.
There are several mitigation options for this issue.
1. Use a different terminal. Konsole is the only terminal that exhibits this behavior.
-2. Use a monospace font. For example, [the recommended Powerlevel10k font](
- #meslo-nerd-font-patched-for-powerlevel10k).
+2. Use a monospace font.
3. Manually add an extra space after the icon that gets cut off. For example, if the content of
`os_icon` prompt segment gets cut off, open `~/.p10k.zsh`, search for
`POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION` and change it as follows:
```zsh
-typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='%B${P9K_CONTENT} ' # extra space at the end
+typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='${P9K_CONTENT} ' # extra space at the end
```
-4. Use a different icon that isn't monospace. For example, if Arch Linux logo gets cut off, add
+4. Use a different icon that is monospace. For example, if Arch Linux logo gets cut off, add
the following parameter to `~/.p10k.zsh`:
```zsh
typeset -g POWERLEVEL9K_LINUX_ARCH_ICON='Arch' # plain "Arch" in place of a logo
@@ -1566,83 +2101,15 @@ Some fonts have this incorrect dotted icon in bold typeface. There are two ways
typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='${P9K_CONTENT}' # not bold
```
-## Table of contents
+### Incorrect git status in prompt
-- [Features](#features)
- - [Configuration wizard](#configuration-wizard)
- - [Uncompromising performance](#uncompromising-performance)
- - [Powerlevel9k compatibility](#powerlevel9k-compatibility)
- - [Pure compatibility](#pure-compatibility)
- - [Instant prompt](#instant-prompt)
- - [Show on command](#show-on-command)
- - [Transient prompt](#transient-prompt)
- - [Current directory that just works](#current-directory-that-just-works)
- - [Extremely customizable](#extremely-customizable)
- - [Batteries included](#batteries-included)
- - [Extensible](#extensible)
-- [Installation](#installation)
- - [Manual](#manual)
- - [Oh My Zsh](#oh-my-zsh)
- - [Prezto](#prezto)
- - [Zim](#zim)
- - [Antigen](#antigen)
- - [Zplug](#zplug)
- - [Zgen](#zgen)
- - [Zplugin](#zplugin)
- - [Zinit](#zinit)
- - [Homebrew](#homebrew)
-- [Configuration](#configuration)
- - [For new users](#for-new-users)
- - [For Powerlevel9k users](#for-powerlevel9k-users)
-- [Fonts](#fonts)
- - [Meslo Nerd Font patched for Powerlevel10k](#meslo-nerd-font-patched-for-powerlevel10k)
- - [Automatic font installation](#automatic-font-installation)
- - [Manual font installation](#manual-font-installation)
-- [Try it in Docker](#try-it-in-docker)
-- [License](#license)
-- [FAQ](#faq)
- - [How do I update Powerlevel10k?](#how-do-i-update-powerlevel10k)
- - [Where can I ask for help and report bugs?](#where-can-i-ask-for-help-and-report-bugs)
- - [Which aspects of shell and terminal does Powerlevel10k affect?](#which-aspects-of-shell-and-terminal-does-powerlevel10k-affect)
- - [I'm using Powerlevel9k with Oh My Zsh. How do I migrate?](#im-using-powerlevel9k-with-oh-my-zsh-how-do-i-migrate)
- - [Is it really fast?](#is-it-really-fast)
- - [How do I enable instant prompt?](#how-do-i-enable-instant-prompt)
- - [What do different symbols in Git status mean?](#what-do-different-symbols-in-git-status-mean)
- - [How do I change the format of Git status?](#how-do-i-change-the-format-of-git-status)
- - [Why is Git status from `$HOME/.git` not displayed in prompt?](#why-is-git-status-from-homegit-not-displayed-in-prompt)
- - [Why does Git status sometimes appear grey and then gets colored after a short period of time?](#why-does-git-status-sometimes-appear-grey-and-then-gets-colored-after-a-short-period-of-time)
- - [How do I add username and/or hostname to prompt?](#how-do-i-add-username-andor-hostname-to-prompt)
- - [Why some prompt segments appear and disappear as I'm typing?](#why-some-prompt-segments-appear-and-disappear-as-im-typing)
- - [How do I change prompt colors?](#how-do-i-change-prompt-colors)
- - [Why does Powerlevel10k spawn extra processes?](#why-does-powerlevel10k-spawn-extra-processes)
- - [Are there configuration options that make Powerlevel10k slow?](#are-there-configuration-options-that-make-powerlevel10k-slow)
- - [Is Powerlevel10k fast to load?](#is-powerlevel10k-fast-to-load)
- - [What is the relationship between Powerlevel9k and Powerlevel10k?](#what-is-the-relationship-between-powerlevel9k-and-powerlevel10k)
- - [Does Powerlevel10k always render exactly the same prompt as Powerlevel9k given the same config?](#does-powerlevel10k-always-render-exactly-the-same-prompt-as-powerlevel9k-given-the-same-config)
- - [What is the best prompt style in the configuration wizard?](#what-is-the-best-prompt-style-in-the-configuration-wizard)
- - [How to make Powerlevel10k look like robbyrussell Oh My Zsh theme?](#how-to-make-powerlevel10k-look-like-robbyrussell-oh-my-zsh-theme)
- - [Can prompts for completed commands display error status for *those* commands instead of the commands preceding them?](#can-prompts-for-completed-commands-display-error-status-for-those-commands-instead-of-the-commands-preceding-them)
- - [Is there an AUR package for Powerlevel10k?](#is-there-an-aur-package-for-powerlevel10k)
- - [What is the minimum supported Zsh version?](#what-is-the-minimum-supported-zsh-version)
- - [How were these screenshots and animated gifs created?](#how-were-these-screenshots-and-animated-gifs-created)
-- [Troubleshooting](#troubleshooting)
- - [Question mark in prompt](#question-mark-in-prompt)
- - [Icons, glyphs or powerline symbols don't render](#icons-glyphs-or-powerline-symbols-dont-render)
- - [Sub-pixel imperfections around powerline symbols](#sub-pixel-imperfections-around-powerline-symbols)
- - [Error: character not in range](#error-character-not-in-range)
- - [Cursor is in the wrong place](#cursor-is-in-the-wrong-place)
- - [Prompt wrapping around in a weird way](#prompt-wrapping-around-in-a-weird-way)
- - [Right prompt is in the wrong place](#right-prompt-is-in-the-wrong-place)
- - [Configuration wizard runs automatically every time Zsh is started](#configuration-wizard-runs-automatically-every-time-zsh-is-started)
- - [Some prompt styles are missing from the configuration wizard](#some-prompt-styles-are-missing-from-the-configuration-wizard)
- - [Cannot install the recommended font](#cannot-install-the-recommended-font)
- - [Extra or missing spaces in prompt compared to Powerlevel9k](#extra-or-missing-spaces-in-prompt-compared-to-powerlevel9k)
- - [Extra space without background on the right side of right prompt](#extra-space-without-background-on-the-right-side-of-right-prompt)
- - [Extra or missing spaces around icons](#extra-or-missing-spaces-around-icons)
- - [Weird things happen after typing `source ~/.zshrc`](#weird-things-happen-after-typing-source-zshrc)
- - [Transient prompt stops working after some time](#transient-prompt-stops-working-after-some-time)
- - [Cannot make Powerlevel10k work with my plugin manager](#cannot-make-powerlevel10k-work-with-my-plugin-manager)
- - [Directory is difficult to see in prompt when using Rainbow style](#directory-is-difficult-to-see-in-prompt-when-using-rainbow-style)
- - [Horrific mess when resizing terminal window](#horrific-mess-when-resizing-terminal-window)
- - [Icons cut off in Konsole](#icons-cut-off-in-konsole)
- - [Arch Linux logo has a dot in the bottom right corner](#arch-linux-logo-has-a-dot-in-the-bottom-right-corner)
+Powerlevel10k uses [gitstatusd](https://github.com/romkatv/gitstatus) to inspect the state of git
+repositories. The project relies on the [libgit2](https://github.com/libgit2/libgit2) library, which
+has some gaps in its implementation. Under some conditions, this may result in discrepancies between
+the real state of a git repository (reflected by `git status`) and what gets shown in the
+Powerlevel10k prompt.
+
+Most notably, [libgit2 does not support `skipHash`](https://github.com/libgit2/libgit2/issues/6531).
+If you see incorrect git status in prompt, run `git config -l` and check whether `skipHash` is
+enabled. If it is, consider disabling it. Keep in mind that `skipHash` may be implicitly enabled
+when activating certain git features, such as `manyFiles`.
diff --git a/config/p10k-classic.zsh b/config/p10k-classic.zsh
index 27b84293..623b9308 100644
--- a/config/p10k-classic.zsh
+++ b/config/p10k-classic.zsh
@@ -17,10 +17,10 @@
# Unset all configuration options. This allows you to apply configuration changes without
# restarting zsh. Edit ~/.p10k.zsh and type `source ~/.p10k.zsh`.
- unset -m 'POWERLEVEL9K_*'
+ unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR'
# Zsh >= 5.1 is required.
- autoload -Uz is-at-least && is-at-least 5.1 || return
+ [[ $ZSH_VERSION == (5.<1->*|<6->.*) ]] || return
# The list of segments shown on the left. Fill it with the most important segments.
typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(
@@ -65,23 +65,31 @@
luaenv # lua version from luaenv (https://github.com/cehoffman/luaenv)
jenv # java version from jenv (https://github.com/jenv/jenv)
plenv # perl version from plenv (https://github.com/tokuhirom/plenv)
+ perlbrew # perl version from perlbrew (https://github.com/gugod/App-perlbrew)
phpenv # php version from phpenv (https://github.com/phpenv/phpenv)
+ scalaenv # scala version from scalaenv (https://github.com/scalaenv/scalaenv)
haskell_stack # haskell version from stack (https://haskellstack.org/)
kubecontext # current kubernetes context (https://kubernetes.io/)
terraform # terraform workspace (https://www.terraform.io)
+ # terraform_version # terraform version (https://www.terraform.io)
aws # aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
aws_eb_env # aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/)
azure # azure account name (https://docs.microsoft.com/en-us/cli/azure)
gcloud # google cloud cli account and project (https://cloud.google.com/)
google_app_cred # google application credentials (https://cloud.google.com/docs/authentication/production)
+ toolbox # toolbox name (https://github.com/containers/toolbox)
context # user@hostname
nordvpn # nordvpn connection status, linux only (https://nordvpn.com/)
ranger # ranger shell (https://github.com/ranger/ranger)
+ yazi # yazi shell (https://github.com/sxyazi/yazi)
nnn # nnn shell (https://github.com/jarun/nnn)
+ lf # lf shell (https://github.com/gokcehan/lf)
+ xplr # xplr shell (https://github.com/sayanarijit/xplr)
vim_shell # vim shell indicator (:sh)
midnight_commander # midnight commander shell (https://midnight-commander.org/)
nix_shell # nix shell (https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html)
- vi_mode # vi mode (you don't need this if you've enabled prompt_char)
+ chezmoi_shell # chezmoi shell (https://www.chezmoi.io/)
+ # vi_mode # vi mode (you don't need this if you've enabled prompt_char)
# vpn_ip # virtual private network indicator
# load # CPU load
# disk_usage # disk usage
@@ -90,6 +98,8 @@
todo # todo items (https://github.com/todotxt/todo.txt-cli)
timewarrior # timewarrior tracking status (https://timewarrior.net/)
taskwarrior # taskwarrior task count (https://taskwarrior.org/)
+ per_directory_history # Oh My Zsh per-directory-history local/global indicator
+ # cpu_arch # CPU architecture
# time # current time
# =========================[ Line #2 ]=========================
newline # \n
@@ -136,9 +146,10 @@
# Filler between left and right prompt on the first prompt line. You can set it to ' ', 'ยท' or
# 'โ'. The last two make it easier to see the alignment between left and right prompt and to
# separate prompt from command output. You might want to set POWERLEVEL9K_PROMPT_ADD_NEWLINE=false
- # for more compact prompt if using using this option.
+ # for more compact prompt if using this option.
typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR=' '
typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_BACKGROUND=
+ typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_GAP_BACKGROUND=
if [[ $POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR != ' ' ]]; then
# The color of the filler. You'll probably want to match the color of POWERLEVEL9K_MULTILINE
# ornaments defined above.
@@ -160,6 +171,9 @@
typeset -g POWERLEVEL9K_LEFT_SEGMENT_SEPARATOR='\uE0B0'
# Separator between different-color segments on the right.
typeset -g POWERLEVEL9K_RIGHT_SEGMENT_SEPARATOR='\uE0B2'
+ # To remove a separator between two segments, add "_joined" to the second segment name.
+ # For example: POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(os_icon context_joined)
+
# The right end of left prompt.
typeset -g POWERLEVEL9K_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL='\uE0B0'
# The left end of right prompt.
@@ -174,8 +188,8 @@
#################################[ os_icon: os identifier ]##################################
# OS identifier color.
typeset -g POWERLEVEL9K_OS_ICON_FOREGROUND=255
- # Make the icon bold.
- typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='%B${P9K_CONTENT}'
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='โญ'
################################[ prompt_char: prompt symbol ]################################
# Transparent background.
@@ -189,7 +203,7 @@
# Prompt symbol in command vi mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION='โฎ'
# Prompt symbol in visual vi mode.
- typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='โ
ค'
+ typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='V'
# Prompt symbol in overwrite vi mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIOWR_CONTENT_EXPANSION='โถ'
typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=true
@@ -229,7 +243,8 @@
.java-version
.perl-version
.php-version
- .tool-version
+ .tool-versions
+ .mise.toml
.shorten_folder_marker
.svn
.terraform
@@ -241,10 +256,16 @@
stack.yaml
)
typeset -g POWERLEVEL9K_SHORTEN_FOLDER_MARKER="(${(j:|:)anchor_files})"
- # If set to true, remove everything before the last (deepest) subdirectory that contains files
- # matching $POWERLEVEL9K_SHORTEN_FOLDER_MARKER. For example, when the current directory is
- # /foo/bar/git_repo/baz, prompt will display git_repo/baz. This assumes that /foo/bar/git_repo
- # contains a marker (.git) and other directories don't.
+ # If set to "first" ("last"), remove everything before the first (last) subdirectory that contains
+ # files matching $POWERLEVEL9K_SHORTEN_FOLDER_MARKER. For example, when the current directory is
+ # /foo/bar/git_repo/nested_git_repo/baz, prompt will display git_repo/nested_git_repo/baz (first)
+ # or nested_git_repo/baz (last). This assumes that git_repo and nested_git_repo contain markers
+ # and other directories don't.
+ #
+ # Optionally, "first" and "last" can be followed by ":" where is an integer.
+ # This moves the truncation point to the right (positive offset) or to the left (negative offset)
+ # relative to the marker. Plain "first" and "last" are equivalent to "first:0" and "last:0"
+ # respectively.
typeset -g POWERLEVEL9K_DIR_TRUNCATE_BEFORE_MARKER=false
# Don't shorten this many last directory segments. They are anchors.
typeset -g POWERLEVEL9K_SHORTEN_DIR_LENGTH=1
@@ -266,53 +287,70 @@
# the full directory that was used in previous commands.
typeset -g POWERLEVEL9K_DIR_HYPERLINK=false
- # Enable special styling for non-writable directories.
- typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=true
- # Show this icon when the current directory is not writable. POWERLEVEL9K_DIR_SHOW_WRITABLE
- # above must be set to true for this parameter to have effect.
- # typeset -g POWERLEVEL9K_DIR_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # Enable special styling for non-writable and non-existent directories. See POWERLEVEL9K_LOCK_ICON
+ # and POWERLEVEL9K_DIR_CLASSES below.
+ typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=v3
- # Custom prefix.
- # typeset -g POWERLEVEL9K_DIR_PREFIX='%248Fin '
+ # The default icon shown next to non-writable and non-existent directories when
+ # POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3.
+ # typeset -g POWERLEVEL9K_LOCK_ICON='โญ'
- # POWERLEVEL9K_DIR_CLASSES allows you to specify custom icons for different directories.
- # It must be an array with 3 * N elements. Each triplet consists of:
+ # POWERLEVEL9K_DIR_CLASSES allows you to specify custom icons and colors for different
+ # directories. It must be an array with 3 * N elements. Each triplet consists of:
#
- # 1. A pattern against which the current directory is matched. Matching is done with
+ # 1. A pattern against which the current directory ($PWD) is matched. Matching is done with
# extended_glob option enabled.
# 2. Directory class for the purpose of styling.
- # 3. Icon.
+ # 3. An empty string.
#
- # Triplets are tried in order. The first triplet whose pattern matches $PWD wins. If there
- # are no matches, the directory will have no icon.
+ # Triplets are tried in order. The first triplet whose pattern matches $PWD wins.
#
- # Example:
+ # If POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3, non-writable and non-existent directories
+ # acquire class suffix _NOT_WRITABLE and NON_EXISTENT respectively.
+ #
+ # For example, given these settings:
#
# typeset -g POWERLEVEL9K_DIR_CLASSES=(
- # '~/work(|/*)' WORK '(โฏยฐโกยฐ๏ผโฏ๏ธต โปโโป'
- # '~(|/*)' HOME 'โ'
- # '*' DEFAULT '')
+ # '~/work(|/*)' WORK ''
+ # '~(|/*)' HOME ''
+ # '*' DEFAULT '')
#
- # With these settings, the current directory in the prompt may look like this:
+ # Whenever the current directory is ~/work or a subdirectory of ~/work, it gets styled with one
+ # of the following classes depending on its writability and existence: WORK, WORK_NOT_WRITABLE or
+ # WORK_NON_EXISTENT.
#
- # (โฏยฐโกยฐ๏ผโฏ๏ธต โปโโป ~/work/projects/important/urgent
- #
- # Or like this:
- #
- # โ ~/best/powerlevel10k
- #
- # You can also set different colors for directories of different classes. Remember to override
- # FOREGROUND, SHORTENED_FOREGROUND and ANCHOR_FOREGROUND for every directory class that you wish
- # to have its own color.
+ # Simply assigning classes to directories doesn't have any visible effects. It merely gives you an
+ # option to define custom colors and icons for different directory classes.
#
+ # # Styling for WORK.
+ # typeset -g POWERLEVEL9K_DIR_WORK_VISUAL_IDENTIFIER_EXPANSION='โญ'
# typeset -g POWERLEVEL9K_DIR_WORK_FOREGROUND=31
# typeset -g POWERLEVEL9K_DIR_WORK_SHORTENED_FOREGROUND=103
# typeset -g POWERLEVEL9K_DIR_WORK_ANCHOR_FOREGROUND=39
#
+ # # Styling for WORK_NOT_WRITABLE.
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND=31
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_SHORTENED_FOREGROUND=103
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_ANCHOR_FOREGROUND=39
+ #
+ # # Styling for WORK_NON_EXISTENT.
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_FOREGROUND=31
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_SHORTENED_FOREGROUND=103
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_ANCHOR_FOREGROUND=39
+ #
+ # If a styling parameter isn't explicitly defined for some class, it falls back to the classless
+ # parameter. For example, if POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND is not set, it falls
+ # back to POWERLEVEL9K_DIR_FOREGROUND.
+ #
# typeset -g POWERLEVEL9K_DIR_CLASSES=()
+ # Custom prefix.
+ # typeset -g POWERLEVEL9K_DIR_PREFIX='%248Fin '
+
#####################################[ vcs: git status ]######################################
- # Branch icon. Set this parameter to '\uF126 ' for the popular Powerline branch icon.
+ # Branch icon. Set this parameter to '\UE0A0 ' for the popular Powerline branch icon.
typeset -g POWERLEVEL9K_VCS_BRANCH_ICON=
# Untracked files icon. It's really a question mark, your font isn't broken.
@@ -321,7 +359,7 @@
# Formatter for Git status.
#
- # Example output: master โฃ42โก42 *42 merge ~42 +42 !42 ?42.
+ # Example output: master wip โฃ42โก42 *42 merge ~42 +42 !42 ?42.
#
# You can edit the function to customize how Git status looks.
#
@@ -354,34 +392,55 @@
fi
local res
- local where # branch or tag
+
if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then
- res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}"
- where=${(V)VCS_STATUS_LOCAL_BRANCH}
- elif [[ -n $VCS_STATUS_TAG ]]; then
- res+="${meta}#"
- where=${(V)VCS_STATUS_TAG}
+ local branch=${(V)VCS_STATUS_LOCAL_BRANCH}
+ # If local branch name is at most 32 characters long, show it in full.
+ # Otherwise show the first 12 โฆ the last 12.
+ # Tip: To always show local branch name in full without truncation, delete the next line.
+ (( $#branch > 32 )) && branch[13,-13]="โฆ" # <-- this line
+ res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}${branch//\%/%%}"
fi
- # If local branch name or tag is at most 32 characters long, show it in full.
- # Otherwise show the first 12 โฆ the last 12.
- (( $#where > 32 )) && where[13,-13]="โฆ"
- res+="${clean}${where//\%/%%}" # escape %
+ if [[ -n $VCS_STATUS_TAG
+ # Show tag only if not on a branch.
+ # Tip: To always show tag, delete the next line.
+ && -z $VCS_STATUS_LOCAL_BRANCH # <-- this line
+ ]]; then
+ local tag=${(V)VCS_STATUS_TAG}
+ # If tag name is at most 32 characters long, show it in full.
+ # Otherwise show the first 12 โฆ the last 12.
+ # Tip: To always show tag name in full without truncation, delete the next line.
+ (( $#tag > 32 )) && tag[13,-13]="โฆ" # <-- this line
+ res+="${meta}#${clean}${tag//\%/%%}"
+ fi
- # Display the current Git commit if there is no branch or tag.
- # Tip: To always display the current Git commit, remove `[[ -z $where ]] &&` from the next line.
- [[ -z $where ]] && res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}"
+ # Display the current Git commit if there is no branch and no tag.
+ # Tip: To always display the current Git commit, delete the next line.
+ [[ -z $VCS_STATUS_LOCAL_BRANCH && -z $VCS_STATUS_TAG ]] && # <-- this line
+ res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}"
# Show tracking branch name if it differs from local branch.
if [[ -n ${VCS_STATUS_REMOTE_BRANCH:#$VCS_STATUS_LOCAL_BRANCH} ]]; then
- res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}" # escape %
+ res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}"
+ fi
+
+ # Display "wip" if the latest commit's summary contains "wip" or "WIP".
+ if [[ $VCS_STATUS_COMMIT_SUMMARY == (|*[^[:alnum:]])(wip|WIP)(|[^[:alnum:]]*) ]]; then
+ res+=" ${modified}wip"
+ fi
+
+ if (( VCS_STATUS_COMMITS_AHEAD || VCS_STATUS_COMMITS_BEHIND )); then
+ # โฃ42 if behind the remote.
+ (( VCS_STATUS_COMMITS_BEHIND )) && res+=" ${clean}โฃ${VCS_STATUS_COMMITS_BEHIND}"
+ # โก42 if ahead of the remote; no leading space if also behind the remote: โฃ42โก42.
+ (( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && res+=" "
+ (( VCS_STATUS_COMMITS_AHEAD )) && res+="${clean}โก${VCS_STATUS_COMMITS_AHEAD}"
+ elif [[ -n $VCS_STATUS_REMOTE_BRANCH ]]; then
+ # Tip: Uncomment the next line to display '=' if up to date with the remote.
+ # res+=" ${clean}="
fi
- # โฃ42 if behind the remote.
- (( VCS_STATUS_COMMITS_BEHIND )) && res+=" ${clean}โฃ${VCS_STATUS_COMMITS_BEHIND}"
- # โก42 if ahead of the remote; no leading space if also behind the remote: โฃ42โก42.
- (( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && res+=" "
- (( VCS_STATUS_COMMITS_AHEAD )) && res+="${clean}โก${VCS_STATUS_COMMITS_AHEAD}"
# โ 42 if behind the push remote.
(( VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" ${clean}โ ${VCS_STATUS_PUSH_COMMITS_BEHIND}"
(( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" "
@@ -423,7 +482,7 @@
# Don't show Git status in prompt for repositories whose workdir matches this pattern.
# For example, if set to '~', the Git repository at $HOME/.git will be ignored.
- # Multiple patterns can be combined with '|': '~|~/some/dir'.
+ # Multiple patterns can be combined with '|': '~(|/foo)|/bar/baz/*'.
typeset -g POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN='~'
# Disable the default Git status formatting.
@@ -447,7 +506,7 @@
# isn't in an svn or hg reposotiry.
typeset -g POWERLEVEL9K_VCS_BACKENDS=(git)
- # These settings are used for respositories other than Git or when gitstatusd fails and
+ # These settings are used for repositories other than Git or when gitstatusd fails and
# Powerlevel10k has to fall back to using vcs_info.
typeset -g POWERLEVEL9K_VCS_CLEAN_FOREGROUND=76
typeset -g POWERLEVEL9K_VCS_UNTRACKED_FOREGROUND=76
@@ -490,7 +549,7 @@
typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_VISUAL_IDENTIFIER_EXPANSION='โ'
###################[ command_execution_time: duration of the last command ]###################
- # Show duration of the last command if takes longer than this many seconds.
+ # Show duration of the last command if takes at least this many seconds.
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=3
# Show this many fractional digits. Zero means round to seconds.
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0
@@ -519,6 +578,7 @@
###############[ asdf: asdf version manager (https://github.com/asdf-vm/asdf) ]###############
# Default asdf color. Only used to display tools for which there is no color override (see below).
+ # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_FOREGROUND.
typeset -g POWERLEVEL9K_ASDF_FOREGROUND=66
# There are four parameters that can be used to hide asdf tools. Each parameter describes
@@ -564,7 +624,7 @@
typeset -g POWERLEVEL9K_ASDF_SHOW_SYSTEM=true
# If set to non-empty value, hide tools unless there is a file matching the specified file pattern
- # in the current directory, or its parent diretory, or its grandparent directory, and so on.
+ # in the current directory, or its parent directory, or its grandparent directory, and so on.
#
# Note: If this parameter is set to empty value, it won't hide tools.
# Note: SHOW_ON_UPGLOB isn't specific to asdf. It works with all prompt segments.
@@ -651,6 +711,11 @@
# typeset -g POWERLEVEL9K_ASDF_HASKELL_VISUAL_IDENTIFIER_EXPANSION='โญ'
# typeset -g POWERLEVEL9K_ASDF_HASKELL_SHOW_ON_UPGLOB='*.foo|*.bar'
+ # Julia version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_JULIA_FOREGROUND=70
+ # typeset -g POWERLEVEL9K_ASDF_JULIA_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_ASDF_JULIA_SHOW_ON_UPGLOB='*.foo|*.bar'
+
##########[ nordvpn: nordvpn connection status, linux only (https://nordvpn.com/) ]###########
# NordVPN connection indicator color.
typeset -g POWERLEVEL9K_NORDVPN_FOREGROUND=39
@@ -665,6 +730,12 @@
typeset -g POWERLEVEL9K_RANGER_FOREGROUND=178
# Custom icon.
# typeset -g POWERLEVEL9K_RANGER_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ ####################[ yazi: yazi shell (https://github.com/sxyazi/yazi) ]#####################
+ # Yazi shell color.
+ typeset -g POWERLEVEL9K_YAZI_FOREGROUND=178
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_YAZI_VISUAL_IDENTIFIER_EXPANSION='โญ'
######################[ nnn: nnn shell (https://github.com/jarun/nnn) ]#######################
# Nnn shell color.
@@ -672,6 +743,18 @@
# Custom icon.
# typeset -g POWERLEVEL9K_NNN_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ ######################[ lf: lf shell (https://github.com/gokcehan/lf) ]#######################
+ # lf shell color.
+ typeset -g POWERLEVEL9K_LF_FOREGROUND=72
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_LF_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ ##################[ xplr: xplr shell (https://github.com/sayanarijit/xplr) ]##################
+ # xplr shell color.
+ typeset -g POWERLEVEL9K_XPLR_FOREGROUND=72
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_XPLR_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
###########################[ vim_shell: vim shell indicator (:sh) ]###########################
# Vim shell indicator color.
typeset -g POWERLEVEL9K_VIM_SHELL_FOREGROUND=34
@@ -688,13 +771,22 @@
# Nix shell color.
typeset -g POWERLEVEL9K_NIX_SHELL_FOREGROUND=74
+ # Display the icon of nix_shell if PATH contains a subdirectory of /nix/store.
+ # typeset -g POWERLEVEL9K_NIX_SHELL_INFER_FROM_PATH=false
+
# Tip: If you want to see just the icon without "pure" and "impure", uncomment the next line.
# typeset -g POWERLEVEL9K_NIX_SHELL_CONTENT_EXPANSION=
# Custom icon.
# typeset -g POWERLEVEL9K_NIX_SHELL_VISUAL_IDENTIFIER_EXPANSION='โญ'
- ##################################[ disk_usgae: disk usage ]##################################
+ ##################[ chezmoi_shell: chezmoi shell (https://www.chezmoi.io/) ]##################
+ # chezmoi shell color.
+ typeset -g POWERLEVEL9K_CHEZMOI_SHELL_FOREGROUND=33
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_CHEZMOI_SHELL_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ ##################################[ disk_usage: disk usage ]##################################
# Colors for different levels of disk usage.
typeset -g POWERLEVEL9K_DISK_USAGE_NORMAL_FOREGROUND=35
typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_FOREGROUND=220
@@ -720,9 +812,8 @@
# Text and color for insert vi mode.
typeset -g POWERLEVEL9K_VI_INSERT_MODE_STRING=
typeset -g POWERLEVEL9K_VI_MODE_INSERT_FOREGROUND=66
-
# Custom icon.
- # typeset -g POWERLEVEL9K_RANGER_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_VI_MODE_VISUAL_IDENTIFIER_EXPANSION='โญ'
######################################[ ram: free RAM ]#######################################
# RAM color.
@@ -787,7 +878,7 @@
##############[ taskwarrior: taskwarrior task count (https://taskwarrior.org/) ]##############
# Taskwarrior color.
typeset -g POWERLEVEL9K_TASKWARRIOR_FOREGROUND=74
-
+
# Taskwarrior segment format. The following parameters are available within the expansion.
#
# - P9K_TASKWARRIOR_PENDING_COUNT The number of pending tasks: `task +PENDING count`.
@@ -804,6 +895,30 @@
# Custom icon.
# typeset -g POWERLEVEL9K_TASKWARRIOR_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ ######[ per_directory_history: Oh My Zsh per-directory-history local/global indicator ]#######
+ # Color when using local/global history.
+ typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_FOREGROUND=135
+ typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_FOREGROUND=130
+
+ # Tip: Uncomment the next two lines to hide "local"/"global" text and leave just the icon.
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_CONTENT_EXPANSION=''
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_CONTENT_EXPANSION=''
+
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ ################################[ cpu_arch: CPU architecture ]################################
+ # CPU architecture color.
+ typeset -g POWERLEVEL9K_CPU_ARCH_FOREGROUND=172
+
+ # Hide the segment when on a specific CPU architecture.
+ # typeset -g POWERLEVEL9K_CPU_ARCH_X86_64_CONTENT_EXPANSION=
+ # typeset -g POWERLEVEL9K_CPU_ARCH_X86_64_VISUAL_IDENTIFIER_EXPANSION=
+
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_CPU_ARCH_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
##################################[ context: user@hostname ]##################################
# Context color when running with privileges.
typeset -g POWERLEVEL9K_CONTEXT_ROOT_FOREGROUND=178
@@ -833,6 +948,9 @@
typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=37
# Don't show Python version next to the virtual environment name.
typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false
+ # If set to "false", won't show virtualenv if pyenv is already shown.
+ # If set to "if-different", won't show virtualenv if it's the same as pyenv.
+ typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_WITH_PYENV=false
# Separate environment name from Python version only with a space.
typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER=
# Custom icon.
@@ -841,10 +959,33 @@
#####################[ anaconda: conda environment (https://conda.io/) ]######################
# Anaconda environment color.
typeset -g POWERLEVEL9K_ANACONDA_FOREGROUND=37
- # Don't show Python version next to the anaconda environment name.
- typeset -g POWERLEVEL9K_ANACONDA_SHOW_PYTHON_VERSION=false
- # Separate environment name from Python version only with a space.
- typeset -g POWERLEVEL9K_ANACONDA_{LEFT,RIGHT}_DELIMITER=
+
+ # Anaconda segment format. The following parameters are available within the expansion.
+ #
+ # - CONDA_PREFIX Absolute path to the active Anaconda/Miniconda environment.
+ # - CONDA_DEFAULT_ENV Name of the active Anaconda/Miniconda environment.
+ # - CONDA_PROMPT_MODIFIER Configurable prompt modifier (see below).
+ # - P9K_ANACONDA_PYTHON_VERSION Current python version (python --version).
+ #
+ # CONDA_PROMPT_MODIFIER can be configured with the following command:
+ #
+ # conda config --set env_prompt '({default_env}) '
+ #
+ # The last argument is a Python format string that can use the following variables:
+ #
+ # - prefix The same as CONDA_PREFIX.
+ # - default_env The same as CONDA_DEFAULT_ENV.
+ # - name The last segment of CONDA_PREFIX.
+ # - stacked_env Comma-separated list of names in the environment stack. The first element is
+ # always the same as default_env.
+ #
+ # Note: '({default_env}) ' is the default value of env_prompt.
+ #
+ # The default value of POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION expands to $CONDA_PROMPT_MODIFIER
+ # without the surrounding parentheses, or to the last path component of CONDA_PREFIX if the former
+ # is empty.
+ typeset -g POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION='${${${${CONDA_PROMPT_MODIFIER#\(}% }%\)}:-${CONDA_PREFIX:t}}'
+
# Custom icon.
# typeset -g POWERLEVEL9K_ANACONDA_VISUAL_IDENTIFIER_EXPANSION='โญ'
@@ -858,6 +999,19 @@
typeset -g POWERLEVEL9K_PYENV_PROMPT_ALWAYS_SHOW=false
# If set to false, hide python version if it's equal to "system".
typeset -g POWERLEVEL9K_PYENV_SHOW_SYSTEM=true
+
+ # Pyenv segment format. The following parameters are available within the expansion.
+ #
+ # - P9K_CONTENT Current pyenv environment (pyenv version-name).
+ # - P9K_PYENV_PYTHON_VERSION Current python version (python --version).
+ #
+ # The default format has the following logic:
+ #
+ # 1. Display just "$P9K_CONTENT" if it's equal to "$P9K_PYENV_PYTHON_VERSION" or
+ # starts with "$P9K_PYENV_PYTHON_VERSION/".
+ # 2. Otherwise display "$P9K_CONTENT $P9K_PYENV_PYTHON_VERSION".
+ typeset -g POWERLEVEL9K_PYENV_CONTENT_EXPANSION='${P9K_CONTENT}${${P9K_CONTENT:#$P9K_PYENV_PYTHON_VERSION(|/*)}:+ $P9K_PYENV_PYTHON_VERSION}'
+
# Custom icon.
# typeset -g POWERLEVEL9K_PYENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
@@ -890,6 +1044,11 @@
##############[ nvm: node.js version from nvm (https://github.com/nvm-sh/nvm) ]###############
# Nvm color.
typeset -g POWERLEVEL9K_NVM_FOREGROUND=70
+ # If set to false, hide node version if it's the same as default:
+ # $(nvm version current) == $(nvm version default).
+ typeset -g POWERLEVEL9K_NVM_PROMPT_ALWAYS_SHOW=false
+ # If set to false, hide node version if it's equal to "system".
+ typeset -g POWERLEVEL9K_NVM_SHOW_SYSTEM=true
# Custom icon.
# typeset -g POWERLEVEL9K_NVM_VISUAL_IDENTIFIER_EXPANSION='โญ'
@@ -1039,6 +1198,16 @@
# Custom icon.
# typeset -g POWERLEVEL9K_PLENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ ###########[ perlbrew: perl version from perlbrew (https://github.com/gugod/App-perlbrew) ]############
+ # Perlbrew color.
+ typeset -g POWERLEVEL9K_PERLBREW_FOREGROUND=67
+ # Show perlbrew version only when in a perl project subdirectory.
+ typeset -g POWERLEVEL9K_PERLBREW_PROJECT_ONLY=true
+ # Don't show "perl-" at the front.
+ typeset -g POWERLEVEL9K_PERLBREW_SHOW_PREFIX=false
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_PERLBREW_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
############[ phpenv: php version from phpenv (https://github.com/phpenv/phpenv) ]############
# PHP color.
typeset -g POWERLEVEL9K_PHPENV_FOREGROUND=99
@@ -1052,6 +1221,19 @@
# Custom icon.
# typeset -g POWERLEVEL9K_PHPENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ #######[ scalaenv: scala version from scalaenv (https://github.com/scalaenv/scalaenv) ]#######
+ # Scala color.
+ typeset -g POWERLEVEL9K_SCALAENV_FOREGROUND=160
+ # Hide scala version if it doesn't come from one of these sources.
+ typeset -g POWERLEVEL9K_SCALAENV_SOURCES=(shell local global)
+ # If set to false, hide scala version if it's the same as global:
+ # $(scalaenv version-name) == $(scalaenv global).
+ typeset -g POWERLEVEL9K_SCALAENV_PROMPT_ALWAYS_SHOW=false
+ # If set to false, hide scala version if it's equal to "system".
+ typeset -g POWERLEVEL9K_SCALAENV_SHOW_SYSTEM=true
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_SCALAENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
##########[ haskell_stack: haskell version from stack (https://haskellstack.org/) ]###########
# Haskell color.
typeset -g POWERLEVEL9K_HASKELL_STACK_FOREGROUND=172
@@ -1067,6 +1249,8 @@
# typeset -g POWERLEVEL9K_HASKELL_STACK_VISUAL_IDENTIFIER_EXPANSION='โญ'
################[ terraform: terraform workspace (https://www.terraform.io) ]#################
+ # Don't show terraform workspace if it's literally "default".
+ typeset -g POWERLEVEL9K_TERRAFORM_SHOW_DEFAULT=false
# POWERLEVEL9K_TERRAFORM_CLASSES is an array with even number of elements. The first element
# in each pair defines a pattern against which the current terraform workspace gets matched.
# More specifically, it's P9K_CONTENT prior to the application of context expansion (see below)
@@ -1080,7 +1264,7 @@
# typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
# '*prod*' PROD
# '*test*' TEST
- # '*' DEFAULT)
+ # '*' OTHER)
#
# If your current terraform workspace is "project_test", its class is TEST because "project_test"
# doesn't match the pattern '*prod*' but does match '*test*'.
@@ -1093,14 +1277,20 @@
typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
# '*prod*' PROD # These values are examples that are unlikely
# '*test*' TEST # to match your needs. Customize them as needed.
- '*' DEFAULT)
- typeset -g POWERLEVEL9K_TERRAFORM_DEFAULT_FOREGROUND=38
- # typeset -g POWERLEVEL9K_TERRAFORM_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ '*' OTHER)
+ typeset -g POWERLEVEL9K_TERRAFORM_OTHER_FOREGROUND=38
+ # typeset -g POWERLEVEL9K_TERRAFORM_OTHER_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ #############[ terraform_version: terraform version (https://www.terraform.io) ]##############
+ # Terraform version color.
+ typeset -g POWERLEVEL9K_TERRAFORM_VERSION_FOREGROUND=38
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_TERRAFORM_VERSION_VISUAL_IDENTIFIER_EXPANSION='โญ'
#############[ kubecontext: current kubernetes context (https://kubernetes.io/) ]#############
- # Show kubecontext only when the the command you are typing invokes one of these tools.
+ # Show kubecontext only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show kubecontext.
- typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito'
+ typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito|k9s|helmfile|flux|fluxctl|stern|kubeseal|skaffold|kubent|kubecolor|cmctl|sparkctl'
# Kubernetes context classes for the purpose of using different colors, icons and expansions with
# different contexts.
@@ -1185,9 +1375,9 @@
# typeset -g POWERLEVEL9K_KUBECONTEXT_PREFIX='%248Fat '
#[ aws: aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) ]#
- # Show aws only when the the command you are typing invokes one of these tools.
+ # Show aws only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show aws.
- typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|terraform|pulumi'
+ typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|cdk|terraform|pulumi|terragrunt'
# POWERLEVEL9K_AWS_CLASSES is an array with even number of elements. The first element
# in each pair defines a pattern against which the current AWS profile gets matched.
@@ -1219,6 +1409,12 @@
typeset -g POWERLEVEL9K_AWS_DEFAULT_FOREGROUND=208
# typeset -g POWERLEVEL9K_AWS_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # AWS segment format. The following parameters are available within the expansion.
+ #
+ # - P9K_AWS_PROFILE The name of the current AWS profile.
+ # - P9K_AWS_REGION The region associated with the current AWS profile.
+ typeset -g POWERLEVEL9K_AWS_CONTENT_EXPANSION='${P9K_AWS_PROFILE//\%/%%}${P9K_AWS_REGION:+ ${P9K_AWS_REGION//\%/%%}}'
+
#[ aws_eb_env: aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/) ]#
# AWS Elastic Beanstalk environment color.
typeset -g POWERLEVEL9K_AWS_EB_ENV_FOREGROUND=70
@@ -1226,37 +1422,88 @@
# typeset -g POWERLEVEL9K_AWS_EB_ENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
##########[ azure: azure account name (https://docs.microsoft.com/en-us/cli/azure) ]##########
- # Show azure only when the the command you are typing invokes one of these tools.
+ # Show azure only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show azure.
- typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi'
+ typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi|terragrunt'
+
+ # POWERLEVEL9K_AZURE_CLASSES is an array with even number of elements. The first element
+ # in each pair defines a pattern against which the current azure account name gets matched.
+ # More specifically, it's P9K_CONTENT prior to the application of context expansion (see below)
+ # that gets matched. If you unset all POWERLEVEL9K_AZURE_*CONTENT_EXPANSION parameters,
+ # you'll see this value in your prompt. The second element of each pair in
+ # POWERLEVEL9K_AZURE_CLASSES defines the account class. Patterns are tried in order. The
+ # first match wins.
+ #
+ # For example, given these settings:
+ #
+ # typeset -g POWERLEVEL9K_AZURE_CLASSES=(
+ # '*prod*' PROD
+ # '*test*' TEST
+ # '*' OTHER)
+ #
+ # If your current azure account is "company_test", its class is TEST because "company_test"
+ # doesn't match the pattern '*prod*' but does match '*test*'.
+ #
+ # You can define different colors, icons and content expansions for different classes:
+ #
+ # typeset -g POWERLEVEL9K_AZURE_TEST_FOREGROUND=28
+ # typeset -g POWERLEVEL9K_AZURE_TEST_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_AZURE_TEST_CONTENT_EXPANSION='> ${P9K_CONTENT} <'
+ typeset -g POWERLEVEL9K_AZURE_CLASSES=(
+ # '*prod*' PROD # These values are examples that are unlikely
+ # '*test*' TEST # to match your needs. Customize them as needed.
+ '*' OTHER)
+
# Azure account name color.
- typeset -g POWERLEVEL9K_AZURE_FOREGROUND=32
+ typeset -g POWERLEVEL9K_AZURE_OTHER_FOREGROUND=32
# Custom icon.
- # typeset -g POWERLEVEL9K_AZURE_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_AZURE_OTHER_VISUAL_IDENTIFIER_EXPANSION='โญ'
##########[ gcloud: google cloud account and project (https://cloud.google.com/) ]###########
- # Show gcloud only when the the command you are typing invokes one of these tools.
+ # Show gcloud only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show gcloud.
- typeset -g POWERLEVEL9K_GCLOUD_SHOW_ON_COMMAND='gcloud|gcs'
+ typeset -g POWERLEVEL9K_GCLOUD_SHOW_ON_COMMAND='gcloud|gcs|gsutil'
# Google cloud color.
typeset -g POWERLEVEL9K_GCLOUD_FOREGROUND=32
- # Google cloud format. Change the value of POWERLEVEL9K_GCLOUD_CONTENT_EXPANSION if the default
- # is too verbose or not informative enough.
+ # Google cloud format. Change the value of POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION and/or
+ # POWERLEVEL9K_GCLOUD_COMPLETE_CONTENT_EXPANSION if the default is too verbose or not informative
+ # enough. You can use the following parameters in the expansions. Each of them corresponds to the
+ # output of `gcloud` tool.
#
- # P9K_GCLOUD_ACCOUNT: the output of `gcloud config get-value account`
- # P9K_GCLOUD_PROJECT: the output of `gcloud config get-value project`
- # ${VARIABLE//\%/%%}: ${VARIABLE} with all occurences of '%' replaced with '%%'.
+ # Parameter | Source
+ # -------------------------|--------------------------------------------------------------------
+ # P9K_GCLOUD_CONFIGURATION | gcloud config configurations list --format='value(name)'
+ # P9K_GCLOUD_ACCOUNT | gcloud config get-value account
+ # P9K_GCLOUD_PROJECT_ID | gcloud config get-value project
+ # P9K_GCLOUD_PROJECT_NAME | gcloud projects describe $P9K_GCLOUD_PROJECT_ID --format='value(name)'
#
- typeset -g POWERLEVEL9K_GCLOUD_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT//\%/%%}'
+ # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced with '%%'.
+ #
+ # Obtaining project name requires sending a request to Google servers. This can take a long time
+ # and even fail. When project name is unknown, P9K_GCLOUD_PROJECT_NAME is not set and gcloud
+ # prompt segment is in state PARTIAL. When project name gets known, P9K_GCLOUD_PROJECT_NAME gets
+ # set and gcloud prompt segment transitions to state COMPLETE.
+ #
+ # You can customize the format, icon and colors of gcloud segment separately for states PARTIAL
+ # and COMPLETE. You can also hide gcloud in state PARTIAL by setting
+ # POWERLEVEL9K_GCLOUD_PARTIAL_VISUAL_IDENTIFIER_EXPANSION and
+ # POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION to empty.
+ typeset -g POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_ID//\%/%%}'
+ typeset -g POWERLEVEL9K_GCLOUD_COMPLETE_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_NAME//\%/%%}'
+
+ # Send a request to Google (by means of `gcloud projects describe ...`) to obtain project name
+ # this often. Negative value disables periodic polling. In this mode project name is retrieved
+ # only when the current configuration, account or project id changes.
+ typeset -g POWERLEVEL9K_GCLOUD_REFRESH_PROJECT_NAME_SECONDS=60
# Custom icon.
# typeset -g POWERLEVEL9K_GCLOUD_VISUAL_IDENTIFIER_EXPANSION='โญ'
#[ google_app_cred: google application credentials (https://cloud.google.com/docs/authentication/production) ]#
- # Show google_app_cred only when the the command you are typing invokes one of these tools.
+ # Show google_app_cred only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show google_app_cred.
- typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi'
+ typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi|terragrunt'
# Google application credentials classes for the purpose of using different colors, icons and
# expansions with different credentials.
@@ -1304,9 +1551,19 @@
# P9K_GOOGLE_APP_CRED_PROJECT_ID | project_id
# P9K_GOOGLE_APP_CRED_CLIENT_EMAIL | client_email
#
- # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurences of '%' replaced by '%%'.
+ # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced by '%%'.
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_CONTENT_EXPANSION='${P9K_GOOGLE_APP_CRED_PROJECT_ID//\%/%%}'
+ ##############[ toolbox: toolbox name (https://github.com/containers/toolbox) ]###############
+ # Toolbox color.
+ typeset -g POWERLEVEL9K_TOOLBOX_FOREGROUND=178
+ # Don't display the name of the toolbox if it matches fedora-toolbox-*.
+ typeset -g POWERLEVEL9K_TOOLBOX_CONTENT_EXPANSION='${P9K_TOOLBOX_NAME:#fedora-toolbox-*}'
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_TOOLBOX_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # Custom prefix.
+ # typeset -g POWERLEVEL9K_TOOLBOX_PREFIX='%248Fin '
+
###############################[ public_ip: public IP address ]###############################
# Public IP color.
typeset -g POWERLEVEL9K_PUBLIC_IP_FOREGROUND=94
@@ -1321,7 +1578,7 @@
typeset -g POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION=
# Regular expression for the VPN network interface. Run `ifconfig` or `ip -4 a show` while on VPN
# to see the name of the interface.
- typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(wg|(.*tun))[0-9]*'
+ typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(gpd|wg|(.*tun)|tailscale)[0-9]*|(zt.*)'
# If set to true, show one segment per matching network interface. If set to false, show only
# one segment corresponding to the first matching network interface.
# Tip: If you set it to true, you'll probably want to unset POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION.
@@ -1335,17 +1592,19 @@
# The following parameters are accessible within the expansion:
#
# Parameter | Meaning
- # ----------------------+---------------
- # P9K_IP_IP | IP address
- # P9K_IP_INTERFACE | network interface
- # P9K_IP_RX_BYTES | total number of bytes received
- # P9K_IP_TX_BYTES | total number of bytes sent
- # P9K_IP_RX_RATE | receive rate (since last prompt)
- # P9K_IP_TX_RATE | send rate (since last prompt)
+ # ----------------------+-------------------------------------------
+ # P9K_IP_IP | IP address
+ # P9K_IP_INTERFACE | network interface
+ # P9K_IP_RX_BYTES | total number of bytes received
+ # P9K_IP_TX_BYTES | total number of bytes sent
+ # P9K_IP_RX_BYTES_DELTA | number of bytes received since last prompt
+ # P9K_IP_TX_BYTES_DELTA | number of bytes sent since last prompt
+ # P9K_IP_RX_RATE | receive rate (since last prompt)
+ # P9K_IP_TX_RATE | send rate (since last prompt)
typeset -g POWERLEVEL9K_IP_CONTENT_EXPANSION='${P9K_IP_RX_RATE:+%70Fโฃ$P9K_IP_RX_RATE }${P9K_IP_TX_RATE:+%215Fโก$P9K_IP_TX_RATE }%38F$P9K_IP_IP'
# Show information for the first network interface whose name matches this regular expression.
# Run `ifconfig` or `ip -4 a show` to see the names of all network interfaces.
- typeset -g POWERLEVEL9K_IP_INTERFACE='e.*'
+ typeset -g POWERLEVEL9K_IP_INTERFACE='[ew].*'
# Custom icon.
# typeset -g POWERLEVEL9K_IP_VISUAL_IDENTIFIER_EXPANSION='โญ'
@@ -1388,15 +1647,11 @@
# Parameter | Meaning
# ----------------------+---------------
# P9K_WIFI_SSID | service set identifier, a.k.a. network name
- # P9K_WIFI_LINK_AUTH | authentication protocol such as "wpa2-psk" or "none"
+ # P9K_WIFI_LINK_AUTH | authentication protocol such as "wpa2-psk" or "none"; empty if unknown
# P9K_WIFI_LAST_TX_RATE | wireless transmit rate in megabits per second
# P9K_WIFI_RSSI | signal strength in dBm, from -120 to 0
# P9K_WIFI_NOISE | noise in dBm, from -120 to 0
# P9K_WIFI_BARS | signal strength in bars, from 0 to 4 (derived from P9K_WIFI_RSSI and P9K_WIFI_NOISE)
- #
- # All parameters except P9K_WIFI_BARS are extracted from the output of the following command:
- #
- # /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I
####################################[ time: current time ]####################################
# Current time color.
@@ -1423,7 +1678,7 @@
# User-defined prompt segments may optionally provide an instant_prompt_* function. Its job
# is to generate the prompt segment for display in instant prompt. See
- # https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt.
+ # https://github.com/romkatv/powerlevel10k#instant-prompt.
#
# Powerlevel10k will call instant_prompt_* at the same time as the regular prompt_* function
# and will record all `p10k segment` calls it makes. When displaying instant prompt, Powerlevel10k
@@ -1459,7 +1714,7 @@
# it incompatible with your zsh configuration files.
# - quiet: Enable instant prompt and don't print warnings when detecting console output
# during zsh initialization. Choose this if you've read and understood
- # https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt.
+ # https://github.com/romkatv/powerlevel10k#instant-prompt.
# - verbose: Enable instant prompt and print a warning when detecting console output during
# zsh initialization. Choose this if you've never tried instant prompt, haven't
# seen the warning, or if you are unsure what this all means.
@@ -1476,5 +1731,8 @@
(( ! $+functions[p10k] )) || p10k reload
}
+# Tell `p10k configure` which file it should overwrite.
+typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a}
+
(( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]}
'builtin' 'unset' 'p10k_config_opts'
diff --git a/config/p10k-lean-8colors.zsh b/config/p10k-lean-8colors.zsh
index b11ad05a..3f72ff4b 100644
--- a/config/p10k-lean-8colors.zsh
+++ b/config/p10k-lean-8colors.zsh
@@ -17,10 +17,10 @@
# Unset all configuration options. This allows you to apply configuration changes without
# restarting zsh. Edit ~/.p10k.zsh and type `source ~/.p10k.zsh`.
- unset -m 'POWERLEVEL9K_*'
+ unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR'
# Zsh >= 5.1 is required.
- autoload -Uz is-at-least && is-at-least 5.1 || return
+ [[ $ZSH_VERSION == (5.<1->*|<6->.*) ]] || return
# The list of segments shown on the left. Fill it with the most important segments.
typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(
@@ -65,22 +65,30 @@
luaenv # lua version from luaenv (https://github.com/cehoffman/luaenv)
jenv # java version from jenv (https://github.com/jenv/jenv)
plenv # perl version from plenv (https://github.com/tokuhirom/plenv)
+ perlbrew # perl version from perlbrew (https://github.com/gugod/App-perlbrew)
phpenv # php version from phpenv (https://github.com/phpenv/phpenv)
+ scalaenv # scala version from scalaenv (https://github.com/scalaenv/scalaenv)
haskell_stack # haskell version from stack (https://haskellstack.org/)
kubecontext # current kubernetes context (https://kubernetes.io/)
terraform # terraform workspace (https://www.terraform.io)
+ # terraform_version # terraform version (https://www.terraform.io)
aws # aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
aws_eb_env # aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/)
azure # azure account name (https://docs.microsoft.com/en-us/cli/azure)
gcloud # google cloud cli account and project (https://cloud.google.com/)
google_app_cred # google application credentials (https://cloud.google.com/docs/authentication/production)
+ toolbox # toolbox name (https://github.com/containers/toolbox)
context # user@hostname
nordvpn # nordvpn connection status, linux only (https://nordvpn.com/)
ranger # ranger shell (https://github.com/ranger/ranger)
+ yazi # yazi shell (https://github.com/sxyazi/yazi)
nnn # nnn shell (https://github.com/jarun/nnn)
+ lf # lf shell (https://github.com/gokcehan/lf)
+ xplr # xplr shell (https://github.com/sayanarijit/xplr)
vim_shell # vim shell indicator (:sh)
midnight_commander # midnight commander shell (https://midnight-commander.org/)
nix_shell # nix shell (https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html)
+ chezmoi_shell # chezmoi shell (https://www.chezmoi.io/)
# vpn_ip # virtual private network indicator
# load # CPU load
# disk_usage # disk usage
@@ -89,6 +97,8 @@
todo # todo items (https://github.com/todotxt/todo.txt-cli)
timewarrior # timewarrior tracking status (https://timewarrior.net/)
taskwarrior # taskwarrior task count (https://taskwarrior.org/)
+ per_directory_history # Oh My Zsh per-directory-history local/global indicator
+ # cpu_arch # CPU architecture
# time # current time
# =========================[ Line #2 ]=========================
newline # \n
@@ -174,7 +184,7 @@
# OS identifier color.
typeset -g POWERLEVEL9K_OS_ICON_FOREGROUND=
# Custom icon.
- typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='${P9K_CONTENT}'
+ # typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='โญ'
################################[ prompt_char: prompt symbol ]################################
# Green prompt symbol if the last command succeeded.
@@ -186,7 +196,7 @@
# Prompt symbol in command vi mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION='โฎ'
# Prompt symbol in visual vi mode.
- typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='โ
ค'
+ typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='V'
# Prompt symbol in overwrite vi mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIOWR_CONTENT_EXPANSION='โถ'
typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=true
@@ -224,7 +234,8 @@
.java-version
.perl-version
.php-version
- .tool-version
+ .tool-versions
+ .mise.toml
.shorten_folder_marker
.svn
.terraform
@@ -236,10 +247,16 @@
stack.yaml
)
typeset -g POWERLEVEL9K_SHORTEN_FOLDER_MARKER="(${(j:|:)anchor_files})"
- # If set to true, remove everything before the last (deepest) subdirectory that contains files
- # matching $POWERLEVEL9K_SHORTEN_FOLDER_MARKER. For example, when the current directory is
- # /foo/bar/git_repo/baz, prompt will display git_repo/baz. This assumes that /foo/bar/git_repo
- # contains a marker (.git) and other directories don't.
+ # If set to "first" ("last"), remove everything before the first (last) subdirectory that contains
+ # files matching $POWERLEVEL9K_SHORTEN_FOLDER_MARKER. For example, when the current directory is
+ # /foo/bar/git_repo/nested_git_repo/baz, prompt will display git_repo/nested_git_repo/baz (first)
+ # or nested_git_repo/baz (last). This assumes that git_repo and nested_git_repo contain markers
+ # and other directories don't.
+ #
+ # Optionally, "first" and "last" can be followed by ":" where is an integer.
+ # This moves the truncation point to the right (positive offset) or to the left (negative offset)
+ # relative to the marker. Plain "first" and "last" are equivalent to "first:0" and "last:0"
+ # respectively.
typeset -g POWERLEVEL9K_DIR_TRUNCATE_BEFORE_MARKER=false
# Don't shorten this many last directory segments. They are anchors.
typeset -g POWERLEVEL9K_SHORTEN_DIR_LENGTH=1
@@ -261,53 +278,70 @@
# the full directory that was used in previous commands.
typeset -g POWERLEVEL9K_DIR_HYPERLINK=false
- # Enable special styling for non-writable directories.
- typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=true
- # Show this icon when the current directory is not writable. POWERLEVEL9K_DIR_SHOW_WRITABLE
- # above must be set to true for this parameter to have effect.
- # typeset -g POWERLEVEL9K_DIR_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # Enable special styling for non-writable and non-existent directories. See POWERLEVEL9K_LOCK_ICON
+ # and POWERLEVEL9K_DIR_CLASSES below.
+ typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=v3
+
+ # The default icon shown next to non-writable and non-existent directories when
+ # POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3.
+ # typeset -g POWERLEVEL9K_LOCK_ICON='โญ'
+
+ # POWERLEVEL9K_DIR_CLASSES allows you to specify custom icons and colors for different
+ # directories. It must be an array with 3 * N elements. Each triplet consists of:
+ #
+ # 1. A pattern against which the current directory ($PWD) is matched. Matching is done with
+ # extended_glob option enabled.
+ # 2. Directory class for the purpose of styling.
+ # 3. An empty string.
+ #
+ # Triplets are tried in order. The first triplet whose pattern matches $PWD wins.
+ #
+ # If POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3, non-writable and non-existent directories
+ # acquire class suffix _NOT_WRITABLE and NON_EXISTENT respectively.
+ #
+ # For example, given these settings:
+ #
+ # typeset -g POWERLEVEL9K_DIR_CLASSES=(
+ # '~/work(|/*)' WORK ''
+ # '~(|/*)' HOME ''
+ # '*' DEFAULT '')
+ #
+ # Whenever the current directory is ~/work or a subdirectory of ~/work, it gets styled with one
+ # of the following classes depending on its writability and existence: WORK, WORK_NOT_WRITABLE or
+ # WORK_NON_EXISTENT.
+ #
+ # Simply assigning classes to directories doesn't have any visible effects. It merely gives you an
+ # option to define custom colors and icons for different directory classes.
+ #
+ # # Styling for WORK.
+ # typeset -g POWERLEVEL9K_DIR_WORK_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_DIR_WORK_FOREGROUND=4
+ # typeset -g POWERLEVEL9K_DIR_WORK_SHORTENED_FOREGROUND=4
+ # typeset -g POWERLEVEL9K_DIR_WORK_ANCHOR_FOREGROUND=4
+ #
+ # # Styling for WORK_NOT_WRITABLE.
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND=4
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_SHORTENED_FOREGROUND=4
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_ANCHOR_FOREGROUND=4#
+ #
+ # Styling for WORK_NON_EXISTENT.
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_FOREGROUND=4
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_SHORTENED_FOREGROUND=4
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_ANCHOR_FOREGROUND=4
+ #
+ # If a styling parameter isn't explicitly defined for some class, it falls back to the classless
+ # parameter. For example, if POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND is not set, it falls
+ # back to POWERLEVEL9K_DIR_FOREGROUND.
+ #
+ # typeset -g POWERLEVEL9K_DIR_CLASSES=()
# Custom prefix.
# typeset -g POWERLEVEL9K_DIR_PREFIX='%fin '
- # POWERLEVEL9K_DIR_CLASSES allows you to specify custom icons for different directories.
- # It must be an array with 3 * N elements. Each triplet consists of:
- #
- # 1. A pattern against which the current directory is matched. Matching is done with
- # extended_glob option enabled.
- # 2. Directory class for the purpose of styling.
- # 3. Icon.
- #
- # Triplets are tried in order. The first triplet whose pattern matches $PWD wins. If there
- # are no matches, the directory will have no icon.
- #
- # Example:
- #
- # typeset -g POWERLEVEL9K_DIR_CLASSES=(
- # '~/work(|/*)' WORK '(โฏยฐโกยฐ๏ผโฏ๏ธต โปโโป'
- # '~(|/*)' HOME 'โ'
- # '*' DEFAULT '')
- #
- # With these settings, the current directory in the prompt may look like this:
- #
- # (โฏยฐโกยฐ๏ผโฏ๏ธต โปโโป ~/work/projects/important/urgent
- #
- # Or like this:
- #
- # โ ~/best/powerlevel10k
- #
- # You can also set different colors for directories of different classes. Remember to override
- # FOREGROUND, SHORTENED_FOREGROUND and ANCHOR_FOREGROUND for every directory class that you wish
- # to have its own color.
- #
- # typeset -g POWERLEVEL9K_DIR_WORK_FOREGROUND=31
- # typeset -g POWERLEVEL9K_DIR_WORK_SHORTENED_FOREGROUND=103
- # typeset -g POWERLEVEL9K_DIR_WORK_ANCHOR_FOREGROUND=39
- #
- # typeset -g POWERLEVEL9K_DIR_CLASSES=()
-
#####################################[ vcs: git status ]######################################
- # Branch icon. Set this parameter to '\uF126 ' for the popular Powerline branch icon.
+ # Branch icon. Set this parameter to '\UE0A0 ' for the popular Powerline branch icon.
typeset -g POWERLEVEL9K_VCS_BRANCH_ICON=
# Untracked files icon. It's really a question mark, your font isn't broken.
@@ -316,7 +350,7 @@
# Formatter for Git status.
#
- # Example output: master โฃ42โก42 *42 merge ~42 +42 !42 ?42.
+ # Example output: master wip โฃ42โก42 *42 merge ~42 +42 !42 ?42.
#
# You can edit the function to customize how Git status looks.
#
@@ -349,34 +383,55 @@
fi
local res
- local where # branch or tag
+
if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then
- res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}"
- where=${(V)VCS_STATUS_LOCAL_BRANCH}
- elif [[ -n $VCS_STATUS_TAG ]]; then
- res+="${meta}#"
- where=${(V)VCS_STATUS_TAG}
+ local branch=${(V)VCS_STATUS_LOCAL_BRANCH}
+ # If local branch name is at most 32 characters long, show it in full.
+ # Otherwise show the first 12 โฆ the last 12.
+ # Tip: To always show local branch name in full without truncation, delete the next line.
+ (( $#branch > 32 )) && branch[13,-13]="โฆ" # <-- this line
+ res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}${branch//\%/%%}"
fi
- # If local branch name or tag is at most 32 characters long, show it in full.
- # Otherwise show the first 12 โฆ the last 12.
- (( $#where > 32 )) && where[13,-13]="โฆ"
- res+="${clean}${where//\%/%%}" # escape %
+ if [[ -n $VCS_STATUS_TAG
+ # Show tag only if not on a branch.
+ # Tip: To always show tag, delete the next line.
+ && -z $VCS_STATUS_LOCAL_BRANCH # <-- this line
+ ]]; then
+ local tag=${(V)VCS_STATUS_TAG}
+ # If tag name is at most 32 characters long, show it in full.
+ # Otherwise show the first 12 โฆ the last 12.
+ # Tip: To always show tag name in full without truncation, delete the next line.
+ (( $#tag > 32 )) && tag[13,-13]="โฆ" # <-- this line
+ res+="${meta}#${clean}${tag//\%/%%}"
+ fi
- # Display the current Git commit if there is no branch or tag.
- # Tip: To always display the current Git commit, remove `[[ -z $where ]] &&` from the next line.
- [[ -z $where ]] && res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}"
+ # Display the current Git commit if there is no branch and no tag.
+ # Tip: To always display the current Git commit, delete the next line.
+ [[ -z $VCS_STATUS_LOCAL_BRANCH && -z $VCS_STATUS_TAG ]] && # <-- this line
+ res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}"
# Show tracking branch name if it differs from local branch.
if [[ -n ${VCS_STATUS_REMOTE_BRANCH:#$VCS_STATUS_LOCAL_BRANCH} ]]; then
- res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}" # escape %
+ res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}"
+ fi
+
+ # Display "wip" if the latest commit's summary contains "wip" or "WIP".
+ if [[ $VCS_STATUS_COMMIT_SUMMARY == (|*[^[:alnum:]])(wip|WIP)(|[^[:alnum:]]*) ]]; then
+ res+=" ${modified}wip"
+ fi
+
+ if (( VCS_STATUS_COMMITS_AHEAD || VCS_STATUS_COMMITS_BEHIND )); then
+ # โฃ42 if behind the remote.
+ (( VCS_STATUS_COMMITS_BEHIND )) && res+=" ${clean}โฃ${VCS_STATUS_COMMITS_BEHIND}"
+ # โก42 if ahead of the remote; no leading space if also behind the remote: โฃ42โก42.
+ (( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && res+=" "
+ (( VCS_STATUS_COMMITS_AHEAD )) && res+="${clean}โก${VCS_STATUS_COMMITS_AHEAD}"
+ elif [[ -n $VCS_STATUS_REMOTE_BRANCH ]]; then
+ # Tip: Uncomment the next line to display '=' if up to date with the remote.
+ # res+=" ${clean}="
fi
- # โฃ42 if behind the remote.
- (( VCS_STATUS_COMMITS_BEHIND )) && res+=" ${clean}โฃ${VCS_STATUS_COMMITS_BEHIND}"
- # โก42 if ahead of the remote; no leading space if also behind the remote: โฃ42โก42.
- (( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && res+=" "
- (( VCS_STATUS_COMMITS_AHEAD )) && res+="${clean}โก${VCS_STATUS_COMMITS_AHEAD}"
# โ 42 if behind the push remote.
(( VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" ${clean}โ ${VCS_STATUS_PUSH_COMMITS_BEHIND}"
(( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" "
@@ -418,7 +473,7 @@
# Don't show Git status in prompt for repositories whose workdir matches this pattern.
# For example, if set to '~', the Git repository at $HOME/.git will be ignored.
- # Multiple patterns can be combined with '|': '~|~/some/dir'.
+ # Multiple patterns can be combined with '|': '~(|/foo)|/bar/baz/*'.
typeset -g POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN='~'
# Disable the default Git status formatting.
@@ -439,10 +494,10 @@
# Show status of repositories of these types. You can add svn and/or hg if you are
# using them. If you do, your prompt may become slow even when your current directory
- # isn't in an svn or hg reposotiry.
+ # isn't in an svn or hg repository.
typeset -g POWERLEVEL9K_VCS_BACKENDS=(git)
- # These settings are used for respositories other than Git or when gitstatusd fails and
+ # These settings are used for repositories other than Git or when gitstatusd fails and
# Powerlevel10k has to fall back to using vcs_info.
typeset -g POWERLEVEL9K_VCS_CLEAN_FOREGROUND=2
typeset -g POWERLEVEL9K_VCS_UNTRACKED_FOREGROUND=2
@@ -485,7 +540,7 @@
typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_VISUAL_IDENTIFIER_EXPANSION='โ'
###################[ command_execution_time: duration of the last command ]###################
- # Show duration of the last command if takes longer than this many seconds.
+ # Show duration of the last command if takes at least this many seconds.
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=3
# Show this many fractional digits. Zero means round to seconds.
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0
@@ -514,6 +569,7 @@
###############[ asdf: asdf version manager (https://github.com/asdf-vm/asdf) ]###############
# Default asdf color. Only used to display tools for which there is no color override (see below).
+ # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_FOREGROUND.
typeset -g POWERLEVEL9K_ASDF_FOREGROUND=6
# There are four parameters that can be used to hide asdf tools. Each parameter describes
@@ -559,7 +615,7 @@
typeset -g POWERLEVEL9K_ASDF_SHOW_SYSTEM=true
# If set to non-empty value, hide tools unless there is a file matching the specified file pattern
- # in the current directory, or its parent diretory, or its grandparent directory, and so on.
+ # in the current directory, or its parent directory, or its grandparent directory, and so on.
#
# Note: If this parameter is set to empty value, it won't hide tools.
# Note: SHOW_ON_UPGLOB isn't specific to asdf. It works with all prompt segments.
@@ -646,6 +702,11 @@
# typeset -g POWERLEVEL9K_ASDF_HASKELL_VISUAL_IDENTIFIER_EXPANSION='โญ'
# typeset -g POWERLEVEL9K_ASDF_HASKELL_SHOW_ON_UPGLOB='*.foo|*.bar'
+ # Julia version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_JULIA_FOREGROUND=2
+ # typeset -g POWERLEVEL9K_ASDF_JULIA_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_ASDF_JULIA_SHOW_ON_UPGLOB='*.foo|*.bar'
+
##########[ nordvpn: nordvpn connection status, linux only (https://nordvpn.com/) ]###########
# NordVPN connection indicator color.
typeset -g POWERLEVEL9K_NORDVPN_FOREGROUND=6
@@ -660,6 +721,12 @@
typeset -g POWERLEVEL9K_RANGER_FOREGROUND=3
# Custom icon.
# typeset -g POWERLEVEL9K_RANGER_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ ####################[ yazi: yazi shell (https://github.com/sxyazi/yazi) ]#####################
+ # Yazi shell color.
+ typeset -g POWERLEVEL9K_YAZI_FOREGROUND=3
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_YAZI_VISUAL_IDENTIFIER_EXPANSION='โญ'
######################[ nnn: nnn shell (https://github.com/jarun/nnn) ]#######################
# Nnn shell color.
@@ -667,6 +734,18 @@
# Custom icon.
# typeset -g POWERLEVEL9K_NNN_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ ######################[ lf: lf shell (https://github.com/gokcehan/lf) ]#######################
+ # lf shell color.
+ typeset -g POWERLEVEL9K_LF_FOREGROUND=3
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_LF_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ ##################[ xplr: xplr shell (https://github.com/sayanarijit/xplr) ]##################
+ # xplr shell color.
+ typeset -g POWERLEVEL9K_XPLR_FOREGROUND=3
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_XPLR_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
###########################[ vim_shell: vim shell indicator (:sh) ]###########################
# Vim shell indicator color.
typeset -g POWERLEVEL9K_VIM_SHELL_FOREGROUND=3
@@ -683,13 +762,22 @@
# Nix shell color.
typeset -g POWERLEVEL9K_NIX_SHELL_FOREGROUND=4
+ # Display the icon of nix_shell if PATH contains a subdirectory of /nix/store.
+ # typeset -g POWERLEVEL9K_NIX_SHELL_INFER_FROM_PATH=false
+
# Tip: If you want to see just the icon without "pure" and "impure", uncomment the next line.
# typeset -g POWERLEVEL9K_NIX_SHELL_CONTENT_EXPANSION=
# Custom icon.
# typeset -g POWERLEVEL9K_NIX_SHELL_VISUAL_IDENTIFIER_EXPANSION='โญ'
- ##################################[ disk_usgae: disk usage ]##################################
+ ##################[ chezmoi_shell: chezmoi shell (https://www.chezmoi.io/) ]##################
+ # chezmoi shell color.
+ typeset -g POWERLEVEL9K_CHEZMOI_SHELL_FOREGROUND=4
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_CHEZMOI_SHELL_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ ##################################[ disk_usage: disk usage ]##################################
# Colors for different levels of disk usage.
typeset -g POWERLEVEL9K_DISK_USAGE_NORMAL_FOREGROUND=2
typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_FOREGROUND=3
@@ -765,7 +853,7 @@
##############[ taskwarrior: taskwarrior task count (https://taskwarrior.org/) ]##############
# Taskwarrior color.
typeset -g POWERLEVEL9K_TASKWARRIOR_FOREGROUND=6
-
+
# Taskwarrior segment format. The following parameters are available within the expansion.
#
# - P9K_TASKWARRIOR_PENDING_COUNT The number of pending tasks: `task +PENDING count`.
@@ -782,6 +870,30 @@
# Custom icon.
# typeset -g POWERLEVEL9K_TASKWARRIOR_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ ######[ per_directory_history: Oh My Zsh per-directory-history local/global indicator ]#######
+ # Color when using local/global history.
+ typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_FOREGROUND=5
+ typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_FOREGROUND=3
+
+ # Tip: Uncomment the next two lines to hide "local"/"global" text and leave just the icon.
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_CONTENT_EXPANSION=''
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_CONTENT_EXPANSION=''
+
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ ################################[ cpu_arch: CPU architecture ]################################
+ # CPU architecture color.
+ typeset -g POWERLEVEL9K_CPU_ARCH_FOREGROUND=3
+
+ # Hide the segment when on a specific CPU architecture.
+ # typeset -g POWERLEVEL9K_CPU_ARCH_X86_64_CONTENT_EXPANSION=
+ # typeset -g POWERLEVEL9K_CPU_ARCH_X86_64_VISUAL_IDENTIFIER_EXPANSION=
+
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_CPU_ARCH_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
##################################[ context: user@hostname ]##################################
# Context color when running with privileges.
typeset -g POWERLEVEL9K_CONTEXT_ROOT_FOREGROUND=1
@@ -811,6 +923,9 @@
typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=6
# Don't show Python version next to the virtual environment name.
typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false
+ # If set to "false", won't show virtualenv if pyenv is already shown.
+ # If set to "if-different", won't show virtualenv if it's the same as pyenv.
+ typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_WITH_PYENV=false
# Separate environment name from Python version only with a space.
typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER=
# Custom icon.
@@ -819,10 +934,33 @@
#####################[ anaconda: conda environment (https://conda.io/) ]######################
# Anaconda environment color.
typeset -g POWERLEVEL9K_ANACONDA_FOREGROUND=6
- # Don't show Python version next to the anaconda environment name.
- typeset -g POWERLEVEL9K_ANACONDA_SHOW_PYTHON_VERSION=false
- # Separate environment name from Python version only with a space.
- typeset -g POWERLEVEL9K_ANACONDA_{LEFT,RIGHT}_DELIMITER=
+
+ # Anaconda segment format. The following parameters are available within the expansion.
+ #
+ # - CONDA_PREFIX Absolute path to the active Anaconda/Miniconda environment.
+ # - CONDA_DEFAULT_ENV Name of the active Anaconda/Miniconda environment.
+ # - CONDA_PROMPT_MODIFIER Configurable prompt modifier (see below).
+ # - P9K_ANACONDA_PYTHON_VERSION Current python version (python --version).
+ #
+ # CONDA_PROMPT_MODIFIER can be configured with the following command:
+ #
+ # conda config --set env_prompt '({default_env}) '
+ #
+ # The last argument is a Python format string that can use the following variables:
+ #
+ # - prefix The same as CONDA_PREFIX.
+ # - default_env The same as CONDA_DEFAULT_ENV.
+ # - name The last segment of CONDA_PREFIX.
+ # - stacked_env Comma-separated list of names in the environment stack. The first element is
+ # always the same as default_env.
+ #
+ # Note: '({default_env}) ' is the default value of env_prompt.
+ #
+ # The default value of POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION expands to $CONDA_PROMPT_MODIFIER
+ # without the surrounding parentheses, or to the last path component of CONDA_PREFIX if the former
+ # is empty.
+ typeset -g POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION='${${${${CONDA_PROMPT_MODIFIER#\(}% }%\)}:-${CONDA_PREFIX:t}}'
+
# Custom icon.
# typeset -g POWERLEVEL9K_ANACONDA_VISUAL_IDENTIFIER_EXPANSION='โญ'
@@ -836,6 +974,19 @@
typeset -g POWERLEVEL9K_PYENV_PROMPT_ALWAYS_SHOW=false
# If set to false, hide python version if it's equal to "system".
typeset -g POWERLEVEL9K_PYENV_SHOW_SYSTEM=true
+
+ # Pyenv segment format. The following parameters are available within the expansion.
+ #
+ # - P9K_CONTENT Current pyenv environment (pyenv version-name).
+ # - P9K_PYENV_PYTHON_VERSION Current python version (python --version).
+ #
+ # The default format has the following logic:
+ #
+ # 1. Display just "$P9K_CONTENT" if it's equal to "$P9K_PYENV_PYTHON_VERSION" or
+ # starts with "$P9K_PYENV_PYTHON_VERSION/".
+ # 2. Otherwise display "$P9K_CONTENT $P9K_PYENV_PYTHON_VERSION".
+ typeset -g POWERLEVEL9K_PYENV_CONTENT_EXPANSION='${P9K_CONTENT}${${P9K_CONTENT:#$P9K_PYENV_PYTHON_VERSION(|/*)}:+ $P9K_PYENV_PYTHON_VERSION}'
+
# Custom icon.
# typeset -g POWERLEVEL9K_PYENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
@@ -868,6 +1019,11 @@
##############[ nvm: node.js version from nvm (https://github.com/nvm-sh/nvm) ]###############
# Nvm color.
typeset -g POWERLEVEL9K_NVM_FOREGROUND=2
+ # If set to false, hide node version if it's the same as default:
+ # $(nvm version current) == $(nvm version default).
+ typeset -g POWERLEVEL9K_NVM_PROMPT_ALWAYS_SHOW=false
+ # If set to false, hide node version if it's equal to "system".
+ typeset -g POWERLEVEL9K_NVM_SHOW_SYSTEM=true
# Custom icon.
# typeset -g POWERLEVEL9K_NVM_VISUAL_IDENTIFIER_EXPANSION='โญ'
@@ -1017,6 +1173,16 @@
# Custom icon.
# typeset -g POWERLEVEL9K_PLENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ ###########[ perlbrew: perl version from perlbrew (https://github.com/gugod/App-perlbrew) ]############
+ # Perlbrew color.
+ typeset -g POWERLEVEL9K_PERLBREW_FOREGROUND=67
+ # Show perlbrew version only when in a perl project subdirectory.
+ typeset -g POWERLEVEL9K_PERLBREW_PROJECT_ONLY=true
+ # Don't show "perl-" at the front.
+ typeset -g POWERLEVEL9K_PERLBREW_SHOW_PREFIX=false
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_PERLBREW_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
############[ phpenv: php version from phpenv (https://github.com/phpenv/phpenv) ]############
# PHP color.
typeset -g POWERLEVEL9K_PHPENV_FOREGROUND=5
@@ -1030,6 +1196,19 @@
# Custom icon.
# typeset -g POWERLEVEL9K_PHPENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ #######[ scalaenv: scala version from scalaenv (https://github.com/scalaenv/scalaenv) ]#######
+ # Scala color.
+ typeset -g POWERLEVEL9K_SCALAENV_FOREGROUND=1
+ # Hide scala version if it doesn't come from one of these sources.
+ typeset -g POWERLEVEL9K_SCALAENV_SOURCES=(shell local global)
+ # If set to false, hide scala version if it's the same as global:
+ # $(scalaenv version-name) == $(scalaenv global).
+ typeset -g POWERLEVEL9K_SCALAENV_PROMPT_ALWAYS_SHOW=false
+ # If set to false, hide scala version if it's equal to "system".
+ typeset -g POWERLEVEL9K_SCALAENV_SHOW_SYSTEM=true
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_SCALAENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
##########[ haskell_stack: haskell version from stack (https://haskellstack.org/) ]###########
# Haskell color.
typeset -g POWERLEVEL9K_HASKELL_STACK_FOREGROUND=3
@@ -1045,9 +1224,9 @@
# typeset -g POWERLEVEL9K_HASKELL_STACK_VISUAL_IDENTIFIER_EXPANSION='โญ'
#############[ kubecontext: current kubernetes context (https://kubernetes.io/) ]#############
- # Show kubecontext only when the the command you are typing invokes one of these tools.
+ # Show kubecontext only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show kubecontext.
- typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito'
+ typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito|k9s|helmfile|flux|fluxctl|stern|kubeseal|skaffold|kubent|kubecolor|cmctl|sparkctl'
# Kubernetes context classes for the purpose of using different colors, icons and expansions with
# different contexts.
@@ -1132,6 +1311,8 @@
# typeset -g POWERLEVEL9K_KUBECONTEXT_PREFIX='%fat '
################[ terraform: terraform workspace (https://www.terraform.io) ]#################
+ # Don't show terraform workspace if it's literally "default".
+ typeset -g POWERLEVEL9K_TERRAFORM_SHOW_DEFAULT=false
# POWERLEVEL9K_TERRAFORM_CLASSES is an array with even number of elements. The first element
# in each pair defines a pattern against which the current terraform workspace gets matched.
# More specifically, it's P9K_CONTENT prior to the application of context expansion (see below)
@@ -1145,7 +1326,7 @@
# typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
# '*prod*' PROD
# '*test*' TEST
- # '*' DEFAULT)
+ # '*' OTHER)
#
# If your current terraform workspace is "project_test", its class is TEST because "project_test"
# doesn't match the pattern '*prod*' but does match '*test*'.
@@ -1158,14 +1339,20 @@
typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
# '*prod*' PROD # These values are examples that are unlikely
# '*test*' TEST # to match your needs. Customize them as needed.
- '*' DEFAULT)
- typeset -g POWERLEVEL9K_TERRAFORM_DEFAULT_FOREGROUND=4
- # typeset -g POWERLEVEL9K_TERRAFORM_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ '*' OTHER)
+ typeset -g POWERLEVEL9K_TERRAFORM_OTHER_FOREGROUND=4
+ # typeset -g POWERLEVEL9K_TERRAFORM_OTHER_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ #############[ terraform_version: terraform version (https://www.terraform.io) ]##############
+ # Terraform version color.
+ typeset -g POWERLEVEL9K_TERRAFORM_VERSION_FOREGROUND=4
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_TERRAFORM_VERSION_VISUAL_IDENTIFIER_EXPANSION='โญ'
#[ aws: aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) ]#
- # Show aws only when the the command you are typing invokes one of these tools.
+ # Show aws only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show aws.
- typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|terraform|pulumi'
+ typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|cdk|terraform|pulumi|terragrunt'
# POWERLEVEL9K_AWS_CLASSES is an array with even number of elements. The first element
# in each pair defines a pattern against which the current AWS profile gets matched.
@@ -1197,6 +1384,12 @@
typeset -g POWERLEVEL9K_AWS_DEFAULT_FOREGROUND=3
# typeset -g POWERLEVEL9K_AWS_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # AWS segment format. The following parameters are available within the expansion.
+ #
+ # - P9K_AWS_PROFILE The name of the current AWS profile.
+ # - P9K_AWS_REGION The region associated with the current AWS profile.
+ typeset -g POWERLEVEL9K_AWS_CONTENT_EXPANSION='${P9K_AWS_PROFILE//\%/%%}${P9K_AWS_REGION:+ ${P9K_AWS_REGION//\%/%%}}'
+
#[ aws_eb_env: aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/) ]#
# AWS Elastic Beanstalk environment color.
typeset -g POWERLEVEL9K_AWS_EB_ENV_FOREGROUND=2
@@ -1204,37 +1397,88 @@
# typeset -g POWERLEVEL9K_AWS_EB_ENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
##########[ azure: azure account name (https://docs.microsoft.com/en-us/cli/azure) ]##########
- # Show azure only when the the command you are typing invokes one of these tools.
+ # Show azure only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show azure.
- typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi'
+ typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi|terragrunt'
+
+ # POWERLEVEL9K_AZURE_CLASSES is an array with even number of elements. The first element
+ # in each pair defines a pattern against which the current azure account name gets matched.
+ # More specifically, it's P9K_CONTENT prior to the application of context expansion (see below)
+ # that gets matched. If you unset all POWERLEVEL9K_AZURE_*CONTENT_EXPANSION parameters,
+ # you'll see this value in your prompt. The second element of each pair in
+ # POWERLEVEL9K_AZURE_CLASSES defines the account class. Patterns are tried in order. The
+ # first match wins.
+ #
+ # For example, given these settings:
+ #
+ # typeset -g POWERLEVEL9K_AZURE_CLASSES=(
+ # '*prod*' PROD
+ # '*test*' TEST
+ # '*' OTHER)
+ #
+ # If your current azure account is "company_test", its class is TEST because "company_test"
+ # doesn't match the pattern '*prod*' but does match '*test*'.
+ #
+ # You can define different colors, icons and content expansions for different classes:
+ #
+ # typeset -g POWERLEVEL9K_AZURE_TEST_FOREGROUND=2
+ # typeset -g POWERLEVEL9K_AZURE_TEST_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_AZURE_TEST_CONTENT_EXPANSION='> ${P9K_CONTENT} <'
+ typeset -g POWERLEVEL9K_AZURE_CLASSES=(
+ # '*prod*' PROD # These values are examples that are unlikely
+ # '*test*' TEST # to match your needs. Customize them as needed.
+ '*' OTHER)
+
# Azure account name color.
- typeset -g POWERLEVEL9K_AZURE_FOREGROUND=4
+ typeset -g POWERLEVEL9K_AZURE_OTHER_FOREGROUND=4
# Custom icon.
- # typeset -g POWERLEVEL9K_AZURE_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_AZURE_OTHER_VISUAL_IDENTIFIER_EXPANSION='โญ'
##########[ gcloud: google cloud account and project (https://cloud.google.com/) ]###########
- # Show gcloud only when the the command you are typing invokes one of these tools.
+ # Show gcloud only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show gcloud.
- typeset -g POWERLEVEL9K_GCLOUD_SHOW_ON_COMMAND='gcloud|gcs'
+ typeset -g POWERLEVEL9K_GCLOUD_SHOW_ON_COMMAND='gcloud|gcs|gsutil'
# Google cloud color.
typeset -g POWERLEVEL9K_GCLOUD_FOREGROUND=4
- # Google cloud format. Change the value of POWERLEVEL9K_GCLOUD_CONTENT_EXPANSION if the default
- # is too verbose or not informative enough.
+ # Google cloud format. Change the value of POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION and/or
+ # POWERLEVEL9K_GCLOUD_COMPLETE_CONTENT_EXPANSION if the default is too verbose or not informative
+ # enough. You can use the following parameters in the expansions. Each of them corresponds to the
+ # output of `gcloud` tool.
#
- # P9K_GCLOUD_ACCOUNT: the output of `gcloud config get-value account`
- # P9K_GCLOUD_PROJECT: the output of `gcloud config get-value project`
- # ${VARIABLE//\%/%%}: ${VARIABLE} with all occurences of '%' replaced with '%%'.
+ # Parameter | Source
+ # -------------------------|--------------------------------------------------------------------
+ # P9K_GCLOUD_CONFIGURATION | gcloud config configurations list --format='value(name)'
+ # P9K_GCLOUD_ACCOUNT | gcloud config get-value account
+ # P9K_GCLOUD_PROJECT_ID | gcloud config get-value project
+ # P9K_GCLOUD_PROJECT_NAME | gcloud projects describe $P9K_GCLOUD_PROJECT_ID --format='value(name)'
#
- typeset -g POWERLEVEL9K_GCLOUD_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT//\%/%%}'
+ # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced with '%%'.
+ #
+ # Obtaining project name requires sending a request to Google servers. This can take a long time
+ # and even fail. When project name is unknown, P9K_GCLOUD_PROJECT_NAME is not set and gcloud
+ # prompt segment is in state PARTIAL. When project name gets known, P9K_GCLOUD_PROJECT_NAME gets
+ # set and gcloud prompt segment transitions to state COMPLETE.
+ #
+ # You can customize the format, icon and colors of gcloud segment separately for states PARTIAL
+ # and COMPLETE. You can also hide gcloud in state PARTIAL by setting
+ # POWERLEVEL9K_GCLOUD_PARTIAL_VISUAL_IDENTIFIER_EXPANSION and
+ # POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION to empty.
+ typeset -g POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_ID//\%/%%}'
+ typeset -g POWERLEVEL9K_GCLOUD_COMPLETE_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_NAME//\%/%%}'
+
+ # Send a request to Google (by means of `gcloud projects describe ...`) to obtain project name
+ # this often. Negative value disables periodic polling. In this mode project name is retrieved
+ # only when the current configuration, account or project id changes.
+ typeset -g POWERLEVEL9K_GCLOUD_REFRESH_PROJECT_NAME_SECONDS=60
# Custom icon.
# typeset -g POWERLEVEL9K_GCLOUD_VISUAL_IDENTIFIER_EXPANSION='โญ'
#[ google_app_cred: google application credentials (https://cloud.google.com/docs/authentication/production) ]#
- # Show google_app_cred only when the the command you are typing invokes one of these tools.
+ # Show google_app_cred only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show google_app_cred.
- typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi'
+ typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi|terragrunt'
# Google application credentials classes for the purpose of using different colors, icons and
# expansions with different credentials.
@@ -1282,9 +1526,19 @@
# P9K_GOOGLE_APP_CRED_PROJECT_ID | project_id
# P9K_GOOGLE_APP_CRED_CLIENT_EMAIL | client_email
#
- # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurences of '%' replaced by '%%'.
+ # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced by '%%'.
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_CONTENT_EXPANSION='${P9K_GOOGLE_APP_CRED_PROJECT_ID//\%/%%}'
+ ##############[ toolbox: toolbox name (https://github.com/containers/toolbox) ]###############
+ # Toolbox color.
+ typeset -g POWERLEVEL9K_TOOLBOX_FOREGROUND=3
+ # Don't display the name of the toolbox if it matches fedora-toolbox-*.
+ typeset -g POWERLEVEL9K_TOOLBOX_CONTENT_EXPANSION='${P9K_TOOLBOX_NAME:#fedora-toolbox-*}'
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_TOOLBOX_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # Custom prefix.
+ # typeset -g POWERLEVEL9K_TOOLBOX_PREFIX='%fin '
+
###############################[ public_ip: public IP address ]###############################
# Public IP color.
typeset -g POWERLEVEL9K_PUBLIC_IP_FOREGROUND=6
@@ -1299,7 +1553,7 @@
typeset -g POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION=
# Regular expression for the VPN network interface. Run `ifconfig` or `ip -4 a show` while on VPN
# to see the name of the interface.
- typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(wg|(.*tun))[0-9]*'
+ typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(gpd|wg|(.*tun)|tailscale)[0-9]*|(zt.*)'
# If set to true, show one segment per matching network interface. If set to false, show only
# one segment corresponding to the first matching network interface.
# Tip: If you set it to true, you'll probably want to unset POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION.
@@ -1313,17 +1567,19 @@
# The following parameters are accessible within the expansion:
#
# Parameter | Meaning
- # ----------------------+---------------
- # P9K_IP_IP | IP address
- # P9K_IP_INTERFACE | network interface
- # P9K_IP_RX_BYTES | total number of bytes received
- # P9K_IP_TX_BYTES | total number of bytes sent
- # P9K_IP_RX_RATE | receive rate (since last prompt)
- # P9K_IP_TX_RATE | send rate (since last prompt)
+ # ----------------------+-------------------------------------------
+ # P9K_IP_IP | IP address
+ # P9K_IP_INTERFACE | network interface
+ # P9K_IP_RX_BYTES | total number of bytes received
+ # P9K_IP_TX_BYTES | total number of bytes sent
+ # P9K_IP_RX_BYTES_DELTA | number of bytes received since last prompt
+ # P9K_IP_TX_BYTES_DELTA | number of bytes sent since last prompt
+ # P9K_IP_RX_RATE | receive rate (since last prompt)
+ # P9K_IP_TX_RATE | send rate (since last prompt)
typeset -g POWERLEVEL9K_IP_CONTENT_EXPANSION='$P9K_IP_IP${P9K_IP_RX_RATE:+ %2Fโฃ$P9K_IP_RX_RATE}${P9K_IP_TX_RATE:+ %3Fโก$P9K_IP_TX_RATE}'
# Show information for the first network interface whose name matches this regular expression.
# Run `ifconfig` or `ip -4 a show` to see the names of all network interfaces.
- typeset -g POWERLEVEL9K_IP_INTERFACE='e.*'
+ typeset -g POWERLEVEL9K_IP_INTERFACE='[ew].*'
# Custom icon.
# typeset -g POWERLEVEL9K_IP_VISUAL_IDENTIFIER_EXPANSION='โญ'
@@ -1366,15 +1622,11 @@
# Parameter | Meaning
# ----------------------+---------------
# P9K_WIFI_SSID | service set identifier, a.k.a. network name
- # P9K_WIFI_LINK_AUTH | authentication protocol such as "wpa2-psk" or "none"
+ # P9K_WIFI_LINK_AUTH | authentication protocol such as "wpa2-psk" or "none"; empty if unknown
# P9K_WIFI_LAST_TX_RATE | wireless transmit rate in megabits per second
# P9K_WIFI_RSSI | signal strength in dBm, from -120 to 0
# P9K_WIFI_NOISE | noise in dBm, from -120 to 0
# P9K_WIFI_BARS | signal strength in bars, from 0 to 4 (derived from P9K_WIFI_RSSI and P9K_WIFI_NOISE)
- #
- # All parameters except P9K_WIFI_BARS are extracted from the output of the following command:
- #
- # /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I
####################################[ time: current time ]####################################
# Current time color.
@@ -1401,7 +1653,7 @@
# User-defined prompt segments may optionally provide an instant_prompt_* function. Its job
# is to generate the prompt segment for display in instant prompt. See
- # https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt.
+ # https://github.com/romkatv/powerlevel10k#instant-prompt.
#
# Powerlevel10k will call instant_prompt_* at the same time as the regular prompt_* function
# and will record all `p10k segment` calls it makes. When displaying instant prompt, Powerlevel10k
@@ -1437,7 +1689,7 @@
# it incompatible with your zsh configuration files.
# - quiet: Enable instant prompt and don't print warnings when detecting console output
# during zsh initialization. Choose this if you've read and understood
- # https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt.
+ # https://github.com/romkatv/powerlevel10k#instant-prompt.
# - verbose: Enable instant prompt and print a warning when detecting console output during
# zsh initialization. Choose this if you've never tried instant prompt, haven't
# seen the warning, or if you are unsure what this all means.
@@ -1454,5 +1706,8 @@
(( ! $+functions[p10k] )) || p10k reload
}
+# Tell `p10k configure` which file it should overwrite.
+typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a}
+
(( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]}
'builtin' 'unset' 'p10k_config_opts'
diff --git a/config/p10k-lean.zsh b/config/p10k-lean.zsh
index 1ae74ec9..cef70fd8 100644
--- a/config/p10k-lean.zsh
+++ b/config/p10k-lean.zsh
@@ -17,10 +17,10 @@
# Unset all configuration options. This allows you to apply configuration changes without
# restarting zsh. Edit ~/.p10k.zsh and type `source ~/.p10k.zsh`.
- unset -m 'POWERLEVEL9K_*'
+ unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR'
# Zsh >= 5.1 is required.
- autoload -Uz is-at-least && is-at-least 5.1 || return
+ [[ $ZSH_VERSION == (5.<1->*|<6->.*) ]] || return
# The list of segments shown on the left. Fill it with the most important segments.
typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(
@@ -65,22 +65,30 @@
luaenv # lua version from luaenv (https://github.com/cehoffman/luaenv)
jenv # java version from jenv (https://github.com/jenv/jenv)
plenv # perl version from plenv (https://github.com/tokuhirom/plenv)
+ perlbrew # perl version from perlbrew (https://github.com/gugod/App-perlbrew)
phpenv # php version from phpenv (https://github.com/phpenv/phpenv)
+ scalaenv # scala version from scalaenv (https://github.com/scalaenv/scalaenv)
haskell_stack # haskell version from stack (https://haskellstack.org/)
kubecontext # current kubernetes context (https://kubernetes.io/)
terraform # terraform workspace (https://www.terraform.io)
+ # terraform_version # terraform version (https://www.terraform.io)
aws # aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
aws_eb_env # aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/)
azure # azure account name (https://docs.microsoft.com/en-us/cli/azure)
gcloud # google cloud cli account and project (https://cloud.google.com/)
google_app_cred # google application credentials (https://cloud.google.com/docs/authentication/production)
+ toolbox # toolbox name (https://github.com/containers/toolbox)
context # user@hostname
nordvpn # nordvpn connection status, linux only (https://nordvpn.com/)
ranger # ranger shell (https://github.com/ranger/ranger)
+ yazi # yazi shell (https://github.com/sxyazi/yazi)
nnn # nnn shell (https://github.com/jarun/nnn)
+ lf # lf shell (https://github.com/gokcehan/lf)
+ xplr # xplr shell (https://github.com/sayanarijit/xplr)
vim_shell # vim shell indicator (:sh)
midnight_commander # midnight commander shell (https://midnight-commander.org/)
nix_shell # nix shell (https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html)
+ chezmoi_shell # chezmoi shell (https://www.chezmoi.io/)
# vpn_ip # virtual private network indicator
# load # CPU load
# disk_usage # disk usage
@@ -89,6 +97,8 @@
todo # todo items (https://github.com/todotxt/todo.txt-cli)
timewarrior # timewarrior tracking status (https://timewarrior.net/)
taskwarrior # taskwarrior task count (https://taskwarrior.org/)
+ per_directory_history # Oh My Zsh per-directory-history local/global indicator
+ # cpu_arch # CPU architecture
# time # current time
# =========================[ Line #2 ]=========================
newline
@@ -173,8 +183,8 @@
#################################[ os_icon: os identifier ]##################################
# OS identifier color.
typeset -g POWERLEVEL9K_OS_ICON_FOREGROUND=
- # Make the icon bold.
- typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='%B${P9K_CONTENT}'
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='โญ'
################################[ prompt_char: prompt symbol ]################################
# Green prompt symbol if the last command succeeded.
@@ -186,7 +196,7 @@
# Prompt symbol in command vi mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION='โฎ'
# Prompt symbol in visual vi mode.
- typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='โ
ค'
+ typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='V'
# Prompt symbol in overwrite vi mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIOWR_CONTENT_EXPANSION='โถ'
typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=true
@@ -224,7 +234,8 @@
.java-version
.perl-version
.php-version
- .tool-version
+ .tool-versions
+ .mise.toml
.shorten_folder_marker
.svn
.terraform
@@ -236,10 +247,16 @@
stack.yaml
)
typeset -g POWERLEVEL9K_SHORTEN_FOLDER_MARKER="(${(j:|:)anchor_files})"
- # If set to true, remove everything before the last (deepest) subdirectory that contains files
- # matching $POWERLEVEL9K_SHORTEN_FOLDER_MARKER. For example, when the current directory is
- # /foo/bar/git_repo/baz, prompt will display git_repo/baz. This assumes that /foo/bar/git_repo
- # contains a marker (.git) and other directories don't.
+ # If set to "first" ("last"), remove everything before the first (last) subdirectory that contains
+ # files matching $POWERLEVEL9K_SHORTEN_FOLDER_MARKER. For example, when the current directory is
+ # /foo/bar/git_repo/nested_git_repo/baz, prompt will display git_repo/nested_git_repo/baz (first)
+ # or nested_git_repo/baz (last). This assumes that git_repo and nested_git_repo contain markers
+ # and other directories don't.
+ #
+ # Optionally, "first" and "last" can be followed by ":" where is an integer.
+ # This moves the truncation point to the right (positive offset) or to the left (negative offset)
+ # relative to the marker. Plain "first" and "last" are equivalent to "first:0" and "last:0"
+ # respectively.
typeset -g POWERLEVEL9K_DIR_TRUNCATE_BEFORE_MARKER=false
# Don't shorten this many last directory segments. They are anchors.
typeset -g POWERLEVEL9K_SHORTEN_DIR_LENGTH=1
@@ -261,53 +278,70 @@
# the full directory that was used in previous commands.
typeset -g POWERLEVEL9K_DIR_HYPERLINK=false
- # Enable special styling for non-writable directories.
- typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=true
- # Show this icon when the current directory is not writable. POWERLEVEL9K_DIR_SHOW_WRITABLE
- # above must be set to true for this parameter to have effect.
- # typeset -g POWERLEVEL9K_DIR_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # Enable special styling for non-writable and non-existent directories. See POWERLEVEL9K_LOCK_ICON
+ # and POWERLEVEL9K_DIR_CLASSES below.
+ typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=v3
- # Custom prefix.
- # typeset -g POWERLEVEL9K_DIR_PREFIX='%fin '
+ # The default icon shown next to non-writable and non-existent directories when
+ # POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3.
+ # typeset -g POWERLEVEL9K_LOCK_ICON='โญ'
- # POWERLEVEL9K_DIR_CLASSES allows you to specify custom icons for different directories.
- # It must be an array with 3 * N elements. Each triplet consists of:
+ # POWERLEVEL9K_DIR_CLASSES allows you to specify custom icons and colors for different
+ # directories. It must be an array with 3 * N elements. Each triplet consists of:
#
- # 1. A pattern against which the current directory is matched. Matching is done with
+ # 1. A pattern against which the current directory ($PWD) is matched. Matching is done with
# extended_glob option enabled.
# 2. Directory class for the purpose of styling.
- # 3. Icon.
+ # 3. An empty string.
#
- # Triplets are tried in order. The first triplet whose pattern matches $PWD wins. If there
- # are no matches, the directory will have no icon.
+ # Triplets are tried in order. The first triplet whose pattern matches $PWD wins.
#
- # Example:
+ # If POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3, non-writable and non-existent directories
+ # acquire class suffix _NOT_WRITABLE and NON_EXISTENT respectively.
+ #
+ # For example, given these settings:
#
# typeset -g POWERLEVEL9K_DIR_CLASSES=(
- # '~/work(|/*)' WORK '(โฏยฐโกยฐ๏ผโฏ๏ธต โปโโป'
- # '~(|/*)' HOME 'โ'
- # '*' DEFAULT '')
+ # '~/work(|/*)' WORK ''
+ # '~(|/*)' HOME ''
+ # '*' DEFAULT '')
#
- # With these settings, the current directory in the prompt may look like this:
+ # Whenever the current directory is ~/work or a subdirectory of ~/work, it gets styled with one
+ # of the following classes depending on its writability and existence: WORK, WORK_NOT_WRITABLE or
+ # WORK_NON_EXISTENT.
#
- # (โฏยฐโกยฐ๏ผโฏ๏ธต โปโโป ~/work/projects/important/urgent
- #
- # Or like this:
- #
- # โ ~/best/powerlevel10k
- #
- # You can also set different colors for directories of different classes. Remember to override
- # FOREGROUND, SHORTENED_FOREGROUND and ANCHOR_FOREGROUND for every directory class that you wish
- # to have its own color.
+ # Simply assigning classes to directories doesn't have any visible effects. It merely gives you an
+ # option to define custom colors and icons for different directory classes.
#
+ # # Styling for WORK.
+ # typeset -g POWERLEVEL9K_DIR_WORK_VISUAL_IDENTIFIER_EXPANSION='โญ'
# typeset -g POWERLEVEL9K_DIR_WORK_FOREGROUND=31
# typeset -g POWERLEVEL9K_DIR_WORK_SHORTENED_FOREGROUND=103
# typeset -g POWERLEVEL9K_DIR_WORK_ANCHOR_FOREGROUND=39
#
+ # # Styling for WORK_NOT_WRITABLE.
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND=31
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_SHORTENED_FOREGROUND=103
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_ANCHOR_FOREGROUND=39
+ #
+ # # Styling for WORK_NON_EXISTENT.
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_FOREGROUND=31
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_SHORTENED_FOREGROUND=103
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_ANCHOR_FOREGROUND=39
+ #
+ # If a styling parameter isn't explicitly defined for some class, it falls back to the classless
+ # parameter. For example, if POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND is not set, it falls
+ # back to POWERLEVEL9K_DIR_FOREGROUND.
+ #
# typeset -g POWERLEVEL9K_DIR_CLASSES=()
+ # Custom prefix.
+ # typeset -g POWERLEVEL9K_DIR_PREFIX='%fin '
+
#####################################[ vcs: git status ]######################################
- # Branch icon. Set this parameter to '\uF126 ' for the popular Powerline branch icon.
+ # Branch icon. Set this parameter to '\UE0A0 ' for the popular Powerline branch icon.
typeset -g POWERLEVEL9K_VCS_BRANCH_ICON=
# Untracked files icon. It's really a question mark, your font isn't broken.
@@ -316,7 +350,7 @@
# Formatter for Git status.
#
- # Example output: master โฃ42โก42 *42 merge ~42 +42 !42 ?42.
+ # Example output: master wip โฃ42โก42 *42 merge ~42 +42 !42 ?42.
#
# You can edit the function to customize how Git status looks.
#
@@ -349,34 +383,55 @@
fi
local res
- local where # branch or tag
+
if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then
- res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}"
- where=${(V)VCS_STATUS_LOCAL_BRANCH}
- elif [[ -n $VCS_STATUS_TAG ]]; then
- res+="${meta}#"
- where=${(V)VCS_STATUS_TAG}
+ local branch=${(V)VCS_STATUS_LOCAL_BRANCH}
+ # If local branch name is at most 32 characters long, show it in full.
+ # Otherwise show the first 12 โฆ the last 12.
+ # Tip: To always show local branch name in full without truncation, delete the next line.
+ (( $#branch > 32 )) && branch[13,-13]="โฆ" # <-- this line
+ res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}${branch//\%/%%}"
fi
- # If local branch name or tag is at most 32 characters long, show it in full.
- # Otherwise show the first 12 โฆ the last 12.
- (( $#where > 32 )) && where[13,-13]="โฆ"
- res+="${clean}${where//\%/%%}" # escape %
+ if [[ -n $VCS_STATUS_TAG
+ # Show tag only if not on a branch.
+ # Tip: To always show tag, delete the next line.
+ && -z $VCS_STATUS_LOCAL_BRANCH # <-- this line
+ ]]; then
+ local tag=${(V)VCS_STATUS_TAG}
+ # If tag name is at most 32 characters long, show it in full.
+ # Otherwise show the first 12 โฆ the last 12.
+ # Tip: To always show tag name in full without truncation, delete the next line.
+ (( $#tag > 32 )) && tag[13,-13]="โฆ" # <-- this line
+ res+="${meta}#${clean}${tag//\%/%%}"
+ fi
- # Display the current Git commit if there is no branch or tag.
- # Tip: To always display the current Git commit, remove `[[ -z $where ]] &&` from the next line.
- [[ -z $where ]] && res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}"
+ # Display the current Git commit if there is no branch and no tag.
+ # Tip: To always display the current Git commit, delete the next line.
+ [[ -z $VCS_STATUS_LOCAL_BRANCH && -z $VCS_STATUS_TAG ]] && # <-- this line
+ res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}"
# Show tracking branch name if it differs from local branch.
if [[ -n ${VCS_STATUS_REMOTE_BRANCH:#$VCS_STATUS_LOCAL_BRANCH} ]]; then
- res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}" # escape %
+ res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}"
+ fi
+
+ # Display "wip" if the latest commit's summary contains "wip" or "WIP".
+ if [[ $VCS_STATUS_COMMIT_SUMMARY == (|*[^[:alnum:]])(wip|WIP)(|[^[:alnum:]]*) ]]; then
+ res+=" ${modified}wip"
+ fi
+
+ if (( VCS_STATUS_COMMITS_AHEAD || VCS_STATUS_COMMITS_BEHIND )); then
+ # โฃ42 if behind the remote.
+ (( VCS_STATUS_COMMITS_BEHIND )) && res+=" ${clean}โฃ${VCS_STATUS_COMMITS_BEHIND}"
+ # โก42 if ahead of the remote; no leading space if also behind the remote: โฃ42โก42.
+ (( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && res+=" "
+ (( VCS_STATUS_COMMITS_AHEAD )) && res+="${clean}โก${VCS_STATUS_COMMITS_AHEAD}"
+ elif [[ -n $VCS_STATUS_REMOTE_BRANCH ]]; then
+ # Tip: Uncomment the next line to display '=' if up to date with the remote.
+ # res+=" ${clean}="
fi
- # โฃ42 if behind the remote.
- (( VCS_STATUS_COMMITS_BEHIND )) && res+=" ${clean}โฃ${VCS_STATUS_COMMITS_BEHIND}"
- # โก42 if ahead of the remote; no leading space if also behind the remote: โฃ42โก42.
- (( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && res+=" "
- (( VCS_STATUS_COMMITS_AHEAD )) && res+="${clean}โก${VCS_STATUS_COMMITS_AHEAD}"
# โ 42 if behind the push remote.
(( VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" ${clean}โ ${VCS_STATUS_PUSH_COMMITS_BEHIND}"
(( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" "
@@ -418,7 +473,7 @@
# Don't show Git status in prompt for repositories whose workdir matches this pattern.
# For example, if set to '~', the Git repository at $HOME/.git will be ignored.
- # Multiple patterns can be combined with '|': '~|~/some/dir'.
+ # Multiple patterns can be combined with '|': '~(|/foo)|/bar/baz/*'.
typeset -g POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN='~'
# Disable the default Git status formatting.
@@ -439,10 +494,10 @@
# Show status of repositories of these types. You can add svn and/or hg if you are
# using them. If you do, your prompt may become slow even when your current directory
- # isn't in an svn or hg reposotiry.
+ # isn't in an svn or hg repository.
typeset -g POWERLEVEL9K_VCS_BACKENDS=(git)
- # These settings are used for respositories other than Git or when gitstatusd fails and
+ # These settings are used for repositories other than Git or when gitstatusd fails and
# Powerlevel10k has to fall back to using vcs_info.
typeset -g POWERLEVEL9K_VCS_CLEAN_FOREGROUND=76
typeset -g POWERLEVEL9K_VCS_UNTRACKED_FOREGROUND=76
@@ -485,7 +540,7 @@
typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_VISUAL_IDENTIFIER_EXPANSION='โ'
###################[ command_execution_time: duration of the last command ]###################
- # Show duration of the last command if takes longer than this many seconds.
+ # Show duration of the last command if takes at least this many seconds.
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=3
# Show this many fractional digits. Zero means round to seconds.
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0
@@ -514,6 +569,7 @@
###############[ asdf: asdf version manager (https://github.com/asdf-vm/asdf) ]###############
# Default asdf color. Only used to display tools for which there is no color override (see below).
+ # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_FOREGROUND.
typeset -g POWERLEVEL9K_ASDF_FOREGROUND=66
# There are four parameters that can be used to hide asdf tools. Each parameter describes
@@ -559,7 +615,7 @@
typeset -g POWERLEVEL9K_ASDF_SHOW_SYSTEM=true
# If set to non-empty value, hide tools unless there is a file matching the specified file pattern
- # in the current directory, or its parent diretory, or its grandparent directory, and so on.
+ # in the current directory, or its parent directory, or its grandparent directory, and so on.
#
# Note: If this parameter is set to empty value, it won't hide tools.
# Note: SHOW_ON_UPGLOB isn't specific to asdf. It works with all prompt segments.
@@ -646,6 +702,11 @@
# typeset -g POWERLEVEL9K_ASDF_HASKELL_VISUAL_IDENTIFIER_EXPANSION='โญ'
# typeset -g POWERLEVEL9K_ASDF_HASKELL_SHOW_ON_UPGLOB='*.foo|*.bar'
+ # Julia version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_JULIA_FOREGROUND=70
+ # typeset -g POWERLEVEL9K_ASDF_JULIA_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_ASDF_JULIA_SHOW_ON_UPGLOB='*.foo|*.bar'
+
##########[ nordvpn: nordvpn connection status, linux only (https://nordvpn.com/) ]###########
# NordVPN connection indicator color.
typeset -g POWERLEVEL9K_NORDVPN_FOREGROUND=39
@@ -660,6 +721,12 @@
typeset -g POWERLEVEL9K_RANGER_FOREGROUND=178
# Custom icon.
# typeset -g POWERLEVEL9K_RANGER_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ ####################[ yazi: yazi shell (https://github.com/sxyazi/yazi) ]#####################
+ # Yazi shell color.
+ typeset -g POWERLEVEL9K_YAZI_FOREGROUND=178
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_YAZI_VISUAL_IDENTIFIER_EXPANSION='โญ'
######################[ nnn: nnn shell (https://github.com/jarun/nnn) ]#######################
# Nnn shell color.
@@ -667,6 +734,18 @@
# Custom icon.
# typeset -g POWERLEVEL9K_NNN_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ ######################[ lf: lf shell (https://github.com/gokcehan/lf) ]#######################
+ # lf shell color.
+ typeset -g POWERLEVEL9K_LF_FOREGROUND=72
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_LF_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ ##################[ xplr: xplr shell (https://github.com/sayanarijit/xplr) ]##################
+ # xplr shell color.
+ typeset -g POWERLEVEL9K_XPLR_FOREGROUND=72
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_XPLR_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
###########################[ vim_shell: vim shell indicator (:sh) ]###########################
# Vim shell indicator color.
typeset -g POWERLEVEL9K_VIM_SHELL_FOREGROUND=34
@@ -683,13 +762,22 @@
# Nix shell color.
typeset -g POWERLEVEL9K_NIX_SHELL_FOREGROUND=74
+ # Display the icon of nix_shell if PATH contains a subdirectory of /nix/store.
+ # typeset -g POWERLEVEL9K_NIX_SHELL_INFER_FROM_PATH=false
+
# Tip: If you want to see just the icon without "pure" and "impure", uncomment the next line.
# typeset -g POWERLEVEL9K_NIX_SHELL_CONTENT_EXPANSION=
# Custom icon.
# typeset -g POWERLEVEL9K_NIX_SHELL_VISUAL_IDENTIFIER_EXPANSION='โญ'
- ##################################[ disk_usgae: disk usage ]##################################
+ ##################[ chezmoi_shell: chezmoi shell (https://www.chezmoi.io/) ]##################
+ # chezmoi shell color.
+ typeset -g POWERLEVEL9K_CHEZMOI_SHELL_FOREGROUND=33
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_CHEZMOI_SHELL_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ ##################################[ disk_usage: disk usage ]##################################
# Colors for different levels of disk usage.
typeset -g POWERLEVEL9K_DISK_USAGE_NORMAL_FOREGROUND=35
typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_FOREGROUND=220
@@ -765,7 +853,7 @@
##############[ taskwarrior: taskwarrior task count (https://taskwarrior.org/) ]##############
# Taskwarrior color.
typeset -g POWERLEVEL9K_TASKWARRIOR_FOREGROUND=74
-
+
# Taskwarrior segment format. The following parameters are available within the expansion.
#
# - P9K_TASKWARRIOR_PENDING_COUNT The number of pending tasks: `task +PENDING count`.
@@ -782,6 +870,30 @@
# Custom icon.
# typeset -g POWERLEVEL9K_TASKWARRIOR_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ ######[ per_directory_history: Oh My Zsh per-directory-history local/global indicator ]#######
+ # Color when using local/global history.
+ typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_FOREGROUND=135
+ typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_FOREGROUND=130
+
+ # Tip: Uncomment the next two lines to hide "local"/"global" text and leave just the icon.
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_CONTENT_EXPANSION=''
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_CONTENT_EXPANSION=''
+
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ ################################[ cpu_arch: CPU architecture ]################################
+ # CPU architecture color.
+ typeset -g POWERLEVEL9K_CPU_ARCH_FOREGROUND=172
+
+ # Hide the segment when on a specific CPU architecture.
+ # typeset -g POWERLEVEL9K_CPU_ARCH_X86_64_CONTENT_EXPANSION=
+ # typeset -g POWERLEVEL9K_CPU_ARCH_X86_64_VISUAL_IDENTIFIER_EXPANSION=
+
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_CPU_ARCH_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
##################################[ context: user@hostname ]##################################
# Context color when running with privileges.
typeset -g POWERLEVEL9K_CONTEXT_ROOT_FOREGROUND=178
@@ -811,6 +923,9 @@
typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=37
# Don't show Python version next to the virtual environment name.
typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false
+ # If set to "false", won't show virtualenv if pyenv is already shown.
+ # If set to "if-different", won't show virtualenv if it's the same as pyenv.
+ typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_WITH_PYENV=false
# Separate environment name from Python version only with a space.
typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER=
# Custom icon.
@@ -819,10 +934,33 @@
#####################[ anaconda: conda environment (https://conda.io/) ]######################
# Anaconda environment color.
typeset -g POWERLEVEL9K_ANACONDA_FOREGROUND=37
- # Don't show Python version next to the anaconda environment name.
- typeset -g POWERLEVEL9K_ANACONDA_SHOW_PYTHON_VERSION=false
- # Separate environment name from Python version only with a space.
- typeset -g POWERLEVEL9K_ANACONDA_{LEFT,RIGHT}_DELIMITER=
+
+ # Anaconda segment format. The following parameters are available within the expansion.
+ #
+ # - CONDA_PREFIX Absolute path to the active Anaconda/Miniconda environment.
+ # - CONDA_DEFAULT_ENV Name of the active Anaconda/Miniconda environment.
+ # - CONDA_PROMPT_MODIFIER Configurable prompt modifier (see below).
+ # - P9K_ANACONDA_PYTHON_VERSION Current python version (python --version).
+ #
+ # CONDA_PROMPT_MODIFIER can be configured with the following command:
+ #
+ # conda config --set env_prompt '({default_env}) '
+ #
+ # The last argument is a Python format string that can use the following variables:
+ #
+ # - prefix The same as CONDA_PREFIX.
+ # - default_env The same as CONDA_DEFAULT_ENV.
+ # - name The last segment of CONDA_PREFIX.
+ # - stacked_env Comma-separated list of names in the environment stack. The first element is
+ # always the same as default_env.
+ #
+ # Note: '({default_env}) ' is the default value of env_prompt.
+ #
+ # The default value of POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION expands to $CONDA_PROMPT_MODIFIER
+ # without the surrounding parentheses, or to the last path component of CONDA_PREFIX if the former
+ # is empty.
+ typeset -g POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION='${${${${CONDA_PROMPT_MODIFIER#\(}% }%\)}:-${CONDA_PREFIX:t}}'
+
# Custom icon.
# typeset -g POWERLEVEL9K_ANACONDA_VISUAL_IDENTIFIER_EXPANSION='โญ'
@@ -836,6 +974,19 @@
typeset -g POWERLEVEL9K_PYENV_PROMPT_ALWAYS_SHOW=false
# If set to false, hide python version if it's equal to "system".
typeset -g POWERLEVEL9K_PYENV_SHOW_SYSTEM=true
+
+ # Pyenv segment format. The following parameters are available within the expansion.
+ #
+ # - P9K_CONTENT Current pyenv environment (pyenv version-name).
+ # - P9K_PYENV_PYTHON_VERSION Current python version (python --version).
+ #
+ # The default format has the following logic:
+ #
+ # 1. Display just "$P9K_CONTENT" if it's equal to "$P9K_PYENV_PYTHON_VERSION" or
+ # starts with "$P9K_PYENV_PYTHON_VERSION/".
+ # 2. Otherwise display "$P9K_CONTENT $P9K_PYENV_PYTHON_VERSION".
+ typeset -g POWERLEVEL9K_PYENV_CONTENT_EXPANSION='${P9K_CONTENT}${${P9K_CONTENT:#$P9K_PYENV_PYTHON_VERSION(|/*)}:+ $P9K_PYENV_PYTHON_VERSION}'
+
# Custom icon.
# typeset -g POWERLEVEL9K_PYENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
@@ -868,6 +1019,11 @@
##############[ nvm: node.js version from nvm (https://github.com/nvm-sh/nvm) ]###############
# Nvm color.
typeset -g POWERLEVEL9K_NVM_FOREGROUND=70
+ # If set to false, hide node version if it's the same as default:
+ # $(nvm version current) == $(nvm version default).
+ typeset -g POWERLEVEL9K_NVM_PROMPT_ALWAYS_SHOW=false
+ # If set to false, hide node version if it's equal to "system".
+ typeset -g POWERLEVEL9K_NVM_SHOW_SYSTEM=true
# Custom icon.
# typeset -g POWERLEVEL9K_NVM_VISUAL_IDENTIFIER_EXPANSION='โญ'
@@ -1017,6 +1173,16 @@
# Custom icon.
# typeset -g POWERLEVEL9K_PLENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ ###########[ perlbrew: perl version from perlbrew (https://github.com/gugod/App-perlbrew) ]############
+ # Perlbrew color.
+ typeset -g POWERLEVEL9K_PERLBREW_FOREGROUND=67
+ # Show perlbrew version only when in a perl project subdirectory.
+ typeset -g POWERLEVEL9K_PERLBREW_PROJECT_ONLY=true
+ # Don't show "perl-" at the front.
+ typeset -g POWERLEVEL9K_PERLBREW_SHOW_PREFIX=false
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_PERLBREW_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
############[ phpenv: php version from phpenv (https://github.com/phpenv/phpenv) ]############
# PHP color.
typeset -g POWERLEVEL9K_PHPENV_FOREGROUND=99
@@ -1030,6 +1196,19 @@
# Custom icon.
# typeset -g POWERLEVEL9K_PHPENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ #######[ scalaenv: scala version from scalaenv (https://github.com/scalaenv/scalaenv) ]#######
+ # Scala color.
+ typeset -g POWERLEVEL9K_SCALAENV_FOREGROUND=160
+ # Hide scala version if it doesn't come from one of these sources.
+ typeset -g POWERLEVEL9K_SCALAENV_SOURCES=(shell local global)
+ # If set to false, hide scala version if it's the same as global:
+ # $(scalaenv version-name) == $(scalaenv global).
+ typeset -g POWERLEVEL9K_SCALAENV_PROMPT_ALWAYS_SHOW=false
+ # If set to false, hide scala version if it's equal to "system".
+ typeset -g POWERLEVEL9K_SCALAENV_SHOW_SYSTEM=true
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_SCALAENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
##########[ haskell_stack: haskell version from stack (https://haskellstack.org/) ]###########
# Haskell color.
typeset -g POWERLEVEL9K_HASKELL_STACK_FOREGROUND=172
@@ -1045,9 +1224,9 @@
# typeset -g POWERLEVEL9K_HASKELL_STACK_VISUAL_IDENTIFIER_EXPANSION='โญ'
#############[ kubecontext: current kubernetes context (https://kubernetes.io/) ]#############
- # Show kubecontext only when the the command you are typing invokes one of these tools.
+ # Show kubecontext only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show kubecontext.
- typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito'
+ typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito|k9s|helmfile|flux|fluxctl|stern|kubeseal|skaffold|kubent|kubecolor|cmctl|sparkctl'
# Kubernetes context classes for the purpose of using different colors, icons and expansions with
# different contexts.
@@ -1132,6 +1311,8 @@
# typeset -g POWERLEVEL9K_KUBECONTEXT_PREFIX='%fat '
################[ terraform: terraform workspace (https://www.terraform.io) ]#################
+ # Don't show terraform workspace if it's literally "default".
+ typeset -g POWERLEVEL9K_TERRAFORM_SHOW_DEFAULT=false
# POWERLEVEL9K_TERRAFORM_CLASSES is an array with even number of elements. The first element
# in each pair defines a pattern against which the current terraform workspace gets matched.
# More specifically, it's P9K_CONTENT prior to the application of context expansion (see below)
@@ -1145,7 +1326,7 @@
# typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
# '*prod*' PROD
# '*test*' TEST
- # '*' DEFAULT)
+ # '*' OTHER)
#
# If your current terraform workspace is "project_test", its class is TEST because "project_test"
# doesn't match the pattern '*prod*' but does match '*test*'.
@@ -1158,14 +1339,20 @@
typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
# '*prod*' PROD # These values are examples that are unlikely
# '*test*' TEST # to match your needs. Customize them as needed.
- '*' DEFAULT)
- typeset -g POWERLEVEL9K_TERRAFORM_DEFAULT_FOREGROUND=38
- # typeset -g POWERLEVEL9K_TERRAFORM_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ '*' OTHER)
+ typeset -g POWERLEVEL9K_TERRAFORM_OTHER_FOREGROUND=38
+ # typeset -g POWERLEVEL9K_TERRAFORM_OTHER_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ #############[ terraform_version: terraform version (https://www.terraform.io) ]##############
+ # Terraform version color.
+ typeset -g POWERLEVEL9K_TERRAFORM_VERSION_FOREGROUND=38
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_TERRAFORM_VERSION_VISUAL_IDENTIFIER_EXPANSION='โญ'
#[ aws: aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) ]#
- # Show aws only when the the command you are typing invokes one of these tools.
+ # Show aws only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show aws.
- typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|terraform|pulumi'
+ typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|cdk|terraform|pulumi|terragrunt'
# POWERLEVEL9K_AWS_CLASSES is an array with even number of elements. The first element
# in each pair defines a pattern against which the current AWS profile gets matched.
@@ -1197,6 +1384,12 @@
typeset -g POWERLEVEL9K_AWS_DEFAULT_FOREGROUND=208
# typeset -g POWERLEVEL9K_AWS_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # AWS segment format. The following parameters are available within the expansion.
+ #
+ # - P9K_AWS_PROFILE The name of the current AWS profile.
+ # - P9K_AWS_REGION The region associated with the current AWS profile.
+ typeset -g POWERLEVEL9K_AWS_CONTENT_EXPANSION='${P9K_AWS_PROFILE//\%/%%}${P9K_AWS_REGION:+ ${P9K_AWS_REGION//\%/%%}}'
+
#[ aws_eb_env: aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/) ]#
# AWS Elastic Beanstalk environment color.
typeset -g POWERLEVEL9K_AWS_EB_ENV_FOREGROUND=70
@@ -1204,37 +1397,88 @@
# typeset -g POWERLEVEL9K_AWS_EB_ENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
##########[ azure: azure account name (https://docs.microsoft.com/en-us/cli/azure) ]##########
- # Show azure only when the the command you are typing invokes one of these tools.
+ # Show azure only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show azure.
- typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi'
+ typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi|terragrunt'
+
+ # POWERLEVEL9K_AZURE_CLASSES is an array with even number of elements. The first element
+ # in each pair defines a pattern against which the current azure account name gets matched.
+ # More specifically, it's P9K_CONTENT prior to the application of context expansion (see below)
+ # that gets matched. If you unset all POWERLEVEL9K_AZURE_*CONTENT_EXPANSION parameters,
+ # you'll see this value in your prompt. The second element of each pair in
+ # POWERLEVEL9K_AZURE_CLASSES defines the account class. Patterns are tried in order. The
+ # first match wins.
+ #
+ # For example, given these settings:
+ #
+ # typeset -g POWERLEVEL9K_AZURE_CLASSES=(
+ # '*prod*' PROD
+ # '*test*' TEST
+ # '*' OTHER)
+ #
+ # If your current azure account is "company_test", its class is TEST because "company_test"
+ # doesn't match the pattern '*prod*' but does match '*test*'.
+ #
+ # You can define different colors, icons and content expansions for different classes:
+ #
+ # typeset -g POWERLEVEL9K_AZURE_TEST_FOREGROUND=28
+ # typeset -g POWERLEVEL9K_AZURE_TEST_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_AZURE_TEST_CONTENT_EXPANSION='> ${P9K_CONTENT} <'
+ typeset -g POWERLEVEL9K_AZURE_CLASSES=(
+ # '*prod*' PROD # These values are examples that are unlikely
+ # '*test*' TEST # to match your needs. Customize them as needed.
+ '*' OTHER)
+
# Azure account name color.
- typeset -g POWERLEVEL9K_AZURE_FOREGROUND=32
+ typeset -g POWERLEVEL9K_AZURE_OTHER_FOREGROUND=32
# Custom icon.
- # typeset -g POWERLEVEL9K_AZURE_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_AZURE_OTHER_VISUAL_IDENTIFIER_EXPANSION='โญ'
##########[ gcloud: google cloud account and project (https://cloud.google.com/) ]###########
- # Show gcloud only when the the command you are typing invokes one of these tools.
+ # Show gcloud only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show gcloud.
- typeset -g POWERLEVEL9K_GCLOUD_SHOW_ON_COMMAND='gcloud|gcs'
+ typeset -g POWERLEVEL9K_GCLOUD_SHOW_ON_COMMAND='gcloud|gcs|gsutil'
# Google cloud color.
typeset -g POWERLEVEL9K_GCLOUD_FOREGROUND=32
- # Google cloud format. Change the value of POWERLEVEL9K_GCLOUD_CONTENT_EXPANSION if the default
- # is too verbose or not informative enough.
+ # Google cloud format. Change the value of POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION and/or
+ # POWERLEVEL9K_GCLOUD_COMPLETE_CONTENT_EXPANSION if the default is too verbose or not informative
+ # enough. You can use the following parameters in the expansions. Each of them corresponds to the
+ # output of `gcloud` tool.
#
- # P9K_GCLOUD_ACCOUNT: the output of `gcloud config get-value account`
- # P9K_GCLOUD_PROJECT: the output of `gcloud config get-value project`
- # ${VARIABLE//\%/%%}: ${VARIABLE} with all occurences of '%' replaced with '%%'.
+ # Parameter | Source
+ # -------------------------|--------------------------------------------------------------------
+ # P9K_GCLOUD_CONFIGURATION | gcloud config configurations list --format='value(name)'
+ # P9K_GCLOUD_ACCOUNT | gcloud config get-value account
+ # P9K_GCLOUD_PROJECT_ID | gcloud config get-value project
+ # P9K_GCLOUD_PROJECT_NAME | gcloud projects describe $P9K_GCLOUD_PROJECT_ID --format='value(name)'
#
- typeset -g POWERLEVEL9K_GCLOUD_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT//\%/%%}'
+ # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced with '%%'.
+ #
+ # Obtaining project name requires sending a request to Google servers. This can take a long time
+ # and even fail. When project name is unknown, P9K_GCLOUD_PROJECT_NAME is not set and gcloud
+ # prompt segment is in state PARTIAL. When project name gets known, P9K_GCLOUD_PROJECT_NAME gets
+ # set and gcloud prompt segment transitions to state COMPLETE.
+ #
+ # You can customize the format, icon and colors of gcloud segment separately for states PARTIAL
+ # and COMPLETE. You can also hide gcloud in state PARTIAL by setting
+ # POWERLEVEL9K_GCLOUD_PARTIAL_VISUAL_IDENTIFIER_EXPANSION and
+ # POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION to empty.
+ typeset -g POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_ID//\%/%%}'
+ typeset -g POWERLEVEL9K_GCLOUD_COMPLETE_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_NAME//\%/%%}'
+
+ # Send a request to Google (by means of `gcloud projects describe ...`) to obtain project name
+ # this often. Negative value disables periodic polling. In this mode project name is retrieved
+ # only when the current configuration, account or project id changes.
+ typeset -g POWERLEVEL9K_GCLOUD_REFRESH_PROJECT_NAME_SECONDS=60
# Custom icon.
# typeset -g POWERLEVEL9K_GCLOUD_VISUAL_IDENTIFIER_EXPANSION='โญ'
#[ google_app_cred: google application credentials (https://cloud.google.com/docs/authentication/production) ]#
- # Show google_app_cred only when the the command you are typing invokes one of these tools.
+ # Show google_app_cred only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show google_app_cred.
- typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi'
+ typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi|terragrunt'
# Google application credentials classes for the purpose of using different colors, icons and
# expansions with different credentials.
@@ -1282,9 +1526,19 @@
# P9K_GOOGLE_APP_CRED_PROJECT_ID | project_id
# P9K_GOOGLE_APP_CRED_CLIENT_EMAIL | client_email
#
- # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurences of '%' replaced by '%%'.
+ # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced by '%%'.
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_CONTENT_EXPANSION='${P9K_GOOGLE_APP_CRED_PROJECT_ID//\%/%%}'
+ ##############[ toolbox: toolbox name (https://github.com/containers/toolbox) ]###############
+ # Toolbox color.
+ typeset -g POWERLEVEL9K_TOOLBOX_FOREGROUND=178
+ # Don't display the name of the toolbox if it matches fedora-toolbox-*.
+ typeset -g POWERLEVEL9K_TOOLBOX_CONTENT_EXPANSION='${P9K_TOOLBOX_NAME:#fedora-toolbox-*}'
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_TOOLBOX_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # Custom prefix.
+ # typeset -g POWERLEVEL9K_TOOLBOX_PREFIX='%fin '
+
###############################[ public_ip: public IP address ]###############################
# Public IP color.
typeset -g POWERLEVEL9K_PUBLIC_IP_FOREGROUND=94
@@ -1299,7 +1553,7 @@
typeset -g POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION=
# Regular expression for the VPN network interface. Run `ifconfig` or `ip -4 a show` while on VPN
# to see the name of the interface.
- typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(wg|(.*tun))[0-9]*'
+ typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(gpd|wg|(.*tun)|tailscale)[0-9]*|(zt.*)'
# If set to true, show one segment per matching network interface. If set to false, show only
# one segment corresponding to the first matching network interface.
# Tip: If you set it to true, you'll probably want to unset POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION.
@@ -1313,17 +1567,19 @@
# The following parameters are accessible within the expansion:
#
# Parameter | Meaning
- # ----------------------+---------------
- # P9K_IP_IP | IP address
- # P9K_IP_INTERFACE | network interface
- # P9K_IP_RX_BYTES | total number of bytes received
- # P9K_IP_TX_BYTES | total number of bytes sent
- # P9K_IP_RX_RATE | receive rate (since last prompt)
- # P9K_IP_TX_RATE | send rate (since last prompt)
+ # ----------------------+-------------------------------------------
+ # P9K_IP_IP | IP address
+ # P9K_IP_INTERFACE | network interface
+ # P9K_IP_RX_BYTES | total number of bytes received
+ # P9K_IP_TX_BYTES | total number of bytes sent
+ # P9K_IP_RX_BYTES_DELTA | number of bytes received since last prompt
+ # P9K_IP_TX_BYTES_DELTA | number of bytes sent since last prompt
+ # P9K_IP_RX_RATE | receive rate (since last prompt)
+ # P9K_IP_TX_RATE | send rate (since last prompt)
typeset -g POWERLEVEL9K_IP_CONTENT_EXPANSION='$P9K_IP_IP${P9K_IP_RX_RATE:+ %70Fโฃ$P9K_IP_RX_RATE}${P9K_IP_TX_RATE:+ %215Fโก$P9K_IP_TX_RATE}'
# Show information for the first network interface whose name matches this regular expression.
# Run `ifconfig` or `ip -4 a show` to see the names of all network interfaces.
- typeset -g POWERLEVEL9K_IP_INTERFACE='e.*'
+ typeset -g POWERLEVEL9K_IP_INTERFACE='[ew].*'
# Custom icon.
# typeset -g POWERLEVEL9K_IP_VISUAL_IDENTIFIER_EXPANSION='โญ'
@@ -1366,15 +1622,11 @@
# Parameter | Meaning
# ----------------------+---------------
# P9K_WIFI_SSID | service set identifier, a.k.a. network name
- # P9K_WIFI_LINK_AUTH | authentication protocol such as "wpa2-psk" or "none"
+ # P9K_WIFI_LINK_AUTH | authentication protocol such as "wpa2-psk" or "none"; empty if unknown
# P9K_WIFI_LAST_TX_RATE | wireless transmit rate in megabits per second
# P9K_WIFI_RSSI | signal strength in dBm, from -120 to 0
# P9K_WIFI_NOISE | noise in dBm, from -120 to 0
# P9K_WIFI_BARS | signal strength in bars, from 0 to 4 (derived from P9K_WIFI_RSSI and P9K_WIFI_NOISE)
- #
- # All parameters except P9K_WIFI_BARS are extracted from the output of the following command:
- #
- # /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I
####################################[ time: current time ]####################################
# Current time color.
@@ -1401,7 +1653,7 @@
# User-defined prompt segments may optionally provide an instant_prompt_* function. Its job
# is to generate the prompt segment for display in instant prompt. See
- # https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt.
+ # https://github.com/romkatv/powerlevel10k#instant-prompt.
#
# Powerlevel10k will call instant_prompt_* at the same time as the regular prompt_* function
# and will record all `p10k segment` calls it makes. When displaying instant prompt, Powerlevel10k
@@ -1437,7 +1689,7 @@
# it incompatible with your zsh configuration files.
# - quiet: Enable instant prompt and don't print warnings when detecting console output
# during zsh initialization. Choose this if you've read and understood
- # https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt.
+ # https://github.com/romkatv/powerlevel10k#instant-prompt.
# - verbose: Enable instant prompt and print a warning when detecting console output during
# zsh initialization. Choose this if you've never tried instant prompt, haven't
# seen the warning, or if you are unsure what this all means.
@@ -1454,5 +1706,8 @@
(( ! $+functions[p10k] )) || p10k reload
}
+# Tell `p10k configure` which file it should overwrite.
+typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a}
+
(( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]}
'builtin' 'unset' 'p10k_config_opts'
diff --git a/config/p10k-pure.zsh b/config/p10k-pure.zsh
index bda5d3d3..7a4d2441 100644
--- a/config/p10k-pure.zsh
+++ b/config/p10k-pure.zsh
@@ -23,13 +23,13 @@
'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand'
() {
- emulate -L zsh
+ emulate -L zsh -o extended_glob
# Unset all configuration options.
- unset -m 'POWERLEVEL9K_*'
+ unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR'
# Zsh >= 5.1 is required.
- autoload -Uz is-at-least && is-at-least 5.1 || return
+ [[ $ZSH_VERSION == (5.<1->*|<6->.*) ]] || return
# Prompt colors.
local grey=242
@@ -169,7 +169,7 @@
# it incompatible with your zsh configuration files.
# - quiet: Enable instant prompt and don't print warnings when detecting console output
# during zsh initialization. Choose this if you've read and understood
- # https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt.
+ # https://github.com/romkatv/powerlevel10k#instant-prompt.
# - verbose: Enable instant prompt and print a warning when detecting console output during
# zsh initialization. Choose this if you've never tried instant prompt, haven't
# seen the warning, or if you are unsure what this all means.
@@ -186,5 +186,8 @@
(( ! $+functions[p10k] )) || p10k reload
}
+# Tell `p10k configure` which file it should overwrite.
+typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a}
+
(( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]}
'builtin' 'unset' 'p10k_config_opts'
diff --git a/config/p10k-rainbow.zsh b/config/p10k-rainbow.zsh
index 74957a43..ed74b7fa 100644
--- a/config/p10k-rainbow.zsh
+++ b/config/p10k-rainbow.zsh
@@ -17,10 +17,10 @@
# Unset all configuration options. This allows you to apply configuration changes without
# restarting zsh. Edit ~/.p10k.zsh and type `source ~/.p10k.zsh`.
- unset -m 'POWERLEVEL9K_*'
+ unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR'
# Zsh >= 5.1 is required.
- autoload -Uz is-at-least && is-at-least 5.1 || return
+ [[ $ZSH_VERSION == (5.<1->*|<6->.*) ]] || return
# The list of segments shown on the left. Fill it with the most important segments.
typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(
@@ -65,23 +65,31 @@
luaenv # lua version from luaenv (https://github.com/cehoffman/luaenv)
jenv # java version from jenv (https://github.com/jenv/jenv)
plenv # perl version from plenv (https://github.com/tokuhirom/plenv)
+ perlbrew # perl version from perlbrew (https://github.com/gugod/App-perlbrew)
phpenv # php version from phpenv (https://github.com/phpenv/phpenv)
+ scalaenv # scala version from scalaenv (https://github.com/scalaenv/scalaenv)
haskell_stack # haskell version from stack (https://haskellstack.org/)
kubecontext # current kubernetes context (https://kubernetes.io/)
terraform # terraform workspace (https://www.terraform.io)
+ # terraform_version # terraform version (https://www.terraform.io)
aws # aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
aws_eb_env # aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/)
azure # azure account name (https://docs.microsoft.com/en-us/cli/azure)
gcloud # google cloud cli account and project (https://cloud.google.com/)
google_app_cred # google application credentials (https://cloud.google.com/docs/authentication/production)
+ toolbox # toolbox name (https://github.com/containers/toolbox)
context # user@hostname
nordvpn # nordvpn connection status, linux only (https://nordvpn.com/)
ranger # ranger shell (https://github.com/ranger/ranger)
+ yazi # yazi shell (https://github.com/sxyazi/yazi)
nnn # nnn shell (https://github.com/jarun/nnn)
+ lf # lf shell (https://github.com/gokcehan/lf)
+ xplr # xplr shell (https://github.com/sayanarijit/xplr)
vim_shell # vim shell indicator (:sh)
midnight_commander # midnight commander shell (https://midnight-commander.org/)
nix_shell # nix shell (https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html)
- vi_mode # vi mode (you don't need this if you've enabled prompt_char)
+ chezmoi_shell # chezmoi shell (https://www.chezmoi.io/)
+ # vi_mode # vi mode (you don't need this if you've enabled prompt_char)
# vpn_ip # virtual private network indicator
# load # CPU load
# disk_usage # disk usage
@@ -90,6 +98,8 @@
todo # todo items (https://github.com/todotxt/todo.txt-cli)
timewarrior # timewarrior tracking status (https://timewarrior.net/)
taskwarrior # taskwarrior task count (https://taskwarrior.org/)
+ per_directory_history # Oh My Zsh per-directory-history local/global indicator
+ # cpu_arch # CPU architecture
# time # current time
# =========================[ Line #2 ]=========================
newline
@@ -136,9 +146,10 @@
# Filler between left and right prompt on the first prompt line. You can set it to ' ', 'ยท' or
# 'โ'. The last two make it easier to see the alignment between left and right prompt and to
# separate prompt from command output. You might want to set POWERLEVEL9K_PROMPT_ADD_NEWLINE=false
- # for more compact prompt if using using this option.
+ # for more compact prompt if using this option.
typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR=' '
typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_BACKGROUND=
+ typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_GAP_BACKGROUND=
if [[ $POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR != ' ' ]]; then
# The color of the filler. You'll probably want to match the color of POWERLEVEL9K_MULTILINE
# ornaments defined above.
@@ -157,6 +168,9 @@
typeset -g POWERLEVEL9K_LEFT_SEGMENT_SEPARATOR='\uE0B0'
# Separator between different-color segments on the right.
typeset -g POWERLEVEL9K_RIGHT_SEGMENT_SEPARATOR='\uE0B2'
+ # To remove a separator between two segments, add "_joined" to the second segment name.
+ # For example: POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(os_icon context_joined)
+
# The right end of left prompt.
typeset -g POWERLEVEL9K_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL='\uE0B0'
# The left end of right prompt.
@@ -172,8 +186,8 @@
# OS identifier color.
typeset -g POWERLEVEL9K_OS_ICON_FOREGROUND=232
typeset -g POWERLEVEL9K_OS_ICON_BACKGROUND=7
- # Make the icon bold.
- typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='%B${P9K_CONTENT}'
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='โญ'
################################[ prompt_char: prompt symbol ]################################
# Transparent background.
@@ -187,7 +201,7 @@
# Prompt symbol in command vi mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION='โฎ'
# Prompt symbol in visual vi mode.
- typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='โ
ค'
+ typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='V'
# Prompt symbol in overwrite vi mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIOWR_CONTENT_EXPANSION='โถ'
typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=true
@@ -200,7 +214,7 @@
##################################[ dir: current directory ]##################################
# Current directory background color.
- # typeset -g POWERLEVEL9K_DIR_BACKGROUND=4
+ typeset -g POWERLEVEL9K_DIR_BACKGROUND=4
# Default current directory foreground color.
typeset -g POWERLEVEL9K_DIR_FOREGROUND=254
# If directory is too long, shorten some of its segments to the shortest possible unique
@@ -229,7 +243,8 @@
.java-version
.perl-version
.php-version
- .tool-version
+ .tool-versions
+ .mise.toml
.shorten_folder_marker
.svn
.terraform
@@ -241,10 +256,16 @@
stack.yaml
)
typeset -g POWERLEVEL9K_SHORTEN_FOLDER_MARKER="(${(j:|:)anchor_files})"
- # If set to true, remove everything before the last (deepest) subdirectory that contains files
- # matching $POWERLEVEL9K_SHORTEN_FOLDER_MARKER. For example, when the current directory is
- # /foo/bar/git_repo/baz, prompt will display git_repo/baz. This assumes that /foo/bar/git_repo
- # contains a marker (.git) and other directories don't.
+ # If set to "first" ("last"), remove everything before the first (last) subdirectory that contains
+ # files matching $POWERLEVEL9K_SHORTEN_FOLDER_MARKER. For example, when the current directory is
+ # /foo/bar/git_repo/nested_git_repo/baz, prompt will display git_repo/nested_git_repo/baz (first)
+ # or nested_git_repo/baz (last). This assumes that git_repo and nested_git_repo contain markers
+ # and other directories don't.
+ #
+ # Optionally, "first" and "last" can be followed by ":" where is an integer.
+ # This moves the truncation point to the right (positive offset) or to the left (negative offset)
+ # relative to the marker. Plain "first" and "last" are equivalent to "first:0" and "last:0"
+ # respectively.
typeset -g POWERLEVEL9K_DIR_TRUNCATE_BEFORE_MARKER=false
# Don't shorten this many last directory segments. They are anchors.
typeset -g POWERLEVEL9K_SHORTEN_DIR_LENGTH=1
@@ -266,61 +287,80 @@
# the full directory that was used in previous commands.
typeset -g POWERLEVEL9K_DIR_HYPERLINK=false
- # Enable special styling for non-writable directories.
- typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=true
- # Show this icon when the current directory is not writable. POWERLEVEL9K_DIR_SHOW_WRITABLE
- # above must be set to true for this parameter to have effect.
- # typeset -g POWERLEVEL9K_DIR_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # Enable special styling for non-writable and non-existent directories. See POWERLEVEL9K_LOCK_ICON
+ # and POWERLEVEL9K_DIR_CLASSES below.
+ typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=v3
- # Custom prefix.
- # typeset -g POWERLEVEL9K_DIR_PREFIX='in '
+ # The default icon shown next to non-writable and non-existent directories when
+ # POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3.
+ # typeset -g POWERLEVEL9K_LOCK_ICON='โญ'
- # POWERLEVEL9K_DIR_CLASSES allows you to specify custom icons for different directories.
- # It must be an array with 3 * N elements. Each triplet consists of:
+ # POWERLEVEL9K_DIR_CLASSES allows you to specify custom icons and colors for different
+ # directories. It must be an array with 3 * N elements. Each triplet consists of:
#
- # 1. A pattern against which the current directory is matched. Matching is done with
+ # 1. A pattern against which the current directory ($PWD) is matched. Matching is done with
# extended_glob option enabled.
# 2. Directory class for the purpose of styling.
- # 3. Icon.
+ # 3. An empty string.
#
- # Triplets are tried in order. The first triplet whose pattern matches $PWD wins. If there
- # are no matches, the directory will have no icon.
+ # Triplets are tried in order. The first triplet whose pattern matches $PWD wins.
#
- # Example:
+ # If POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3, non-writable and non-existent directories
+ # acquire class suffix _NOT_WRITABLE and NON_EXISTENT respectively.
+ #
+ # For example, given these settings:
#
# typeset -g POWERLEVEL9K_DIR_CLASSES=(
- # '~/work(|/*)' WORK '(โฏยฐโกยฐ๏ผโฏ๏ธต โปโโป'
- # '~(|/*)' HOME 'โ'
- # '*' DEFAULT '')
+ # '~/work(|/*)' WORK ''
+ # '~(|/*)' HOME ''
+ # '*' DEFAULT '')
#
- # With these settings, the current directory in the prompt may look like this:
+ # Whenever the current directory is ~/work or a subdirectory of ~/work, it gets styled with one
+ # of the following classes depending on its writability and existence: WORK, WORK_NOT_WRITABLE or
+ # WORK_NON_EXISTENT.
#
- # (โฏยฐโกยฐ๏ผโฏ๏ธต โปโโป ~/work/projects/important/urgent
- #
- # Or like this:
- #
- # โ ~/best/powerlevel10k
- #
- # You can also set different colors for directories of different classes. Remember to override
- # FOREGROUND, SHORTENED_FOREGROUND and ANCHOR_FOREGROUND for every directory class that you wish
- # to have its own color.
+ # Simply assigning classes to directories doesn't have any visible effects. It merely gives you an
+ # option to define custom colors and icons for different directory classes.
#
+ # # Styling for WORK.
+ # typeset -g POWERLEVEL9K_DIR_WORK_VISUAL_IDENTIFIER_EXPANSION='โญ'
# typeset -g POWERLEVEL9K_DIR_WORK_BACKGROUND=4
# typeset -g POWERLEVEL9K_DIR_WORK_FOREGROUND=254
# typeset -g POWERLEVEL9K_DIR_WORK_SHORTENED_FOREGROUND=250
# typeset -g POWERLEVEL9K_DIR_WORK_ANCHOR_FOREGROUND=255
#
+ # # Styling for WORK_NOT_WRITABLE.
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_BACKGROUND=4
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND=254
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_SHORTENED_FOREGROUND=250
+ # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_ANCHOR_FOREGROUND=255
+ #
+ # # Styling for WORK_NON_EXISTENT.
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_BACKGROUND=4
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_FOREGROUND=254
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_SHORTENED_FOREGROUND=250
+ # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_ANCHOR_FOREGROUND=255
+ #
+ # If a styling parameter isn't explicitly defined for some class, it falls back to the classless
+ # parameter. For example, if POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND is not set, it falls
+ # back to POWERLEVEL9K_DIR_FOREGROUND.
+ #
# typeset -g POWERLEVEL9K_DIR_CLASSES=()
- #####################################[ vcs: git status ]######################################
- # Version control system colors.
- # typeset -g POWERLEVEL9K_VCS_CLEAN_BACKGROUND=2
- # typeset -g POWERLEVEL9K_VCS_MODIFIED_BACKGROUND=3
- # typeset -g POWERLEVEL9K_VCS_UNTRACKED_BACKGROUND=2
- # typeset -g POWERLEVEL9K_VCS_CONFLICTED_BACKGROUND=3
- # typeset -g POWERLEVEL9K_VCS_LOADING_BACKGROUND=8
+ # Custom prefix.
+ # typeset -g POWERLEVEL9K_DIR_PREFIX='in '
- # Branch icon. Set this parameter to '\uF126 ' for the popular Powerline branch icon.
+ #####################################[ vcs: git status ]######################################
+ # Version control background colors.
+ typeset -g POWERLEVEL9K_VCS_CLEAN_BACKGROUND=2
+ typeset -g POWERLEVEL9K_VCS_MODIFIED_BACKGROUND=3
+ typeset -g POWERLEVEL9K_VCS_UNTRACKED_BACKGROUND=2
+ typeset -g POWERLEVEL9K_VCS_CONFLICTED_BACKGROUND=3
+ typeset -g POWERLEVEL9K_VCS_LOADING_BACKGROUND=8
+
+ # Branch icon. Set this parameter to '\UE0A0 ' for the popular Powerline branch icon.
typeset -g POWERLEVEL9K_VCS_BRANCH_ICON=
# Untracked files icon. It's really a question mark, your font isn't broken.
@@ -329,7 +369,7 @@
# Formatter for Git status.
#
- # Example output: master โฃ42โก42 *42 merge ~42 +42 !42 ?42.
+ # Example output: master wip โฃ42โก42 *42 merge ~42 +42 !42 ?42.
#
# You can edit the function to customize how Git status looks.
#
@@ -353,34 +393,55 @@
local conflicted='%1F' # red foreground
local res
- local where # branch or tag
+
if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then
- res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}"
- where=${(V)VCS_STATUS_LOCAL_BRANCH}
- elif [[ -n $VCS_STATUS_TAG ]]; then
- res+="${meta}#"
- where=${(V)VCS_STATUS_TAG}
+ local branch=${(V)VCS_STATUS_LOCAL_BRANCH}
+ # If local branch name is at most 32 characters long, show it in full.
+ # Otherwise show the first 12 โฆ the last 12.
+ # Tip: To always show local branch name in full without truncation, delete the next line.
+ (( $#branch > 32 )) && branch[13,-13]="โฆ" # <-- this line
+ res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}${branch//\%/%%}"
fi
- # If local branch name or tag is at most 32 characters long, show it in full.
- # Otherwise show the first 12 โฆ the last 12.
- (( $#where > 32 )) && where[13,-13]="โฆ"
- res+="${clean}${where//\%/%%}" # escape %
+ if [[ -n $VCS_STATUS_TAG
+ # Show tag only if not on a branch.
+ # Tip: To always show tag, delete the next line.
+ && -z $VCS_STATUS_LOCAL_BRANCH # <-- this line
+ ]]; then
+ local tag=${(V)VCS_STATUS_TAG}
+ # If tag name is at most 32 characters long, show it in full.
+ # Otherwise show the first 12 โฆ the last 12.
+ # Tip: To always show tag name in full without truncation, delete the next line.
+ (( $#tag > 32 )) && tag[13,-13]="โฆ" # <-- this line
+ res+="${meta}#${clean}${tag//\%/%%}"
+ fi
- # Display the current Git commit if there is no branch or tag.
- # Tip: To always display the current Git commit, remove `[[ -z $where ]] &&` from the next line.
- [[ -z $where ]] && res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}"
+ # Display the current Git commit if there is no branch and no tag.
+ # Tip: To always display the current Git commit, delete the next line.
+ [[ -z $VCS_STATUS_LOCAL_BRANCH && -z $VCS_STATUS_TAG ]] && # <-- this line
+ res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}"
# Show tracking branch name if it differs from local branch.
if [[ -n ${VCS_STATUS_REMOTE_BRANCH:#$VCS_STATUS_LOCAL_BRANCH} ]]; then
- res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}" # escape %
+ res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}"
+ fi
+
+ # Display "wip" if the latest commit's summary contains "wip" or "WIP".
+ if [[ $VCS_STATUS_COMMIT_SUMMARY == (|*[^[:alnum:]])(wip|WIP)(|[^[:alnum:]]*) ]]; then
+ res+=" ${modified}wip"
+ fi
+
+ if (( VCS_STATUS_COMMITS_AHEAD || VCS_STATUS_COMMITS_BEHIND )); then
+ # โฃ42 if behind the remote.
+ (( VCS_STATUS_COMMITS_BEHIND )) && res+=" ${clean}โฃ${VCS_STATUS_COMMITS_BEHIND}"
+ # โก42 if ahead of the remote; no leading space if also behind the remote: โฃ42โก42.
+ (( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && res+=" "
+ (( VCS_STATUS_COMMITS_AHEAD )) && res+="${clean}โก${VCS_STATUS_COMMITS_AHEAD}"
+ elif [[ -n $VCS_STATUS_REMOTE_BRANCH ]]; then
+ # Tip: Uncomment the next line to display '=' if up to date with the remote.
+ # res+=" ${clean}="
fi
- # โฃ42 if behind the remote.
- (( VCS_STATUS_COMMITS_BEHIND )) && res+=" ${clean}โฃ${VCS_STATUS_COMMITS_BEHIND}"
- # โก42 if ahead of the remote; no leading space if also behind the remote: โฃ42โก42.
- (( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && res+=" "
- (( VCS_STATUS_COMMITS_AHEAD )) && res+="${clean}โก${VCS_STATUS_COMMITS_AHEAD}"
# โ 42 if behind the push remote.
(( VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" ${clean}โ ${VCS_STATUS_PUSH_COMMITS_BEHIND}"
(( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" "
@@ -422,7 +483,7 @@
# Don't show Git status in prompt for repositories whose workdir matches this pattern.
# For example, if set to '~', the Git repository at $HOME/.git will be ignored.
- # Multiple patterns can be combined with '|': '~|~/some/dir'.
+ # Multiple patterns can be combined with '|': '~(|/foo)|/bar/baz/*'.
typeset -g POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN='~'
# Disable the default Git status formatting.
@@ -439,7 +500,7 @@
# Show status of repositories of these types. You can add svn and/or hg if you are
# using them. If you do, your prompt may become slow even when your current directory
- # isn't in an svn or hg reposotiry.
+ # isn't in an svn or hg repository.
typeset -g POWERLEVEL9K_VCS_BACKENDS=(git)
##########################[ status: exit code of the last command ]###########################
@@ -451,43 +512,43 @@
# it will signify success by turning green.
typeset -g POWERLEVEL9K_STATUS_OK=true
typeset -g POWERLEVEL9K_STATUS_OK_VISUAL_IDENTIFIER_EXPANSION='โ'
- # typeset -g POWERLEVEL9K_STATUS_OK_FOREGROUND=2
- # typeset -g POWERLEVEL9K_STATUS_OK_BACKGROUND=0
+ typeset -g POWERLEVEL9K_STATUS_OK_FOREGROUND=2
+ typeset -g POWERLEVEL9K_STATUS_OK_BACKGROUND=0
# Status when some part of a pipe command fails but the overall exit status is zero. It may look
# like this: 1|0.
typeset -g POWERLEVEL9K_STATUS_OK_PIPE=true
typeset -g POWERLEVEL9K_STATUS_OK_PIPE_VISUAL_IDENTIFIER_EXPANSION='โ'
- # typeset -g POWERLEVEL9K_STATUS_OK_PIPE_FOREGROUND=2
- # typeset -g POWERLEVEL9K_STATUS_OK_PIPE_BACKGROUND=0
+ typeset -g POWERLEVEL9K_STATUS_OK_PIPE_FOREGROUND=2
+ typeset -g POWERLEVEL9K_STATUS_OK_PIPE_BACKGROUND=0
# Status when it's just an error code (e.g., '1'). No need to show it if prompt_char is enabled as
# it will signify error by turning red.
typeset -g POWERLEVEL9K_STATUS_ERROR=true
typeset -g POWERLEVEL9K_STATUS_ERROR_VISUAL_IDENTIFIER_EXPANSION='โ'
- # typeset -g POWERLEVEL9K_STATUS_ERROR_FOREGROUND=3
- # typeset -g POWERLEVEL9K_STATUS_ERROR_BACKGROUND=1
+ typeset -g POWERLEVEL9K_STATUS_ERROR_FOREGROUND=3
+ typeset -g POWERLEVEL9K_STATUS_ERROR_BACKGROUND=1
# Status when the last command was terminated by a signal.
typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL=true
# Use terse signal names: "INT" instead of "SIGINT(2)".
typeset -g POWERLEVEL9K_STATUS_VERBOSE_SIGNAME=false
typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_VISUAL_IDENTIFIER_EXPANSION='โ'
- # typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_FOREGROUND=3
- # typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_BACKGROUND=1
+ typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_FOREGROUND=3
+ typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_BACKGROUND=1
# Status when some part of a pipe command fails and the overall exit status is also non-zero.
# It may look like this: 1|0.
typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE=true
typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_VISUAL_IDENTIFIER_EXPANSION='โ'
- # typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_FOREGROUND=3
- # typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_BACKGROUND=1
+ typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_FOREGROUND=3
+ typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_BACKGROUND=1
###################[ command_execution_time: duration of the last command ]###################
# Execution time color.
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FOREGROUND=0
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_BACKGROUND=3
- # Show duration of the last command if takes longer than this many seconds.
+ # Show duration of the last command if takes at least this many seconds.
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=3
# Show this many fractional digits. Zero means round to seconds.
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0
@@ -500,8 +561,8 @@
#######################[ background_jobs: presence of background jobs ]#######################
# Background jobs color.
- # typeset -g POWERLEVEL9K_BACKGROUND_JOBS_FOREGROUND=6
- # typeset -g POWERLEVEL9K_BACKGROUND_JOBS_BACKGROUND=0
+ typeset -g POWERLEVEL9K_BACKGROUND_JOBS_FOREGROUND=6
+ typeset -g POWERLEVEL9K_BACKGROUND_JOBS_BACKGROUND=0
# Don't show the number of background jobs.
typeset -g POWERLEVEL9K_BACKGROUND_JOBS_VERBOSE=false
# Custom icon.
@@ -509,13 +570,15 @@
#######################[ direnv: direnv status (https://direnv.net/) ]########################
# Direnv color.
- # typeset -g POWERLEVEL9K_DIRENV_FOREGROUND=3
- # typeset -g POWERLEVEL9K_DIRENV_BACKGROUND=0
+ typeset -g POWERLEVEL9K_DIRENV_FOREGROUND=3
+ typeset -g POWERLEVEL9K_DIRENV_BACKGROUND=0
# Custom icon.
# typeset -g POWERLEVEL9K_DIRENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
###############[ asdf: asdf version manager (https://github.com/asdf-vm/asdf) ]###############
# Default asdf color. Only used to display tools for which there is no color override (see below).
+ # Tip: Override these parameters for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_FOREGROUND and
+ # POWERLEVEL9K_ASDF_${TOOL}_BACKGROUND.
typeset -g POWERLEVEL9K_ASDF_FOREGROUND=0
typeset -g POWERLEVEL9K_ASDF_BACKGROUND=7
@@ -562,7 +625,7 @@
typeset -g POWERLEVEL9K_ASDF_SHOW_SYSTEM=true
# If set to non-empty value, hide tools unless there is a file matching the specified file pattern
- # in the current directory, or its parent diretory, or its grandparent directory, and so on.
+ # in the current directory, or its parent directory, or its grandparent directory, and so on.
#
# Note: If this parameter is set to empty value, it won't hide tools.
# Note: SHOW_ON_UPGLOB isn't specific to asdf. It works with all prompt segments.
@@ -664,10 +727,16 @@
# typeset -g POWERLEVEL9K_ASDF_HASKELL_VISUAL_IDENTIFIER_EXPANSION='โญ'
# typeset -g POWERLEVEL9K_ASDF_HASKELL_SHOW_ON_UPGLOB='*.foo|*.bar'
+ # Julia version from asdf.
+ typeset -g POWERLEVEL9K_ASDF_JULIA_FOREGROUND=0
+ typeset -g POWERLEVEL9K_ASDF_JULIA_BACKGROUND=2
+ # typeset -g POWERLEVEL9K_ASDF_JULIA_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_ASDF_JULIA_SHOW_ON_UPGLOB='*.foo|*.bar'
+
##########[ nordvpn: nordvpn connection status, linux only (https://nordvpn.com/) ]###########
# NordVPN connection indicator color.
- # typeset -g POWERLEVEL9K_NORDVPN_FOREGROUND=7
- # typeset -g POWERLEVEL9K_NORDVPN_BACKGROUND=4
+ typeset -g POWERLEVEL9K_NORDVPN_FOREGROUND=7
+ typeset -g POWERLEVEL9K_NORDVPN_BACKGROUND=4
# Hide NordVPN connection indicator when not connected.
typeset -g POWERLEVEL9K_NORDVPN_{DISCONNECTED,CONNECTING,DISCONNECTING}_CONTENT_EXPANSION=
typeset -g POWERLEVEL9K_NORDVPN_{DISCONNECTED,CONNECTING,DISCONNECTING}_VISUAL_IDENTIFIER_EXPANSION=
@@ -676,36 +745,60 @@
#################[ ranger: ranger shell (https://github.com/ranger/ranger) ]##################
# Ranger shell color.
- # typeset -g POWERLEVEL9K_RANGER_FOREGROUND=3
- # typeset -g POWERLEVEL9K_RANGER_BACKGROUND=0
+ typeset -g POWERLEVEL9K_RANGER_FOREGROUND=3
+ typeset -g POWERLEVEL9K_RANGER_BACKGROUND=0
# Custom icon.
# typeset -g POWERLEVEL9K_RANGER_VISUAL_IDENTIFIER_EXPANSION='โญ'
-
+
+ ####################[ yazi: yazi shell (https://github.com/sxyazi/yazi) ]#####################
+ # Yazi shell color.
+ typeset -g POWERLEVEL9K_YAZI_FOREGROUND=3
+ typeset -g POWERLEVEL9K_YAZI_BACKGROUND=0
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_YAZI_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
######################[ nnn: nnn shell (https://github.com/jarun/nnn) ]#######################
# Nnn shell color.
- # typeset -g POWERLEVEL9K_NNN_FOREGROUND=0
- # typeset -g POWERLEVEL9K_NNN_BACKGROUND=6
+ typeset -g POWERLEVEL9K_NNN_FOREGROUND=0
+ typeset -g POWERLEVEL9K_NNN_BACKGROUND=6
# Custom icon.
# typeset -g POWERLEVEL9K_NNN_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ ######################[ lf: lf shell (https://github.com/gokcehan/lf) ]#######################
+ # lf shell color.
+ typeset -g POWERLEVEL9K_LF_FOREGROUND=0
+ typeset -g POWERLEVEL9K_LF_BACKGROUND=6
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_LF_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ ##################[ xplr: xplr shell (https://github.com/sayanarijit/xplr) ]##################
+ # xplr shell color.
+ typeset -g POWERLEVEL9K_XPLR_FOREGROUND=0
+ typeset -g POWERLEVEL9K_XPLR_BACKGROUND=6
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_XPLR_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
###########################[ vim_shell: vim shell indicator (:sh) ]###########################
# Vim shell indicator color.
- # typeset -g POWERLEVEL9K_VIM_SHELL_FOREGROUND=0
- # typeset -g POWERLEVEL9K_VIM_SHELL_BACKGROUND=2
+ typeset -g POWERLEVEL9K_VIM_SHELL_FOREGROUND=0
+ typeset -g POWERLEVEL9K_VIM_SHELL_BACKGROUND=2
# Custom icon.
# typeset -g POWERLEVEL9K_VIM_SHELL_VISUAL_IDENTIFIER_EXPANSION='โญ'
######[ midnight_commander: midnight commander shell (https://midnight-commander.org/) ]######
# Midnight Commander shell color.
- # typeset -g POWERLEVEL9K_MIDNIGHT_COMMANDER_FOREGROUND=3
- # typeset -g POWERLEVEL9K_MIDNIGHT_COMMANDER_BACKGROUND=0
+ typeset -g POWERLEVEL9K_MIDNIGHT_COMMANDER_FOREGROUND=3
+ typeset -g POWERLEVEL9K_MIDNIGHT_COMMANDER_BACKGROUND=0
# Custom icon.
# typeset -g POWERLEVEL9K_MIDNIGHT_COMMANDER_VISUAL_IDENTIFIER_EXPANSION='โญ'
#[ nix_shell: nix shell (https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html) ]##
# Nix shell color.
- # typeset -g POWERLEVEL9K_NIX_SHELL_FOREGROUND=0
- # typeset -g POWERLEVEL9K_NIX_SHELL_BACKGROUND=4
+ typeset -g POWERLEVEL9K_NIX_SHELL_FOREGROUND=0
+ typeset -g POWERLEVEL9K_NIX_SHELL_BACKGROUND=4
+
+ # Display the icon of nix_shell if PATH contains a subdirectory of /nix/store.
+ # typeset -g POWERLEVEL9K_NIX_SHELL_INFER_FROM_PATH=false
# Tip: If you want to see just the icon without "pure" and "impure", uncomment the next line.
# typeset -g POWERLEVEL9K_NIX_SHELL_CONTENT_EXPANSION=
@@ -713,14 +806,21 @@
# Custom icon.
# typeset -g POWERLEVEL9K_NIX_SHELL_VISUAL_IDENTIFIER_EXPANSION='โญ'
- ##################################[ disk_usgae: disk usage ]##################################
+ ##################[ chezmoi_shell: chezmoi shell (https://www.chezmoi.io/) ]##################
+ # chezmoi shell color.
+ typeset -g POWERLEVEL9K_CHEZMOI_SHELL_FOREGROUND=0
+ typeset -g POWERLEVEL9K_CHEZMOI_SHELL_BACKGROUND=4
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_CHEZMOI_SHELL_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ ##################################[ disk_usage: disk usage ]##################################
# Colors for different levels of disk usage.
- # typeset -g POWERLEVEL9K_DISK_USAGE_NORMAL_FOREGROUND=3
- # typeset -g POWERLEVEL9K_DISK_USAGE_NORMAL_BACKGROUND=0
- # typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_FOREGROUND=0
- # typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_BACKGROUND=3
- # typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_FOREGROUND=7
- # typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_BACKGROUND=1
+ typeset -g POWERLEVEL9K_DISK_USAGE_NORMAL_FOREGROUND=3
+ typeset -g POWERLEVEL9K_DISK_USAGE_NORMAL_BACKGROUND=0
+ typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_FOREGROUND=0
+ typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_BACKGROUND=3
+ typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_FOREGROUND=7
+ typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_BACKGROUND=1
# Thresholds for different levels of disk usage (percentage points).
typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_LEVEL=90
typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_LEVEL=95
@@ -744,18 +844,20 @@
# Text and color for insert vi mode.
typeset -g POWERLEVEL9K_VI_INSERT_MODE_STRING=
typeset -g POWERLEVEL9K_VI_MODE_INSERT_FOREGROUND=8
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_VI_MODE_VISUAL_IDENTIFIER_EXPANSION='โญ'
######################################[ ram: free RAM ]#######################################
# RAM color.
- # typeset -g POWERLEVEL9K_RAM_FOREGROUND=0
- # typeset -g POWERLEVEL9K_RAM_BACKGROUND=3
+ typeset -g POWERLEVEL9K_RAM_FOREGROUND=0
+ typeset -g POWERLEVEL9K_RAM_BACKGROUND=3
# Custom icon.
# typeset -g POWERLEVEL9K_RAM_VISUAL_IDENTIFIER_EXPANSION='โญ'
#####################################[ swap: used swap ]######################################
# Swap color.
- # typeset -g POWERLEVEL9K_SWAP_FOREGROUND=0
- # typeset -g POWERLEVEL9K_SWAP_BACKGROUND=3
+ typeset -g POWERLEVEL9K_SWAP_FOREGROUND=0
+ typeset -g POWERLEVEL9K_SWAP_BACKGROUND=3
# Custom icon.
# typeset -g POWERLEVEL9K_SWAP_VISUAL_IDENTIFIER_EXPANSION='โญ'
@@ -763,21 +865,21 @@
# Show average CPU load over this many last minutes. Valid values are 1, 5 and 15.
typeset -g POWERLEVEL9K_LOAD_WHICH=5
# Load color when load is under 50%.
- # typeset -g POWERLEVEL9K_LOAD_NORMAL_FOREGROUND=0
- # typeset -g POWERLEVEL9K_LOAD_NORMAL_BACKGROUND=2
+ typeset -g POWERLEVEL9K_LOAD_NORMAL_FOREGROUND=0
+ typeset -g POWERLEVEL9K_LOAD_NORMAL_BACKGROUND=2
# Load color when load is between 50% and 70%.
- # typeset -g POWERLEVEL9K_LOAD_WARNING_FOREGROUND=0
- # typeset -g POWERLEVEL9K_LOAD_WARNING_BACKGROUND=3
+ typeset -g POWERLEVEL9K_LOAD_WARNING_FOREGROUND=0
+ typeset -g POWERLEVEL9K_LOAD_WARNING_BACKGROUND=3
# Load color when load is over 70%.
- # typeset -g POWERLEVEL9K_LOAD_CRITICAL_FOREGROUND=0
- # typeset -g POWERLEVEL9K_LOAD_CRITICAL_BACKGROUND=1
+ typeset -g POWERLEVEL9K_LOAD_CRITICAL_FOREGROUND=0
+ typeset -g POWERLEVEL9K_LOAD_CRITICAL_BACKGROUND=1
# Custom icon.
# typeset -g POWERLEVEL9K_LOAD_VISUAL_IDENTIFIER_EXPANSION='โญ'
################[ todo: todo items (https://github.com/todotxt/todo.txt-cli) ]################
# Todo color.
- # typeset -g POWERLEVEL9K_TODO_FOREGROUND=0
- # typeset -g POWERLEVEL9K_TODO_BACKGROUND=8
+ typeset -g POWERLEVEL9K_TODO_FOREGROUND=0
+ typeset -g POWERLEVEL9K_TODO_BACKGROUND=8
# Hide todo when the total number of tasks is zero.
typeset -g POWERLEVEL9K_TODO_HIDE_ZERO_TOTAL=true
# Hide todo when the number of tasks after filtering is zero.
@@ -801,8 +903,8 @@
###########[ timewarrior: timewarrior tracking status (https://timewarrior.net/) ]############
# Timewarrior color.
- # typeset -g POWERLEVEL9K_TIMEWARRIOR_FOREGROUND=255
- # typeset -g POWERLEVEL9K_TIMEWARRIOR_BACKGROUND=8
+ typeset -g POWERLEVEL9K_TIMEWARRIOR_FOREGROUND=255
+ typeset -g POWERLEVEL9K_TIMEWARRIOR_BACKGROUND=8
# If the tracked task is longer than 24 characters, truncate and append "โฆ".
# Tip: To always display tasks without truncation, delete the following parameter.
@@ -815,8 +917,8 @@
##############[ taskwarrior: taskwarrior task count (https://taskwarrior.org/) ]##############
# Taskwarrior color.
- # typeset -g POWERLEVEL9K_TASKWARRIOR_FOREGROUND=0
- # typeset -g POWERLEVEL9K_TASKWARRIOR_BACKGROUND=6
+ typeset -g POWERLEVEL9K_TASKWARRIOR_FOREGROUND=0
+ typeset -g POWERLEVEL9K_TASKWARRIOR_BACKGROUND=6
# Taskwarrior segment format. The following parameters are available within the expansion.
#
@@ -834,6 +936,33 @@
# Custom icon.
# typeset -g POWERLEVEL9K_TASKWARRIOR_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ ######[ per_directory_history: Oh My Zsh per-directory-history local/global indicator ]#######
+ # Color when using local/global history.
+ typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_FOREGROUND=0
+ typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_BACKGROUND=5
+ typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_FOREGROUND=0
+ typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_BACKGROUND=3
+
+ # Tip: Uncomment the next two lines to hide "local"/"global" text and leave just the icon.
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_CONTENT_EXPANSION=''
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_CONTENT_EXPANSION=''
+
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ ################################[ cpu_arch: CPU architecture ]################################
+ # CPU architecture color.
+ typeset -g POWERLEVEL9K_CPU_ARCH_FOREGROUND=0
+ typeset -g POWERLEVEL9K_CPU_ARCH_BACKGROUND=3
+
+ # Hide the segment when on a specific CPU architecture.
+ # typeset -g POWERLEVEL9K_CPU_ARCH_X86_64_CONTENT_EXPANSION=
+ # typeset -g POWERLEVEL9K_CPU_ARCH_X86_64_VISUAL_IDENTIFIER_EXPANSION=
+
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_CPU_ARCH_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
##################################[ context: user@hostname ]##################################
# Context color when running with privileges.
typeset -g POWERLEVEL9K_CONTEXT_ROOT_FOREGROUND=1
@@ -863,10 +992,13 @@
###[ virtualenv: python virtual environment (https://docs.python.org/3/library/venv.html) ]###
# Python virtual environment color.
- # typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=0
- # typeset -g POWERLEVEL9K_VIRTUALENV_BACKGROUND=4
+ typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=0
+ typeset -g POWERLEVEL9K_VIRTUALENV_BACKGROUND=4
# Don't show Python version next to the virtual environment name.
typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false
+ # If set to "false", won't show virtualenv if pyenv is already shown.
+ # If set to "if-different", won't show virtualenv if it's the same as pyenv.
+ typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_WITH_PYENV=false
# Separate environment name from Python version only with a space.
typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER=
# Custom icon.
@@ -874,19 +1006,42 @@
#####################[ anaconda: conda environment (https://conda.io/) ]######################
# Anaconda environment color.
- # typeset -g POWERLEVEL9K_ANACONDA_FOREGROUND=0
- # typeset -g POWERLEVEL9K_ANACONDA_BACKGROUND=4
- # Don't show Python version next to the anaconda environment name.
- typeset -g POWERLEVEL9K_ANACONDA_SHOW_PYTHON_VERSION=false
- # Separate environment name from Python version only with a space.
- typeset -g POWERLEVEL9K_ANACONDA_{LEFT,RIGHT}_DELIMITER=
+ typeset -g POWERLEVEL9K_ANACONDA_FOREGROUND=0
+ typeset -g POWERLEVEL9K_ANACONDA_BACKGROUND=4
+
+ # Anaconda segment format. The following parameters are available within the expansion.
+ #
+ # - CONDA_PREFIX Absolute path to the active Anaconda/Miniconda environment.
+ # - CONDA_DEFAULT_ENV Name of the active Anaconda/Miniconda environment.
+ # - CONDA_PROMPT_MODIFIER Configurable prompt modifier (see below).
+ # - P9K_ANACONDA_PYTHON_VERSION Current python version (python --version).
+ #
+ # CONDA_PROMPT_MODIFIER can be configured with the following command:
+ #
+ # conda config --set env_prompt '({default_env}) '
+ #
+ # The last argument is a Python format string that can use the following variables:
+ #
+ # - prefix The same as CONDA_PREFIX.
+ # - default_env The same as CONDA_DEFAULT_ENV.
+ # - name The last segment of CONDA_PREFIX.
+ # - stacked_env Comma-separated list of names in the environment stack. The first element is
+ # always the same as default_env.
+ #
+ # Note: '({default_env}) ' is the default value of env_prompt.
+ #
+ # The default value of POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION expands to $CONDA_PROMPT_MODIFIER
+ # without the surrounding parentheses, or to the last path component of CONDA_PREFIX if the former
+ # is empty.
+ typeset -g POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION='${${${${CONDA_PROMPT_MODIFIER#\(}% }%\)}:-${CONDA_PREFIX:t}}'
+
# Custom icon.
# typeset -g POWERLEVEL9K_ANACONDA_VISUAL_IDENTIFIER_EXPANSION='โญ'
################[ pyenv: python environment (https://github.com/pyenv/pyenv) ]################
# Pyenv color.
- # typeset -g POWERLEVEL9K_PYENV_FOREGROUND=0
- # typeset -g POWERLEVEL9K_PYENV_BACKGROUND=4
+ typeset -g POWERLEVEL9K_PYENV_FOREGROUND=0
+ typeset -g POWERLEVEL9K_PYENV_BACKGROUND=4
# Hide python version if it doesn't come from one of these sources.
typeset -g POWERLEVEL9K_PYENV_SOURCES=(shell local global)
# If set to false, hide python version if it's the same as global:
@@ -894,13 +1049,26 @@
typeset -g POWERLEVEL9K_PYENV_PROMPT_ALWAYS_SHOW=false
# If set to false, hide python version if it's equal to "system".
typeset -g POWERLEVEL9K_PYENV_SHOW_SYSTEM=true
+
+ # Pyenv segment format. The following parameters are available within the expansion.
+ #
+ # - P9K_CONTENT Current pyenv environment (pyenv version-name).
+ # - P9K_PYENV_PYTHON_VERSION Current python version (python --version).
+ #
+ # The default format has the following logic:
+ #
+ # 1. Display just "$P9K_CONTENT" if it's equal to "$P9K_PYENV_PYTHON_VERSION" or
+ # starts with "$P9K_PYENV_PYTHON_VERSION/".
+ # 2. Otherwise display "$P9K_CONTENT $P9K_PYENV_PYTHON_VERSION".
+ typeset -g POWERLEVEL9K_PYENV_CONTENT_EXPANSION='${P9K_CONTENT}${${P9K_CONTENT:#$P9K_PYENV_PYTHON_VERSION(|/*)}:+ $P9K_PYENV_PYTHON_VERSION}'
+
# Custom icon.
# typeset -g POWERLEVEL9K_PYENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
################[ goenv: go environment (https://github.com/syndbg/goenv) ]################
# Goenv color.
- # typeset -g POWERLEVEL9K_GOENV_FOREGROUND=0
- # typeset -g POWERLEVEL9K_GOENV_BACKGROUND=4
+ typeset -g POWERLEVEL9K_GOENV_FOREGROUND=0
+ typeset -g POWERLEVEL9K_GOENV_BACKGROUND=4
# Hide go version if it doesn't come from one of these sources.
typeset -g POWERLEVEL9K_GOENV_SOURCES=(shell local global)
# If set to false, hide go version if it's the same as global:
@@ -913,8 +1081,8 @@
##########[ nodenv: node.js version from nodenv (https://github.com/nodenv/nodenv) ]##########
# Nodenv color.
- # typeset -g POWERLEVEL9K_NODENV_FOREGROUND=2
- # typeset -g POWERLEVEL9K_NODENV_BACKGROUND=0
+ typeset -g POWERLEVEL9K_NODENV_FOREGROUND=2
+ typeset -g POWERLEVEL9K_NODENV_BACKGROUND=0
# Hide node version if it doesn't come from one of these sources.
typeset -g POWERLEVEL9K_NODENV_SOURCES=(shell local global)
# If set to false, hide node version if it's the same as global:
@@ -927,15 +1095,20 @@
##############[ nvm: node.js version from nvm (https://github.com/nvm-sh/nvm) ]###############
# Nvm color.
- # typeset -g POWERLEVEL9K_NVM_FOREGROUND=0
- # typeset -g POWERLEVEL9K_NVM_BACKGROUND=5
+ typeset -g POWERLEVEL9K_NVM_FOREGROUND=0
+ typeset -g POWERLEVEL9K_NVM_BACKGROUND=5
+ # If set to false, hide node version if it's the same as default:
+ # $(nvm version current) == $(nvm version default).
+ typeset -g POWERLEVEL9K_NVM_PROMPT_ALWAYS_SHOW=false
+ # If set to false, hide node version if it's equal to "system".
+ typeset -g POWERLEVEL9K_NVM_SHOW_SYSTEM=true
# Custom icon.
# typeset -g POWERLEVEL9K_NVM_VISUAL_IDENTIFIER_EXPANSION='โญ'
############[ nodeenv: node.js environment (https://github.com/ekalinin/nodeenv) ]############
# Nodeenv color.
- # typeset -g POWERLEVEL9K_NODEENV_FOREGROUND=2
- # typeset -g POWERLEVEL9K_NODEENV_BACKGROUND=0
+ typeset -g POWERLEVEL9K_NODEENV_FOREGROUND=2
+ typeset -g POWERLEVEL9K_NODEENV_BACKGROUND=0
# Don't show Node version next to the environment name.
typeset -g POWERLEVEL9K_NODEENV_SHOW_NODE_VERSION=false
# Separate environment name from Node version only with a space.
@@ -945,8 +1118,8 @@
##############################[ node_version: node.js version ]###############################
# Node version color.
- # typeset -g POWERLEVEL9K_NODE_VERSION_FOREGROUND=7
- # typeset -g POWERLEVEL9K_NODE_VERSION_BACKGROUND=2
+ typeset -g POWERLEVEL9K_NODE_VERSION_FOREGROUND=7
+ typeset -g POWERLEVEL9K_NODE_VERSION_BACKGROUND=2
# Show node version only when in a directory tree containing package.json.
typeset -g POWERLEVEL9K_NODE_VERSION_PROJECT_ONLY=true
# Custom icon.
@@ -954,8 +1127,8 @@
#######################[ go_version: go version (https://golang.org) ]########################
# Go version color.
- # typeset -g POWERLEVEL9K_GO_VERSION_FOREGROUND=255
- # typeset -g POWERLEVEL9K_GO_VERSION_BACKGROUND=2
+ typeset -g POWERLEVEL9K_GO_VERSION_FOREGROUND=255
+ typeset -g POWERLEVEL9K_GO_VERSION_BACKGROUND=2
# Show go version only when in a go project subdirectory.
typeset -g POWERLEVEL9K_GO_VERSION_PROJECT_ONLY=true
# Custom icon.
@@ -963,8 +1136,8 @@
#################[ rust_version: rustc version (https://www.rust-lang.org) ]##################
# Rust version color.
- # typeset -g POWERLEVEL9K_RUST_VERSION_FOREGROUND=0
- # typeset -g POWERLEVEL9K_RUST_VERSION_BACKGROUND=208
+ typeset -g POWERLEVEL9K_RUST_VERSION_FOREGROUND=0
+ typeset -g POWERLEVEL9K_RUST_VERSION_BACKGROUND=208
# Show rust version only when in a rust project subdirectory.
typeset -g POWERLEVEL9K_RUST_VERSION_PROJECT_ONLY=true
# Custom icon.
@@ -972,8 +1145,8 @@
###############[ dotnet_version: .NET version (https://dotnet.microsoft.com) ]################
# .NET version color.
- # typeset -g POWERLEVEL9K_DOTNET_VERSION_FOREGROUND=7
- # typeset -g POWERLEVEL9K_DOTNET_VERSION_BACKGROUND=5
+ typeset -g POWERLEVEL9K_DOTNET_VERSION_FOREGROUND=7
+ typeset -g POWERLEVEL9K_DOTNET_VERSION_BACKGROUND=5
# Show .NET version only when in a .NET project subdirectory.
typeset -g POWERLEVEL9K_DOTNET_VERSION_PROJECT_ONLY=true
# Custom icon.
@@ -997,8 +1170,8 @@
#############[ rbenv: ruby version from rbenv (https://github.com/rbenv/rbenv) ]##############
# Rbenv color.
- # typeset -g POWERLEVEL9K_RBENV_FOREGROUND=0
- # typeset -g POWERLEVEL9K_RBENV_BACKGROUND=1
+ typeset -g POWERLEVEL9K_RBENV_FOREGROUND=0
+ typeset -g POWERLEVEL9K_RBENV_BACKGROUND=1
# Hide ruby version if it doesn't come from one of these sources.
typeset -g POWERLEVEL9K_RBENV_SOURCES=(shell local global)
# If set to false, hide ruby version if it's the same as global:
@@ -1022,8 +1195,8 @@
###[ package: name@version from package.json (https://docs.npmjs.com/files/package.json) ]####
# Package color.
- # typeset -g POWERLEVEL9K_PACKAGE_FOREGROUND=0
- # typeset -g POWERLEVEL9K_PACKAGE_BACKGROUND=6
+ typeset -g POWERLEVEL9K_PACKAGE_FOREGROUND=0
+ typeset -g POWERLEVEL9K_PACKAGE_BACKGROUND=6
# Package format. The following parameters are available within the expansion.
#
@@ -1037,8 +1210,8 @@
#######################[ rvm: ruby version from rvm (https://rvm.io) ]########################
# Rvm color.
- # typeset -g POWERLEVEL9K_RVM_FOREGROUND=0
- # typeset -g POWERLEVEL9K_RVM_BACKGROUND=240
+ typeset -g POWERLEVEL9K_RVM_FOREGROUND=0
+ typeset -g POWERLEVEL9K_RVM_BACKGROUND=240
# Don't show @gemset at the end.
typeset -g POWERLEVEL9K_RVM_SHOW_GEMSET=false
# Don't show ruby- at the front.
@@ -1048,15 +1221,15 @@
###########[ fvm: flutter version management (https://github.com/leoafarias/fvm) ]############
# Fvm color.
- # typeset -g POWERLEVEL9K_FVM_FOREGROUND=0
- # typeset -g POWERLEVEL9K_FVM_BACKGROUND=4
+ typeset -g POWERLEVEL9K_FVM_FOREGROUND=0
+ typeset -g POWERLEVEL9K_FVM_BACKGROUND=4
# Custom icon.
# typeset -g POWERLEVEL9K_FVM_VISUAL_IDENTIFIER_EXPANSION='โญ'
##########[ luaenv: lua version from luaenv (https://github.com/cehoffman/luaenv) ]###########
# Lua color.
- # typeset -g POWERLEVEL9K_LUAENV_FOREGROUND=0
- # typeset -g POWERLEVEL9K_LUAENV_BACKGROUND=4
+ typeset -g POWERLEVEL9K_LUAENV_FOREGROUND=0
+ typeset -g POWERLEVEL9K_LUAENV_BACKGROUND=4
# Hide lua version if it doesn't come from one of these sources.
typeset -g POWERLEVEL9K_LUAENV_SOURCES=(shell local global)
# If set to false, hide lua version if it's the same as global:
@@ -1069,8 +1242,8 @@
###############[ jenv: java version from jenv (https://github.com/jenv/jenv) ]################
# Java color.
- # typeset -g POWERLEVEL9K_JENV_FOREGROUND=1
- # typeset -g POWERLEVEL9K_JENV_BACKGROUND=7
+ typeset -g POWERLEVEL9K_JENV_FOREGROUND=1
+ typeset -g POWERLEVEL9K_JENV_BACKGROUND=7
# Hide java version if it doesn't come from one of these sources.
typeset -g POWERLEVEL9K_JENV_SOURCES=(shell local global)
# If set to false, hide java version if it's the same as global:
@@ -1083,8 +1256,8 @@
###########[ plenv: perl version from plenv (https://github.com/tokuhirom/plenv) ]############
# Perl color.
- # typeset -g POWERLEVEL9K_PLENV_FOREGROUND=0
- # typeset -g POWERLEVEL9K_PLENV_BACKGROUND=4
+ typeset -g POWERLEVEL9K_PLENV_FOREGROUND=0
+ typeset -g POWERLEVEL9K_PLENV_BACKGROUND=4
# Hide perl version if it doesn't come from one of these sources.
typeset -g POWERLEVEL9K_PLENV_SOURCES=(shell local global)
# If set to false, hide perl version if it's the same as global:
@@ -1095,10 +1268,20 @@
# Custom icon.
# typeset -g POWERLEVEL9K_PLENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ ###########[ perlbrew: perl version from perlbrew (https://github.com/gugod/App-perlbrew) ]############
+ # Perlbrew color.
+ typeset -g POWERLEVEL9K_PERLBREW_FOREGROUND=67
+ # Show perlbrew version only when in a perl project subdirectory.
+ typeset -g POWERLEVEL9K_PERLBREW_PROJECT_ONLY=true
+ # Don't show "perl-" at the front.
+ typeset -g POWERLEVEL9K_PERLBREW_SHOW_PREFIX=false
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_PERLBREW_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
############[ phpenv: php version from phpenv (https://github.com/phpenv/phpenv) ]############
# PHP color.
- # typeset -g POWERLEVEL9K_PHPENV_FOREGROUND=0
- # typeset -g POWERLEVEL9K_PHPENV_BACKGROUND=5
+ typeset -g POWERLEVEL9K_PHPENV_FOREGROUND=0
+ typeset -g POWERLEVEL9K_PHPENV_BACKGROUND=5
# Hide php version if it doesn't come from one of these sources.
typeset -g POWERLEVEL9K_PHPENV_SOURCES=(shell local global)
# If set to false, hide php version if it's the same as global:
@@ -1109,10 +1292,24 @@
# Custom icon.
# typeset -g POWERLEVEL9K_PHPENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ #######[ scalaenv: scala version from scalaenv (https://github.com/scalaenv/scalaenv) ]#######
+ # Scala color.
+ typeset -g POWERLEVEL9K_SCALAENV_FOREGROUND=0
+ typeset -g POWERLEVEL9K_SCALAENV_BACKGROUND=1
+ # Hide scala version if it doesn't come from one of these sources.
+ typeset -g POWERLEVEL9K_SCALAENV_SOURCES=(shell local global)
+ # If set to false, hide scala version if it's the same as global:
+ # $(scalaenv version-name) == $(scalaenv global).
+ typeset -g POWERLEVEL9K_SCALAENV_PROMPT_ALWAYS_SHOW=false
+ # If set to false, hide scala version if it's equal to "system".
+ typeset -g POWERLEVEL9K_SCALAENV_SHOW_SYSTEM=true
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_SCALAENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
##########[ haskell_stack: haskell version from stack (https://haskellstack.org/) ]###########
# Haskell color.
- # typeset -g POWERLEVEL9K_HASKELL_STACK_FOREGROUND=0
- # typeset -g POWERLEVEL9K_HASKELL_STACK_BACKGROUND=3
+ typeset -g POWERLEVEL9K_HASKELL_STACK_FOREGROUND=0
+ typeset -g POWERLEVEL9K_HASKELL_STACK_BACKGROUND=3
# Hide haskell version if it doesn't come from one of these sources.
#
@@ -1126,6 +1323,8 @@
# typeset -g POWERLEVEL9K_HASKELL_STACK_VISUAL_IDENTIFIER_EXPANSION='โญ'
################[ terraform: terraform workspace (https://www.terraform.io) ]#################
+ # Don't show terraform workspace if it's literally "default".
+ typeset -g POWERLEVEL9K_TERRAFORM_SHOW_DEFAULT=false
# POWERLEVEL9K_TERRAFORM_CLASSES is an array with even number of elements. The first element
# in each pair defines a pattern against which the current terraform workspace gets matched.
# More specifically, it's P9K_CONTENT prior to the application of context expansion (see below)
@@ -1139,7 +1338,7 @@
# typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
# '*prod*' PROD
# '*test*' TEST
- # '*' DEFAULT)
+ # '*' OTHER)
#
# If your current terraform workspace is "project_test", its class is TEST because "project_test"
# doesn't match the pattern '*prod*' but does match '*test*'.
@@ -1147,20 +1346,31 @@
# You can define different colors, icons and content expansions for different classes:
#
# typeset -g POWERLEVEL9K_TERRAFORM_TEST_FOREGROUND=2
+ # typeset -g POWERLEVEL9K_TERRAFORM_TEST_BACKGROUND=0
# typeset -g POWERLEVEL9K_TERRAFORM_TEST_VISUAL_IDENTIFIER_EXPANSION='โญ'
# typeset -g POWERLEVEL9K_TERRAFORM_TEST_CONTENT_EXPANSION='> ${P9K_CONTENT} <'
typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
# '*prod*' PROD # These values are examples that are unlikely
# '*test*' TEST # to match your needs. Customize them as needed.
- '*' DEFAULT)
- typeset -g POWERLEVEL9K_TERRAFORM_DEFAULT_FOREGROUND=4
- typeset -g POWERLEVEL9K_TERRAFORM_DEFAULT_BACKGROUND=0
- # typeset -g POWERLEVEL9K_TERRAFORM_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ '*' OTHER)
+ typeset -g POWERLEVEL9K_TERRAFORM_OTHER_FOREGROUND=4
+ typeset -g POWERLEVEL9K_TERRAFORM_OTHER_BACKGROUND=0
+ # typeset -g POWERLEVEL9K_TERRAFORM_OTHER_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ #############[ terraform_version: terraform version (https://www.terraform.io) ]##############
+ # Terraform version color.
+ typeset -g POWERLEVEL9K_TERRAFORM_VERSION_FOREGROUND=4
+ typeset -g POWERLEVEL9K_TERRAFORM_VERSION_BACKGROUND=0
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_TERRAFORM_VERSION_VISUAL_IDENTIFIER_EXPANSION='โญ'
+
+ ################[ terraform_version: It shows active terraform version (https://www.terraform.io) ]#################
+ typeset -g POWERLEVEL9K_TERRAFORM_VERSION_SHOW_ON_COMMAND='terraform|tf'
#############[ kubecontext: current kubernetes context (https://kubernetes.io/) ]#############
- # Show kubecontext only when the the command you are typing invokes one of these tools.
+ # Show kubecontext only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show kubecontext.
- typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito'
+ typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito|k9s|helmfile|flux|fluxctl|stern|kubeseal|skaffold|kubent|kubecolor|cmctl|sparkctl'
# Kubernetes context classes for the purpose of using different colors, icons and expansions with
# different contexts.
@@ -1247,9 +1457,9 @@
# typeset -g POWERLEVEL9K_KUBECONTEXT_PREFIX='at '
#[ aws: aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) ]#
- # Show aws only when the the command you are typing invokes one of these tools.
+ # Show aws only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show aws.
- typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|terraform|pulumi'
+ typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|cdk|terraform|pulumi|terragrunt'
# POWERLEVEL9K_AWS_CLASSES is an array with even number of elements. The first element
# in each pair defines a pattern against which the current AWS profile gets matched.
@@ -1278,51 +1488,109 @@
# '*prod*' PROD # These values are examples that are unlikely
# '*test*' TEST # to match your needs. Customize them as needed.
'*' DEFAULT)
- # typeset -g POWERLEVEL9K_AWS_DEFAULT_FOREGROUND=7
- # typeset -g POWERLEVEL9K_AWS_DEFAULT_BACKGROUND=1
+ typeset -g POWERLEVEL9K_AWS_DEFAULT_FOREGROUND=7
+ typeset -g POWERLEVEL9K_AWS_DEFAULT_BACKGROUND=1
# typeset -g POWERLEVEL9K_AWS_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # AWS segment format. The following parameters are available within the expansion.
+ #
+ # - P9K_AWS_PROFILE The name of the current AWS profile.
+ # - P9K_AWS_REGION The region associated with the current AWS profile.
+ typeset -g POWERLEVEL9K_AWS_CONTENT_EXPANSION='${P9K_AWS_PROFILE//\%/%%}${P9K_AWS_REGION:+ ${P9K_AWS_REGION//\%/%%}}'
+
#[ aws_eb_env: aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/) ]#
# AWS Elastic Beanstalk environment color.
- # typeset -g POWERLEVEL9K_AWS_EB_ENV_FOREGROUND=2
- # typeset -g POWERLEVEL9K_AWS_EB_ENV_BACKGROUND=0
+ typeset -g POWERLEVEL9K_AWS_EB_ENV_FOREGROUND=2
+ typeset -g POWERLEVEL9K_AWS_EB_ENV_BACKGROUND=0
# Custom icon.
# typeset -g POWERLEVEL9K_AWS_EB_ENV_VISUAL_IDENTIFIER_EXPANSION='โญ'
##########[ azure: azure account name (https://docs.microsoft.com/en-us/cli/azure) ]##########
- # Show azure only when the the command you are typing invokes one of these tools.
+ # Show azure only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show azure.
- typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi'
+ typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi|terragrunt'
+
+ # POWERLEVEL9K_AZURE_CLASSES is an array with even number of elements. The first element
+ # in each pair defines a pattern against which the current azure account name gets matched.
+ # More specifically, it's P9K_CONTENT prior to the application of context expansion (see below)
+ # that gets matched. If you unset all POWERLEVEL9K_AZURE_*CONTENT_EXPANSION parameters,
+ # you'll see this value in your prompt. The second element of each pair in
+ # POWERLEVEL9K_AZURE_CLASSES defines the account class. Patterns are tried in order. The
+ # first match wins.
+ #
+ # For example, given these settings:
+ #
+ # typeset -g POWERLEVEL9K_AZURE_CLASSES=(
+ # '*prod*' PROD
+ # '*test*' TEST
+ # '*' OTHER)
+ #
+ # If your current azure account is "company_test", its class is TEST because "company_test"
+ # doesn't match the pattern '*prod*' but does match '*test*'.
+ #
+ # You can define different colors, icons and content expansions for different classes:
+ #
+ # typeset -g POWERLEVEL9K_AZURE_TEST_FOREGROUND=2
+ # typeset -g POWERLEVEL9K_AZURE_TEST_BACKGROUND=0
+ # typeset -g POWERLEVEL9K_AZURE_TEST_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_AZURE_TEST_CONTENT_EXPANSION='> ${P9K_CONTENT} <'
+ typeset -g POWERLEVEL9K_AZURE_CLASSES=(
+ # '*prod*' PROD # These values are examples that are unlikely
+ # '*test*' TEST # to match your needs. Customize them as needed.
+ '*' OTHER)
+
# Azure account name color.
- # typeset -g POWERLEVEL9K_AZURE_FOREGROUND=7
- # typeset -g POWERLEVEL9K_AZURE_BACKGROUND=4
+ typeset -g POWERLEVEL9K_AZURE_OTHER_FOREGROUND=7
+ typeset -g POWERLEVEL9K_AZURE_OTHER_BACKGROUND=4
# Custom icon.
- # typeset -g POWERLEVEL9K_AZURE_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # typeset -g POWERLEVEL9K_AZURE_OTHER_VISUAL_IDENTIFIER_EXPANSION='โญ'
##########[ gcloud: google cloud account and project (https://cloud.google.com/) ]###########
- # Show gcloud only when the the command you are typing invokes one of these tools.
+ # Show gcloud only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show gcloud.
- typeset -g POWERLEVEL9K_GCLOUD_SHOW_ON_COMMAND='gcloud|gcs'
+ typeset -g POWERLEVEL9K_GCLOUD_SHOW_ON_COMMAND='gcloud|gcs|gsutil'
# Google cloud color.
- # typeset -g POWERLEVEL9K_GCLOUD_FOREGROUND=7
- # typeset -g POWERLEVEL9K_GCLOUD_BACKGROUND=4
+ typeset -g POWERLEVEL9K_GCLOUD_FOREGROUND=7
+ typeset -g POWERLEVEL9K_GCLOUD_BACKGROUND=4
- # Google cloud format. Change the value of POWERLEVEL9K_GCLOUD_CONTENT_EXPANSION if the default
- # is too verbose or not informative enough.
+ # Google cloud format. Change the value of POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION and/or
+ # POWERLEVEL9K_GCLOUD_COMPLETE_CONTENT_EXPANSION if the default is too verbose or not informative
+ # enough. You can use the following parameters in the expansions. Each of them corresponds to the
+ # output of `gcloud` tool.
#
- # P9K_GCLOUD_ACCOUNT: the output of `gcloud config get-value account`
- # P9K_GCLOUD_PROJECT: the output of `gcloud config get-value project`
- # ${VARIABLE//\%/%%}: ${VARIABLE} with all occurences of '%' replaced with '%%'.
+ # Parameter | Source
+ # -------------------------|--------------------------------------------------------------------
+ # P9K_GCLOUD_CONFIGURATION | gcloud config configurations list --format='value(name)'
+ # P9K_GCLOUD_ACCOUNT | gcloud config get-value account
+ # P9K_GCLOUD_PROJECT_ID | gcloud config get-value project
+ # P9K_GCLOUD_PROJECT_NAME | gcloud projects describe $P9K_GCLOUD_PROJECT_ID --format='value(name)'
#
- typeset -g POWERLEVEL9K_GCLOUD_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT//\%/%%}'
+ # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced with '%%'.
+ #
+ # Obtaining project name requires sending a request to Google servers. This can take a long time
+ # and even fail. When project name is unknown, P9K_GCLOUD_PROJECT_NAME is not set and gcloud
+ # prompt segment is in state PARTIAL. When project name gets known, P9K_GCLOUD_PROJECT_NAME gets
+ # set and gcloud prompt segment transitions to state COMPLETE.
+ #
+ # You can customize the format, icon and colors of gcloud segment separately for states PARTIAL
+ # and COMPLETE. You can also hide gcloud in state PARTIAL by setting
+ # POWERLEVEL9K_GCLOUD_PARTIAL_VISUAL_IDENTIFIER_EXPANSION and
+ # POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION to empty.
+ typeset -g POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_ID//\%/%%}'
+ typeset -g POWERLEVEL9K_GCLOUD_COMPLETE_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_NAME//\%/%%}'
+
+ # Send a request to Google (by means of `gcloud projects describe ...`) to obtain project name
+ # this often. Negative value disables periodic polling. In this mode project name is retrieved
+ # only when the current configuration, account or project id changes.
+ typeset -g POWERLEVEL9K_GCLOUD_REFRESH_PROJECT_NAME_SECONDS=60
# Custom icon.
# typeset -g POWERLEVEL9K_GCLOUD_VISUAL_IDENTIFIER_EXPANSION='โญ'
#[ google_app_cred: google application credentials (https://cloud.google.com/docs/authentication/production) ]#
- # Show google_app_cred only when the the command you are typing invokes one of these tools.
+ # Show google_app_cred only when the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show google_app_cred.
- typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi'
+ typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi|terragrunt'
# Google application credentials classes for the purpose of using different colors, icons and
# expansions with different credentials.
@@ -1354,8 +1622,8 @@
# '*:*prod*:*' PROD # These values are examples that are unlikely
# '*:*test*:*' TEST # to match your needs. Customize them as needed.
'*' DEFAULT)
- # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_FOREGROUND=7
- # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_BACKGROUND=4
+ typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_FOREGROUND=7
+ typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_BACKGROUND=4
# typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='โญ'
# Use POWERLEVEL9K_GOOGLE_APP_CRED_CONTENT_EXPANSION to specify the content displayed by
@@ -1371,26 +1639,37 @@
# P9K_GOOGLE_APP_CRED_PROJECT_ID | project_id
# P9K_GOOGLE_APP_CRED_CLIENT_EMAIL | client_email
#
- # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurences of '%' replaced by '%%'.
+ # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced by '%%'.
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_CONTENT_EXPANSION='${P9K_GOOGLE_APP_CRED_PROJECT_ID//\%/%%}'
+ ##############[ toolbox: toolbox name (https://github.com/containers/toolbox) ]###############
+ # Toolbox color.
+ typeset -g POWERLEVEL9K_TOOLBOX_FOREGROUND=0
+ typeset -g POWERLEVEL9K_TOOLBOX_BACKGROUND=3
+ # Don't display the name of the toolbox if it matches fedora-toolbox-*.
+ typeset -g POWERLEVEL9K_TOOLBOX_CONTENT_EXPANSION='${P9K_TOOLBOX_NAME:#fedora-toolbox-*}'
+ # Custom icon.
+ # typeset -g POWERLEVEL9K_TOOLBOX_VISUAL_IDENTIFIER_EXPANSION='โญ'
+ # Custom prefix.
+ # typeset -g POWERLEVEL9K_TOOLBOX_PREFIX='in '
+
###############################[ public_ip: public IP address ]###############################
# Public IP color.
- # typeset -g POWERLEVEL9K_PUBLIC_IP_FOREGROUND=7
- # typeset -g POWERLEVEL9K_PUBLIC_IP_BACKGROUND=0
+ typeset -g POWERLEVEL9K_PUBLIC_IP_FOREGROUND=7
+ typeset -g POWERLEVEL9K_PUBLIC_IP_BACKGROUND=0
# Custom icon.
# typeset -g POWERLEVEL9K_PUBLIC_IP_VISUAL_IDENTIFIER_EXPANSION='โญ'
########################[ vpn_ip: virtual private network indicator ]#########################
# VPN IP color.
- # typeset -g POWERLEVEL9K_VPN_IP_FOREGROUND=0
- # typeset -g POWERLEVEL9K_VPN_IP_BACKGROUND=6
+ typeset -g POWERLEVEL9K_VPN_IP_FOREGROUND=0
+ typeset -g POWERLEVEL9K_VPN_IP_BACKGROUND=6
# When on VPN, show just an icon without the IP address.
# Tip: To display the private IP address when on VPN, remove the next line.
typeset -g POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION=
# Regular expression for the VPN network interface. Run `ifconfig` or `ip -4 a show` while on VPN
# to see the name of the interface.
- typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(wg|(.*tun))[0-9]*'
+ typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(gpd|wg|(.*tun)|tailscale)[0-9]*|(zt.*)'
# If set to true, show one segment per matching network interface. If set to false, show only
# one segment corresponding to the first matching network interface.
# Tip: If you set it to true, you'll probably want to unset POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION.
@@ -1405,24 +1684,26 @@
# The following parameters are accessible within the expansion:
#
# Parameter | Meaning
- # ----------------------+---------------
- # P9K_IP_IP | IP address
- # P9K_IP_INTERFACE | network interface
- # P9K_IP_RX_BYTES | total number of bytes received
- # P9K_IP_TX_BYTES | total number of bytes sent
- # P9K_IP_RX_RATE | receive rate (since last prompt)
- # P9K_IP_TX_RATE | send rate (since last prompt)
+ # ----------------------+-------------------------------------------
+ # P9K_IP_IP | IP address
+ # P9K_IP_INTERFACE | network interface
+ # P9K_IP_RX_BYTES | total number of bytes received
+ # P9K_IP_TX_BYTES | total number of bytes sent
+ # P9K_IP_RX_BYTES_DELTA | number of bytes received since last prompt
+ # P9K_IP_TX_BYTES_DELTA | number of bytes sent since last prompt
+ # P9K_IP_RX_RATE | receive rate (since last prompt)
+ # P9K_IP_TX_RATE | send rate (since last prompt)
typeset -g POWERLEVEL9K_IP_CONTENT_EXPANSION='${P9K_IP_RX_RATE:+โฃ$P9K_IP_RX_RATE }${P9K_IP_TX_RATE:+โก$P9K_IP_TX_RATE }$P9K_IP_IP'
# Show information for the first network interface whose name matches this regular expression.
# Run `ifconfig` or `ip -4 a show` to see the names of all network interfaces.
- typeset -g POWERLEVEL9K_IP_INTERFACE='e.*'
+ typeset -g POWERLEVEL9K_IP_INTERFACE='[ew].*'
# Custom icon.
# typeset -g POWERLEVEL9K_IP_VISUAL_IDENTIFIER_EXPANSION='โญ'
#########################[ proxy: system-wide http/https/ftp proxy ]##########################
# Proxy color.
- # typeset -g POWERLEVEL9K_PROXY_FOREGROUND=4
- # typeset -g POWERLEVEL9K_PROXY_BACKGROUND=0
+ typeset -g POWERLEVEL9K_PROXY_FOREGROUND=4
+ typeset -g POWERLEVEL9K_PROXY_BACKGROUND=0
# Custom icon.
# typeset -g POWERLEVEL9K_PROXY_VISUAL_IDENTIFIER_EXPANSION='โญ'
@@ -1438,12 +1719,12 @@
typeset -g POWERLEVEL9K_BATTERY_STAGES=('%K{232}โ' '%K{232}โ' '%K{232}โ' '%K{232}โ' '%K{232}โ
' '%K{232}โ' '%K{232}โ' '%K{232}โ')
# Don't show the remaining time to charge/discharge.
typeset -g POWERLEVEL9K_BATTERY_VERBOSE=false
- # typeset -g POWERLEVEL9K_BATTERY_BACKGROUND=0
+ typeset -g POWERLEVEL9K_BATTERY_BACKGROUND=0
#####################################[ wifi: wifi speed ]#####################################
# WiFi color.
- # typeset -g POWERLEVEL9K_WIFI_FOREGROUND=0
- # typeset -g POWERLEVEL9K_WIFI_BACKGROUND=4
+ typeset -g POWERLEVEL9K_WIFI_FOREGROUND=0
+ typeset -g POWERLEVEL9K_WIFI_BACKGROUND=4
# Custom icon.
# typeset -g POWERLEVEL9K_WIFI_VISUAL_IDENTIFIER_EXPANSION='โญ'
@@ -1461,20 +1742,16 @@
# Parameter | Meaning
# ----------------------+---------------
# P9K_WIFI_SSID | service set identifier, a.k.a. network name
- # P9K_WIFI_LINK_AUTH | authentication protocol such as "wpa2-psk" or "none"
+ # P9K_WIFI_LINK_AUTH | authentication protocol such as "wpa2-psk" or "none"; empty if unknown
# P9K_WIFI_LAST_TX_RATE | wireless transmit rate in megabits per second
# P9K_WIFI_RSSI | signal strength in dBm, from -120 to 0
# P9K_WIFI_NOISE | noise in dBm, from -120 to 0
# P9K_WIFI_BARS | signal strength in bars, from 0 to 4 (derived from P9K_WIFI_RSSI and P9K_WIFI_NOISE)
- #
- # All parameters except P9K_WIFI_BARS are extracted from the output of the following command:
- #
- # /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I
####################################[ time: current time ]####################################
# Current time color.
- # typeset -g POWERLEVEL9K_TIME_FOREGROUND=0
- # typeset -g POWERLEVEL9K_TIME_BACKGROUND=7
+ typeset -g POWERLEVEL9K_TIME_FOREGROUND=0
+ typeset -g POWERLEVEL9K_TIME_BACKGROUND=7
# Format for the current time: 09:51:02. See `man 3 strftime`.
typeset -g POWERLEVEL9K_TIME_FORMAT='%D{%H:%M:%S}'
# If set to true, time will update when you hit enter. This way prompts for the past
@@ -1498,7 +1775,7 @@
# User-defined prompt segments may optionally provide an instant_prompt_* function. Its job
# is to generate the prompt segment for display in instant prompt. See
- # https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt.
+ # https://github.com/romkatv/powerlevel10k#instant-prompt.
#
# Powerlevel10k will call instant_prompt_* at the same time as the regular prompt_* function
# and will record all `p10k segment` calls it makes. When displaying instant prompt, Powerlevel10k
@@ -1516,7 +1793,8 @@
}
# User-defined prompt segments can be customized the same way as built-in segments.
- # typeset -g POWERLEVEL9K_EXAMPLE_FOREGROUND=3
+ typeset -g POWERLEVEL9K_EXAMPLE_FOREGROUND=3
+ typeset -g POWERLEVEL9K_EXAMPLE_BACKGROUND=1
# typeset -g POWERLEVEL9K_EXAMPLE_VISUAL_IDENTIFIER_EXPANSION='โญ'
# Transient prompt works similarly to the builtin transient_rprompt option. It trims down prompt
@@ -1534,7 +1812,7 @@
# it incompatible with your zsh configuration files.
# - quiet: Enable instant prompt and don't print warnings when detecting console output
# during zsh initialization. Choose this if you've read and understood
- # https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt.
+ # https://github.com/romkatv/powerlevel10k#instant-prompt.
# - verbose: Enable instant prompt and print a warning when detecting console output during
# zsh initialization. Choose this if you've never tried instant prompt, haven't
# seen the warning, or if you are unsure what this all means.
@@ -1551,5 +1829,8 @@
(( ! $+functions[p10k] )) || p10k reload
}
+# Tell `p10k configure` which file it should overwrite.
+typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a}
+
(( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]}
'builtin' 'unset' 'p10k_config_opts'
diff --git a/config/p10k-robbyrussell.zsh b/config/p10k-robbyrussell.zsh
index 5e0f8958..6a204d29 100644
--- a/config/p10k-robbyrussell.zsh
+++ b/config/p10k-robbyrussell.zsh
@@ -18,13 +18,13 @@
'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand'
() {
- emulate -L zsh
+ emulate -L zsh -o extended_glob
# Unset all configuration options.
- unset -m 'POWERLEVEL9K_*'
+ unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR'
# Zsh >= 5.1 is required.
- autoload -Uz is-at-least && is-at-least 5.1 || return
+ [[ $ZSH_VERSION == (5.<1->*|<6->.*) ]] || return
# Left prompt segments.
typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(prompt_char dir vcs)
@@ -87,7 +87,7 @@
# it incompatible with your zsh configuration files.
# - quiet: Enable instant prompt and don't print warnings when detecting console output
# during zsh initialization. Choose this if you've read and understood
- # https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt.
+ # https://github.com/romkatv/powerlevel10k#instant-prompt.
# - verbose: Enable instant prompt and print a warning when detecting console output during
# zsh initialization. Choose this if you've never tried instant prompt, haven't
# seen the warning, or if you are unsure what this all means.
@@ -104,5 +104,8 @@
(( ! $+functions[p10k] )) || p10k reload
}
+# Tell `p10k configure` which file it should overwrite.
+typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a}
+
(( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]}
'builtin' 'unset' 'p10k_config_opts'
diff --git a/font.md b/font.md
new file mode 100644
index 00000000..cb49af46
--- /dev/null
+++ b/font.md
@@ -0,0 +1,180 @@
+# Recommended font: Meslo Nerd Font patched for Powerlevel10k
+
+Gorgeous monospace font designed by Jim Lyles for Bitstream, customized by the same for Apple,
+further customized by Andrรฉ Berg, and finally patched by yours truly with customized scripts
+originally developed by Ryan L McIntyre of Nerd Fonts. Contains all glyphs and symbols that
+Powerlevel10k may need. Battle-tested in dozens of different terminals on all major operating
+systems.
+
+*FAQ*: [How was the recommended font created?](README.md#how-was-the-recommended-font-created)
+
+## Automatic font installation
+
+If you are using iTerm2 or Termux, `p10k configure` can install the recommended font for you.
+Simply answer `Yes` when asked whether to install *Meslo Nerd Font*.
+
+If you are using a different terminal, proceed with manual font installation. ๐
+
+## Manual font installation
+
+1. Download these four ttf files:
+ - [MesloLGS NF Regular.ttf](
+ https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Regular.ttf)
+ - [MesloLGS NF Bold.ttf](
+ https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Bold.ttf)
+ - [MesloLGS NF Italic.ttf](
+ https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Italic.ttf)
+ - [MesloLGS NF Bold Italic.ttf](
+ https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Bold%20Italic.ttf)
+1. Double-click on each file and click "Install". This will make `MesloLGS NF` font available to all
+ applications on your system.
+1. Configure your terminal to use this font:
+ - **iTerm2**: Type `p10k configure` and answer `Yes` when asked whether to install
+ *Meslo Nerd Font*. Alternatively, open *iTerm2 โ Preferences โ Profiles โ Text* and set *Font* to
+ `MesloLGS NF`.
+ - **Apple Terminal**: Open *Terminal โ Preferences โ Profiles โ Text*, click *Change* under *Font*
+ and select `MesloLGS NF` family.
+ - **Hyper**: Open *Hyper โ Edit โ Preferences* and change the value of `fontFamily` under
+ `module.exports.config` to `MesloLGS NF`.
+ - **Visual Studio Code**: Open *File โ Preferences โ Settings* (PC) or
+ *Code โ Preferences โ Settings* (Mac), enter `terminal.integrated.fontFamily` in the search box at
+ the top of *Settings* tab and set the value below to `MesloLGS NF`.
+ Consult [this screenshot](
+ https://raw.githubusercontent.com/romkatv/powerlevel10k-media/389133fb8c9a2347929a23702ce3039aacc46c3d/visual-studio-code-font-settings.jpg)
+ to see how it should look like or see [this issue](
+ https://github.com/romkatv/powerlevel10k/issues/671) for extra information.
+ - **GNOME Terminal** (the default Ubuntu terminal): Open *Terminal โ Preferences* and click on the
+ selected profile under *Profiles*. Check *Custom font* under *Text Appearance* and select
+ `MesloLGS NF Regular`.
+ - **Konsole**: Open *Settings โ Edit Current Profile โ Appearance*, click *Select Font* and select
+ `MesloLGS NF Regular`.
+ - **Tilix**: Open *Tilix โ Preferences* and click on the selected profile under *Profiles*. Check
+ *Custom font* under *Text Appearance* and select `MesloLGS NF Regular`.
+ - **Windows Console Host** (the old thing): Click the icon in the top left corner, then
+ *Properties โ Font* and set *Font* to `MesloLGS NF`.
+ - **Windows Terminal** by Microsoft (the new thing): Open *Settings* (Ctrl+,), click
+ either on the selected profile under *Profiles* or on *Defaults*, click *Appearance* and set
+ *Font face* to `MesloLGS NF`.
+ - **Conemu**: Open *Setup โ General โ Fonts* and set *Main console font* to `MesloLGS NF`.
+ - **IntelliJ** (and other IDEs by Jet Brains): Open *IDE โ Edit โ Preferences โ Editor โ
+ Color Scheme โ Console Font*. Select *Use console font instead of the default* and set the font
+ name to `MesloLGS NF`.
+ - **Termux**: Type `p10k configure` and answer `Yes` when asked whether to install
+ *Meslo Nerd Font*.
+ - **Blink**: Type `config`, go to *Appearance*, tap *Add a new font*, tap *Open Gallery*, select
+ *MesloLGS NF.css*, tap *import* and type `exit` in the home view to reload the font.
+ - **Tabby** (formerly **Terminus**): Open *Settings โ Appearance* and set *Font* to `MesloLGS NF`.
+ - **Terminator**: Open *Preferences* using the context menu. Under *Profiles* select the *General*
+ tab (should be selected already), uncheck *Use the system fixed width font* (if not already)
+ and select `MesloLGS NF Regular`. Exit the Preferences dialog by clicking *Close*.
+ - **Guake**: Right Click on an open terminal and open *Preferences*. Under *Appearance*
+ tab, uncheck *Use the system fixed width font* (if not already) and select `MesloLGS NF Regular`.
+ Exit the Preferences dialog by clicking *Close*.
+ - **MobaXterm**: Open *Settings* โ *Configuration* โ *Terminal* โ (under *Terminal look and feel*)
+ and change *Font* to `MesloLGS NF`. If you have *sessions*, you need to change the font in each
+ of them through *Settings* โ right click on an individual session โ *Edit Session* โ *Terminal
+ Settings* โ *Font settings*.
+ - **Asbrรบ Connection Manager**: Open *Preferences โ Local Shell Options โ Look and Feel*, enable
+ *Use these personal options* and change *Font:* under *Terminal UI* to `MesloLGS NF Regular`.
+ To change the font for the remote host connections, go to *Preferences โ Terminal Options โ
+ Look and Feel* and change *Font:* under *Terminal UI* to `MesloLGS NF Regular`.
+ - **Warp**: Open Warp and Navigate to *Settings* then *Appearance*. Scroll down to *Text* Section
+ and under *"Terminal Font"*, select the `MesloLGS NF` font.
+ - **WSLtty**: Right click on an open terminal and then on *Options*. In the *Text* section, under
+ *Font*, click *"Select..."* and set Font to `MesloLGS NF Regular`.
+ - **Yakuake**: Click *โก* โ *Manage Profiles* โ *New* โ *Appearance*. Click *Choose* next to the
+ *Font* dropdown, select `MesloLGS NF` and click *OK*. Click *OK* to save the profile. Select the
+ new profile and click *Set as Default*.
+ - **Alacritty**: Create or open `~/.config/alacritty/alacritty.toml` and add the following
+ section to it:
+ ```toml
+ [font.normal]
+ family = "MesloLGS NF"
+ ```
+ - **foot**: Create or open `~/.config/foot/foot.ini` and add the following section to it:
+ ```ini
+ font=MesloLGS NF:size=12
+ ```
+ - **kitty**: Create or open `~/.config/kitty/kitty.conf` and add the following line to it:
+ ```text
+ font_family MesloLGS NF
+ ```
+ Restart kitty by closing all sessions and opening a new session.
+ - **puTTY**: Set *Window* โ *Appearance* โ *Font* to `MesloLGS NF`. Requires puTTY
+ version >= 0.75.
+ - **WezTerm**: Create or open `$HOME/.config/wezterm/wezterm.lua` and add the following:
+ ```lua
+ local wezterm = require 'wezterm';
+ return {
+ font = wezterm.font("MesloLGS NF"),
+ }
+ ```
+ If the file already exists, only add the line with the font to the existing return.
+ Also add the first line if it is not already present.
+ - **urxvt**: Create or open `~/.Xresources` and add the following line to it:
+ ```text
+ URxvt.font: xft:MesloLGS NF:size=11
+ ```
+ You can adjust the font size to your preference. After changing the config run
+ `xrdb ~/.Xresources` to reload it. The new config is applied to all new terminals.
+ - **xterm**: Create or open `~/.Xresources` and add the following line to it:
+ ```text
+ xterm*faceName: MesloLGS NF
+ ```
+ After changing the config run `xrdb ~/.Xresources` to reload it. The new config is applied to
+ all new terminals.
+ - **Zed**: Open `~/.config/zed/settings.json` and set `terminal.font_family` to `"MesloLGS NF"`.
+ ```jsonc
+ {
+ "terminal": {
+ "font_family": "MesloLGS NF"
+ },
+ // Other settings.
+ }
+ ```
+ - Crostini (Linux on Chrome OS): Open
+ chrome-untrusted://terminal/html/nassh_preferences_editor.html, set *Text font family* to
+ `'MesloLGS NF'` (including the quotes) and *Custom CSS (inline text)* to the following:
+ ```css
+ @font-face {
+ font-family: "MesloLGS NF";
+ src: url("https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/MesloLGS%20NF%20Regular.ttf");
+ font-weight: normal;
+ font-style: normal;
+ }
+ @font-face {
+ font-family: "MesloLGS NF";
+ src: url("https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/MesloLGS%20NF%20Bold.ttf");
+ font-weight: bold;
+ font-style: normal;
+ }
+ @font-face {
+ font-family: "MesloLGS NF";
+ src: url("https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/MesloLGS%20NF%20Italic.ttf");
+ font-weight: normal;
+ font-style: italic;
+ }
+ @font-face {
+ font-family: "MesloLGS NF";
+ src: url("https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/MesloLGS%20NF%20Bold%20Italic.ttf");
+ font-weight: bold;
+ font-style: italic;
+ }
+ ```
+ **_CAVEAT_**: If you open the normal terminal preferences these settings will be overwritten.
+ - **Deepin Terminal**: Create or open `~/.config/deepin/deepin-terminal/config.conf` and add the following section
+ to it:
+ ```ini
+ [basic.interface.font]
+ value = "MesloLGS NF"
+ ```
+ - **Ghostty**: Open *Menu โ Open Configuration* (Linux) or *Ghostty โ Settings...* (Mac) and add
+ the following line:
+ ```text
+ font-family = "MesloLGS NF"
+ ```
+1. Run `p10k configure` to generate a new `~/.p10k.zsh`. The old config may work
+ incorrectly with the new font.
+
+_Using a different terminal and know how to set the font for it? Share your knowledge by sending a
+PR to expand the list!_
diff --git a/gitstatus/.clang-format b/gitstatus/.clang-format
new file mode 100644
index 00000000..f5e3c53f
--- /dev/null
+++ b/gitstatus/.clang-format
@@ -0,0 +1,4 @@
+BasedOnStyle: Google
+ColumnLimit: 100
+DerivePointerAlignment: false
+PointerAlignment: Left
diff --git a/gitstatus/.gitattributes b/gitstatus/.gitattributes
new file mode 100644
index 00000000..5c1135cb
--- /dev/null
+++ b/gitstatus/.gitattributes
@@ -0,0 +1,16 @@
+* text=auto
+
+*.cc text eol=lf
+*.h text eol=lf
+*.info text eol=lf
+*.json text eol=lf
+*.md text eol=lf
+*.sh text eol=lf
+*.zsh text eol=lf
+
+/.clang-format text eol=lf
+/LICENSE text eol=lf
+/Makefile text eol=lf
+/build text eol=lf
+/install text eol=lf
+/mbuild text eol=lf
diff --git a/gitstatus/.gitignore b/gitstatus/.gitignore
new file mode 100644
index 00000000..4915fe60
--- /dev/null
+++ b/gitstatus/.gitignore
@@ -0,0 +1,8 @@
+*.zwc
+/core
+/deps/libgit2-*.tar.gz
+/locks
+/logs
+/obj
+/usrbin/gitstatusd*
+/.vscode/ipch
diff --git a/gitstatus/.vscode/c_cpp_properties.json b/gitstatus/.vscode/c_cpp_properties.json
new file mode 100644
index 00000000..323a6cde
--- /dev/null
+++ b/gitstatus/.vscode/c_cpp_properties.json
@@ -0,0 +1,17 @@
+{
+ "configurations": [
+ {
+ "name": "Linux",
+ "includePath": [
+ "${workspaceFolder}/src"
+ ],
+ "defines": [
+ ],
+ "compilerPath": "/usr/bin/g++",
+ "cStandard": "c11",
+ "cppStandard": "c++17",
+ "intelliSenseMode": "gcc-x64"
+ }
+ ],
+ "version": 4
+}
diff --git a/gitstatus/.vscode/settings.json b/gitstatus/.vscode/settings.json
new file mode 100644
index 00000000..bec79f94
--- /dev/null
+++ b/gitstatus/.vscode/settings.json
@@ -0,0 +1,72 @@
+{
+ "files.exclude": {
+ "*.zwc": true,
+ "core": true,
+ "locks/": true,
+ "logs/": true,
+ "obj/": true,
+ "usrbin/": true,
+ },
+ "files.associations": {
+ "array": "cpp",
+ "atomic": "cpp",
+ "*.tcc": "cpp",
+ "cctype": "cpp",
+ "chrono": "cpp",
+ "clocale": "cpp",
+ "cmath": "cpp",
+ "complex": "cpp",
+ "condition_variable": "cpp",
+ "cstddef": "cpp",
+ "cstdint": "cpp",
+ "cstdio": "cpp",
+ "cstdlib": "cpp",
+ "cstring": "cpp",
+ "ctime": "cpp",
+ "cwchar": "cpp",
+ "cwctype": "cpp",
+ "deque": "cpp",
+ "unordered_map": "cpp",
+ "unordered_set": "cpp",
+ "vector": "cpp",
+ "exception": "cpp",
+ "fstream": "cpp",
+ "functional": "cpp",
+ "future": "cpp",
+ "initializer_list": "cpp",
+ "iomanip": "cpp",
+ "iosfwd": "cpp",
+ "iostream": "cpp",
+ "istream": "cpp",
+ "limits": "cpp",
+ "memory": "cpp",
+ "mutex": "cpp",
+ "new": "cpp",
+ "numeric": "cpp",
+ "optional": "cpp",
+ "ostream": "cpp",
+ "ratio": "cpp",
+ "sstream": "cpp",
+ "stdexcept": "cpp",
+ "streambuf": "cpp",
+ "string_view": "cpp",
+ "system_error": "cpp",
+ "thread": "cpp",
+ "type_traits": "cpp",
+ "tuple": "cpp",
+ "typeinfo": "cpp",
+ "utility": "cpp",
+ "variant": "cpp",
+ "cstdarg": "cpp",
+ "charconv": "cpp",
+ "algorithm": "cpp",
+ "cinttypes": "cpp",
+ "iterator": "cpp",
+ "map": "cpp",
+ "memory_resource": "cpp",
+ "random": "cpp",
+ "string": "cpp",
+ "bit": "cpp",
+ "netfwd": "cpp"
+ }
+}
diff --git a/gitstatus/Makefile b/gitstatus/Makefile
new file mode 100644
index 00000000..4a695acb
--- /dev/null
+++ b/gitstatus/Makefile
@@ -0,0 +1,57 @@
+APPNAME ?= gitstatusd
+OBJDIR ?= obj
+
+CXX ?= g++
+ZSH := $(shell command -v zsh 2> /dev/null)
+
+VERSION ?= $(shell . ./build.info && printf "%s" "$$gitstatus_version")
+
+# Note: -fsized-deallocation is not used to avoid binary compatibility issues on macOS.
+#
+# Sized delete is implemented as __ZdlPvm in /usr/lib/libc++.1.dylib but this symbol is
+# missing in macOS prior to 10.13.
+CXXFLAGS += -std=c++14 -funsigned-char -O3 -DNDEBUG -DGITSTATUS_VERSION=$(VERSION) # -Wall -g -fsanitize=thread
+LDFLAGS += -pthread # -fsanitize=thread
+LDLIBS += -lgit2 # -lprofiler -lunwind
+
+SRCS := $(shell find src -name "*.cc")
+OBJS := $(patsubst src/%.cc, $(OBJDIR)/%.o, $(SRCS))
+
+all: $(APPNAME)
+
+$(APPNAME): usrbin/$(APPNAME)
+
+usrbin/$(APPNAME): $(OBJS)
+ $(CXX) $(OBJS) $(LDFLAGS) $(LDLIBS) -o $@
+
+$(OBJDIR):
+ mkdir -p -- $(OBJDIR)
+
+$(OBJDIR)/%.o: src/%.cc Makefile build.info | $(OBJDIR)
+ $(CXX) $(CXXFLAGS) -MM -MT $@ src/$*.cc >$(OBJDIR)/$*.dep
+ $(CXX) $(CXXFLAGS) -Wall -c -o $@ src/$*.cc
+
+clean:
+ rm -rf -- $(OBJDIR)
+
+zwc:
+ $(or $(ZSH),:) -fc 'for f in *.zsh install; do zcompile -R -- $$f.zwc $$f || exit; done'
+
+minify:
+ rm -rf -- .clang-format .git .gitattributes .gitignore .vscode deps docs src usrbin/.gitkeep LICENSE Makefile README.md build mbuild
+
+pkg: zwc
+ GITSTATUS_DAEMON= GITSTATUS_CACHE_DIR=$(shell pwd)/usrbin ./install -f
+
+-include $(OBJS:.o=.dep)
+
+.PHONY: help
+
+help:
+ @echo "Usage: make [TARGET]"
+ @echo "Available targets:"
+ @echo " all Build $(APPNAME) (default target)"
+ @echo " clean Remove generated files and directories"
+ @echo " zwc Compile Zsh files"
+ @echo " minify Remove unnecessary files and folders"
+ @echo " pkg Create a package"
diff --git a/gitstatus/README.md b/gitstatus/README.md
index bfe8c3e3..609d5f82 100644
--- a/gitstatus/README.md
+++ b/gitstatus/README.md
@@ -1 +1,534 @@
-This is a bundled copy of [gitstatus](https://github.com/romkatv/gitstatus) ZSH plugin.
+# gitstatus
+
+- **THE PROJECT HAS VERY LIMITED SUPPORT**
+- **NO NEW FEATURES ARE IN THE WORKS**
+- **MOST BUGS WILL GO UNFIXED**
+
+**gitstatus** is a 10x faster alternative to `git status` and `git describe`. Its primary use
+case is to enable fast git prompt in interactive shells.
+
+Heavy lifting is done by **gitstatusd** -- a custom binary written in C++. It comes with Zsh and
+Bash bindings for integration with shell.
+
+## Table of Contents
+
+1. [Using from Zsh](#using-from-zsh)
+1. [Using from Bash](#using-from-bash)
+2. [Using from other shells](#using-from-other-shells)
+1. [How it works](#how-it-works)
+1. [Benchmarks](#benchmarks)
+1. [Why fast](#why-fast)
+1. [Requirements](#requirements)
+1. [Compiling](#compiling)
+1. [License](#license)
+
+## Using from Zsh
+
+The easiest way to take advantage of gitstatus from Zsh is to use a theme that's already integrated
+with it. For example, [Powerlevel10k](https://github.com/romkatv/powerlevel10k) is a flexible and
+fast theme with first-class gitstatus integration. If you install Powerlevel10k, you don't need to
+install gitstatus.
+
+
+
+For those who wish to use gitstatus without a theme, there is
+[gitstatus.prompt.zsh](gitstatus.prompt.zsh). Install it as follows:
+
+```zsh
+git clone --depth=1 https://github.com/romkatv/gitstatus.git ~/gitstatus
+echo 'source ~/gitstatus/gitstatus.prompt.zsh' >>! ~/.zshrc
+```
+
+Users in China can use the official mirror on gitee.com for faster download.
+ไธญๅฝๅคง้็จๆทๅฏไปฅไฝฟ็จ gitee.com ไธ็ๅฎๆน้ๅๅ ้ไธ่ฝฝ.
+
+```zsh
+git clone --depth=1 https://gitee.com/romkatv/gitstatus.git ~/gitstatus
+echo 'source ~/gitstatus/gitstatus.prompt.zsh' >>! ~/.zshrc
+```
+
+Alternatively, if you have Homebrew installed:
+
+```zsh
+brew install romkatv/gitstatus/gitstatus
+echo "source $(brew --prefix)/opt/gitstatus/gitstatus.prompt.zsh" >>! ~/.zshrc
+```
+
+(If you choose this option, replace `~/gitstatus` with `$(brew --prefix)/opt/gitstatus/gitstatus`
+in all code snippets below.)
+
+_Make sure to disable your current theme if you have one._
+
+This will give you a basic yet functional prompt with git status in it. It's
+[over 10x faster](#benchmarks) than any alternative that can give you comparable prompt. In order
+to customize it, set `PROMPT` and/or `RPROMPT` at the end of `~/.zshrc` after sourcing
+`gitstatus.prompt.zsh`. Insert `${GITSTATUS_PROMPT}` where you want git status to go. For example:
+
+```zsh
+source ~/gitstatus/gitstatus.prompt.zsh
+
+PROMPT='%~%# ' # left prompt: directory followed by %/# (normal/root)
+RPROMPT='$GITSTATUS_PROMPT' # right prompt: git status
+```
+
+The expansion of `${GITSTATUS_PROMPT}` can contain the following bits:
+
+| segment | meaning |
+|-------------|-------------------------------------------------------|
+| `master` | current branch |
+| `#v1` | HEAD is tagged with `v1`; not shown when on a branch |
+| `@5fc6fca4` | current commit; not shown when on a branch or tag |
+| `โฃ1` | local branch is behind the remote by 1 commit |
+| `โก2` | local branch is ahead of the remote by 2 commits |
+| `โ 3` | local branch is behind the push remote by 3 commits |
+| `โข4` | local branch is ahead of the push remote by 4 commits |
+| `*5` | there are 5 stashes |
+| `merge` | merge is in progress (could be some other action) |
+| `~6` | there are 6 merge conflicts |
+| `+7` | there are 7 staged changes |
+| `!8` | there are 8 unstaged changes |
+| `?9` | there are 9 untracked files |
+
+`$GITSTATUS_PROMPT_LEN` tells you how long `$GITSTATUS_PROMPT` is when printed to the console.
+[gitstatus.prompt.zsh](gitstatus.prompt.zsh) has an example of using it to truncate the current
+directory.
+
+If you'd like to change the format of git status, or want to have greater control over the
+process of assembling `PROMPT`, you can copy and modify parts of
+[gitstatus.prompt.zsh](gitstatus.prompt.zsh) instead of sourcing the script. Your `~/.zshrc`
+might look something like this:
+
+```zsh
+source ~/gitstatus/gitstatus.plugin.zsh
+
+function my_set_prompt() {
+ PROMPT='%~%# '
+ RPROMPT=''
+
+ if gitstatus_query MY && [[ $VCS_STATUS_RESULT == ok-sync ]]; then
+ RPROMPT=${${VCS_STATUS_LOCAL_BRANCH:-@${VCS_STATUS_COMMIT}}//\%/%%} # escape %
+ (( VCS_STATUS_NUM_STAGED )) && RPROMPT+='+'
+ (( VCS_STATUS_NUM_UNSTAGED )) && RPROMPT+='!'
+ (( VCS_STATUS_NUM_UNTRACKED )) && RPROMPT+='?'
+ fi
+
+ setopt no_prompt_{bang,subst} prompt_percent # enable/disable correct prompt expansions
+}
+
+gitstatus_stop 'MY' && gitstatus_start -s -1 -u -1 -c -1 -d -1 'MY'
+autoload -Uz add-zsh-hook
+add-zsh-hook precmd my_set_prompt
+```
+
+This snippet is sourcing `gitstatus.plugin.zsh` rather than `gitstatus.prompt.zsh`. The former
+defines low-level bindings that communicate with gitstatusd over pipes. The latter is a simple
+script that uses these bindings to assemble git prompt.
+
+Unlike [Powerlevel10k](https://github.com/romkatv/powerlevel10k), code based on
+[gitstatus.prompt.zsh](gitstatus.prompt.zsh) is communicating with gitstatusd synchronously. This
+can make your prompt slow when working in a large git repository or on a slow machine. To avoid
+this problem, call `gitstatus_query` asynchronously as documented in
+[gitstatus.plugin.zsh](gitstatus.plugin.zsh). This can be quite challenging.
+
+## Using from Bash
+
+The easiest way to take advantage of gitstatus from Bash is via
+[gitstatus.prompt.sh](gitstatus.prompt.sh). Install it as follows:
+
+```bash
+git clone --depth=1 https://github.com/romkatv/gitstatus.git ~/gitstatus
+echo 'source ~/gitstatus/gitstatus.prompt.sh' >> ~/.bashrc
+```
+
+Users in China can use the official mirror on gitee.com for faster download.
+ไธญๅฝๅคง้็จๆทๅฏไปฅไฝฟ็จ gitee.com ไธ็ๅฎๆน้ๅๅ ้ไธ่ฝฝ.
+
+```bash
+git clone --depth=1 https://gitee.com/romkatv/gitstatus.git ~/gitstatus
+echo 'source ~/gitstatus/gitstatus.prompt.sh' >> ~/.bashrc
+```
+
+Alternatively, if you have Homebrew installed:
+
+```zsh
+brew install romkatv/gitstatus/gitstatus
+echo "source $(brew --prefix)/opt/gitstatus/gitstatus.prompt.sh" >> ~/.bashrc
+```
+
+(If you choose this option, replace `~/gitstatus` with `$(brew --prefix)/opt/gitstatus/gitstatus`
+in all code snippets below.)
+
+This will give you a basic yet functional prompt with git status in it. It's
+[over 10x faster](#benchmarks) than any alternative that can give you comparable prompt.
+
+
+
+In order to customize your prompt, set `PS1` at the end of `~/.bashrc` after sourcing
+`gitstatus.prompt.sh`. Insert `${GITSTATUS_PROMPT}` where you want git status to go. For example:
+
+```bash
+source ~/gitstatus/gitstatus.prompt.sh
+
+PS1='\w ${GITSTATUS_PROMPT}\n\$ ' # directory followed by git status and $/# (normal/root)
+```
+
+The expansion of `${GITSTATUS_PROMPT}` can contain the following bits:
+
+| segment | meaning |
+|-------------|-------------------------------------------------------|
+| `master` | current branch |
+| `#v1` | HEAD is tagged with `v1`; not shown when on a branch |
+| `@5fc6fca4` | current commit; not shown when on a branch or tag |
+| `โฃ1` | local branch is behind the remote by 1 commit |
+| `โก2` | local branch is ahead of the remote by 2 commits |
+| `โ 3` | local branch is behind the push remote by 3 commits |
+| `โข4` | local branch is ahead of the push remote by 4 commits |
+| `*5` | there are 5 stashes |
+| `merge` | merge is in progress (could be some other action) |
+| `~6` | there are 6 merge conflicts |
+| `+7` | there are 7 staged changes |
+| `!8` | there are 8 unstaged changes |
+| `?9` | there are 9 untracked files |
+
+If you'd like to change the format of git status, or want to have greater control over the
+process of assembling `PS1`, you can copy and modify parts of
+[gitstatus.prompt.sh](gitstatus.prompt.sh) instead of sourcing the script. Your `~/.bashrc` might
+look something like this:
+
+```bash
+source ~/gitstatus/gitstatus.plugin.sh
+
+function my_set_prompt() {
+ PS1='\w'
+
+ if gitstatus_query && [[ "$VCS_STATUS_RESULT" == ok-sync ]]; then
+ if [[ -n "$VCS_STATUS_LOCAL_BRANCH" ]]; then
+ PS1+=" ${VCS_STATUS_LOCAL_BRANCH//\\/\\\\}" # escape backslash
+ else
+ PS1+=" @${VCS_STATUS_COMMIT//\\/\\\\}" # escape backslash
+ fi
+ (( VCS_STATUS_HAS_STAGED" )) && PS1+='+'
+ (( VCS_STATUS_HAS_UNSTAGED" )) && PS1+='!'
+ (( VCS_STATUS_HAS_UNTRACKED" )) && PS1+='?'
+ fi
+
+ PS1+='\n\$ '
+
+ shopt -u promptvars # disable expansion of '$(...)' and the like
+}
+
+gitstatus_stop && gitstatus_start
+PROMPT_COMMAND=my_set_prompt
+```
+
+This snippet is sourcing `gitstatus.plugin.sh` rather than `gitstatus.prompt.sh`. The former
+defines low-level bindings that communicate with gitstatusd over pipes. The latter is a simple
+script that uses these bindings to assemble git prompt.
+
+Note: Bash bindings, unlike Zsh bindings, don't support asynchronous calls.
+
+## Using from other shells
+
+If there are no gitstatusd bindings for your shell, you'll need to get your hands dirty.
+Use the existing bindings for inspiration; run `gitstatusd --help` or read the same thing in
+[options.cc](src/options.cc).
+
+## How it works
+
+gitstatusd reads requests from stdin and prints responses to stdout. Requests contain an ID and
+a directory. Responses contain the same ID and machine-readable git status for the directory.
+gitstatusd keeps some state in memory for the directories it has seen in order to serve future
+requests faster.
+
+[Zsh bindings](gitstatus.plugin.zsh) and [Bash bindings](gitstatus.plugin.sh) start gitstatusd in
+the background and communicate with it via pipes. Themes such as
+[Powerlevel10k](https://github.com/romkatv/powerlevel10k) use these bindings to put git status in
+`PROMPT`.
+
+Note that gitstatus cannot be used as a drop-in replacement for `git status` command as it doesn't
+produce output in the same format. It does perform the same computation though.
+
+## Benchmarks
+
+The following benchmark results were obtained on Intel i9-7900X running Ubuntu 18.04 in
+a clean [chromium](https://github.com/chromium/chromium) repository synced to `9394e49a`. The
+repository was checked out to an ext4 filesystem on M.2 SSD.
+
+Three functionally equivalent tools for computing git status were benchmarked:
+
+* `gitstatusd`
+* `git` with `core.untrackedcache` enabled and `core.fsmonitor` disabled
+* `lg2` -- a demo/example executable from [libgit2](https://github.com/romkatv/libgit2) that
+ implements a subset of `git` functionality on top of libgit2 API; for the purposes of this
+ benchmark the subset is sufficient to generate the same data as the other tools
+
+Every tool was benchmark in cold and hot conditions. For `git` the first run in a repository was
+considered cold, with the following runs considered hot. `lg2` was patched to compute results twice
+in a single invocation without freeing the repository in between; the second run was considered hot.
+The same patching was not done for `git` because `git` cannot be easily modified to refresh inmemory
+index state between invocations; in fact, this limitation is one of the primary reasons developers
+use libgit2. `gitstatusd` was benchmarked similarly to `lg2` with two result computations in the
+same invocation.
+
+Two commands were benchmarked: `status` and `describe`.
+
+### Status
+
+In this benchmark all tools were computing the equivalent of `git status`. Lower numbers are better.
+
+| Tool | Cold | Hot |
+|---------------|-----------:|------------:|
+| **gitstatus** | **291 ms** | **30.9 ms** |
+| git | 876 ms | 295 ms |
+| lg2 | 1730 ms | 1310 ms |
+
+gitstatusd is substantially faster than the alternatives, especially on hot runs. Note that hot runs
+are of primary importance to the main use case of gitstatus in interactive shells.
+
+The performance of `git status` fluctuated wildly in this benchmarks for reasons unknown to the
+author. Moreover, performance is sticky -- once `git status` settles around a number, it stays
+there for a long time. Numbers as diverse as 295, 352, 663 and 730 had been observed on hot runs on
+the same repository. The number in the table is the lowest (fastest or best) that `git status` had
+shown.
+
+### Describe
+
+In this benchmark all tools were computing the equivalent of `git describe --tags --exact-match`
+to find tags that resolve to the same commit as `HEAD`. Lower numbers are better.
+
+| Tool | Cold | Hot |
+|---------------|------------:|--------------:|
+| **gitstatus** | **4.04 ms** | **0.0345 ms** |
+| git | 18.0 ms | 14.5 ms |
+| lg2 | 185 ms | 45.2 ms |
+
+gitstatusd is once again faster than the alternatives, more so on hot runs.
+
+## Why fast
+
+Since gitstatusd doesn't have to print all staged/unstaged/untracked files but only report
+whether there are any, it can terminate repository scan early. It can also remember which files
+were dirty on the previous run and check them first on the next run to avoid the scan entirely if
+the files are still dirty. However, the benchmarks above were performed in a clean repository where
+these shortcuts do not trigger. All benchmarked tools had to do the same work -- check the status
+of every file in the index to see if it has changed, check every directory for newly created files,
+etc. And yet, gitstatusd came ahead by a large margin. This section describes what it does that
+makes it so fast.
+
+Most of the following comparisons are done against libgit2 rather than git because of the author's
+familiarity with the former but not the with latter. libgit2 has clean, well-documented APIs and an
+elegant implementation, which makes it so much easier to work with and to analyze performance
+bottlenecks.
+
+### Summary for the impatient
+
+Under the benchmark conditions described above, the equivalent of libgit2's
+`git_diff_index_to_workdir` (the most expensive part of `status` command) is 46.3 times faster in
+gitstatusd. The speedup comes from the following sources.
+
+* gitstatusd uses more efficient data structures and algorithms and employs performance-conscious
+coding style throughout the codebase. This reduces CPU time in userspace by 32x compared to libgit2.
+* gitstatusd uses less expensive system calls and makes fewer of them. This reduces CPU time spent
+in kernel by 1.9x.
+* gitstatusd can utilize multiple cores to scan index and workdir in parallel with almost perfect
+scaling. This reduces total run time by 12.4x while having virtually no effect on total CPU time.
+
+### Problem statement
+
+The most resource-intensive part of the `status` command is finding the difference between _index_
+and _workdir_ (`git_diff_index_to_workdir` in libgit2). Index is a list of all files in the git
+repository with their last modification times. This is an obvious simplification but it suffices for
+this exposition. On disk, index is stored sorted by file path. Here's an example of git index:
+
+| File | Last modification time |
+|-------------|-----------------------:|
+| Makefile | 2019-04-01T14:12:32Z |
+| src/hello.c | 2019-04-01T14:12:00Z |
+| src/hello.h | 2019-04-01T14:12:32Z |
+
+This list needs to be compared to the list of files in the working directory. If any of the files
+listed in the index are missing from the workdir or have different last modification time, they are
+"unstaged" in gitstatusd parlance. If you run `git status`, they'll be shown as "changes not staged
+for commit". Thus, any implementation of `status` command has to call `stat()` or one of its
+variants on every file in the index.
+
+In addition, all files in the working directory for which there is no entry in the index at all are
+"untracked". `git status` will show them as "untracked files". Finding untracked files requires some
+form of work directory traversal.
+
+### Single-threaded scan
+
+Let's see how `git_diff_index_to_workdir` from libgit2 accomplishes these tasks. Here's its CPU
+profile from 200 hot runs over chromium repository.
+
+
+
+(The CPU profile was created with [gperftools](https://github.com/gperftools/gperftools) and
+rendered with [pprof](https://github.com/google/pprof)).
+
+We can see `__GI__lxstat` taking a lot of time. This is the `stat()` call for every file in the
+index. We can also identify `__opendir`, `__readdir` and `__GI___close_nocancel` -- glibc wrappers
+for reading the contents of a directory. This is for finding untracked files. Out of the total 232
+seconds, 111 seconds -- or 47.7% -- was spent on these calls. The rest is computation -- comparing
+strings, sorting arrays, etc.
+
+Now let's take a look at the CPU profile of gitstatusd on the same task.
+
+
+
+The first impression is that this profile looks pruned. This isn't an artifact. The profile was
+generated with the same tools and the same flags as the profile of libgit2.
+
+Since both profiles were generated from the same workload, absolute numbers can be compared. We can
+see that gitstatusd took 62 seconds in total compared to libgit2's 232 seconds. System calls at the
+core of the algorithm are clearly visible. `__GI___fxstatat` is a flavor of `stat()`, and the other
+three calls -- `__libc_openat64`, `__libc_close` and `__GI___fxstat` are responsible for opening
+directories and finding untracked files. Notice that there is almost nothing else in the profile
+apart from these calls. The rest of the code accounts for 3.77 seconds of CPU time -- 32 times less
+than in libgit2.
+
+So, one reason gitstatusd is fast is that it has efficient diffing code -- very little time is spent
+outside of kernel. However, if we look closely, we can notice that system calls in gitstatusd are
+_also_ faster than in libgit2. For example, libgit2 spent 72.07 seconds in `__GI__lxstat` while
+gitstatusd spent only 48.82 seconds in `__GI___fxstatat`. There are two reasons for this difference.
+First, libgit2 makes more `stat()` calls than is strictly required. It's not necessary to stat
+directories because index only has files. There are 25k directories in chromium repository (and 300k
+files) -- that's 25k `stat()` calls that could be avoided. The second reason is that libgit2 and
+gitstatusd use different flavors of `stat()`. libgit2 uses `lstat()`, which takes a path to the file
+as input. Its performance is linear in the number of subdirectories in the path because it needs to
+perform a lookup for every one of them and to check permissions. gitstatusd uses `fstatat()`, which
+takes a file descriptor to the parent directory and a name of the file. Just a single lookup, less
+CPU time.
+
+Similarly to `lstat()` vs `fstatat()`, it's faster to open files and directories with `openat()`
+from the parent directory file descriptor than with regular `open()` that accepts full file path.
+gitstatusd takes advantage of `openat()` to open directories as fast as possible. It opens about 90%
+of the directories (this depends on the actual directory structure of the repository) from the
+immediate parent -- the most efficient way -- and the remaining 10% it opens from the repository's
+root directory. The reason it's done this way is to keep the maximum number of simultaneously open
+file descriptors bounded. libgit2 can have O(repository depth) simultaneously open file descriptors,
+which may be OK for a single-threaded application but can balloon to a large number when scans are
+done by many threads simultaneously, like in gitstatusd.
+
+There is no equivalent to `__opendir` or `__readdir` in the gitstatusd profile because it uses the
+equivalent of [untracked cache](https://git-scm.com/docs/git-update-index#_untracked_cache) from
+git. On the first scan of the workdir gitstatusd lists all files just like libgit2. But, unlike
+libgit2, it remembers the last modification time of every directory along with the list of
+untracked files under it. On the next scan, gitstatusd can skip listing files in directories whose
+last modification time hasn't changed.
+
+To summarize, here's what gitstatusd was doing when the CPU profile was captured:
+
+1. `__libc_openat64`: Open every directory for which there are files in the index.
+2. `__GI___fxstat`: Check last modification time of the directory. Since it's the same as on the
+ last scan, this directory has the same list of untracked files as before, which is empty (the
+ repository is clean).
+3. `__GI___fxstatat`: Check last modification time for every file in the index that belongs to this
+ directory.
+4. `__libc_close`: Close the file descriptor to the directory.
+
+Here's how the very first scan of a repository looks like in gitstatusd:
+
+
+
+(Some glibc functions are mislabel on this profile. `explicit_bzero` and `__nss_passwd_lookup` are
+in reality `strcmp` and `memcmp`.)
+
+This is a superset of the previous -- hot -- profile, with an extra `syscall` and string sorting for
+directory listing. gitstatusd uses `getdents64` Linux system call directly, bypassing the glibc
+wrapper that libgit2 uses. This is 23% faster. The details of this optimization can be found in a
+[separate document](docs/listdir.md).
+
+### Multithreading
+
+The diffing algorithm in gitstatusd was designed from the ground up with the intention of using it
+concurrently from multiple threads. With a fast SSD, `status` is CPU bound, so taking advantage of
+all available CPU cores is an obvious way to yield results faster.
+
+gitstatusd exhibits almost perfect scaling from multithreading. Engaging all cores allows it to
+produce results 12.4 times faster than in single-threaded execution. This is on Intel i9-7900X with
+10 cores (20 with hyperthreading) with single-core frequency of 4.3GHz and all-core frequency of
+4.0GHz.
+
+Note: `git status` also uses all available cores in some parts of its algorithm while `lg2` does
+everything in a single thread.
+
+### Postprocessing
+
+Once the difference between the index and the workdir is found, we have a list of _candidates_ --
+files that may be unstaged or untracked. To make the final judgement, these files need to be checked
+against `.gitignore` rules and a few other things.
+
+gitstatusd uses [patched libgit2](https://github.com/romkatv/libgit2) for this step. This fork
+adds several optimizations that make libgit2 faster. The patched libgit2 performs more than twice
+as fast in the benchmark as the original even without changes in the user code (that is, in the
+code that uses the libgit2 APIs). The fork also adds several API extensions, most notable of which
+is the support for multi-threaded scans. If `lg2 status` is modified to take advantage of these
+extensions, it outperforms the original libgit2 by a factor of 18. Lastly, the fork fixes a score of
+bugs, most of which become apparent only when using libgit2 from multiple threads.
+
+_WARNING: Changes to libgit2 are extensive but the testing they underwent isn't. It is
+**not recommended** to use the patched libgit2 in production._
+
+## Requirements
+
+* To compile: binutils, cmake, gcc, g++, git and GNU make.
+* To run: Linux, macOS, FreeBSD, Android, WSL, Cygwin or MSYS2.
+
+## Compiling
+
+There are prebuilt `gitstatusd` binaries in [releases](
+ https://github.com/romkatv/gitstatus/releases). When using the official shell bindings
+provided by gitstatus, the right binary for your architecture gets downloaded automatically.
+
+If prebuilt binaries don't work for you, you'll need to get your hands dirty.
+
+### Compiling for personal use
+
+```zsh
+git clone --depth=1 https://github.com/romkatv/gitstatus.git
+cd gitstatus
+./build -w -s -d docker
+```
+
+Users in China can use the official mirror on gitee.com for faster download.
+ไธญๅฝๅคง้็จๆทๅฏไปฅไฝฟ็จ gitee.com ไธ็ๅฎๆน้ๅๅ ้ไธ่ฝฝ.
+
+```zsh
+git clone --depth=1 https://gitee.com/romkatv/gitstatus.git
+cd gitstatus
+./build -w -s -d docker
+```
+
+- If it says that `-d docker` is not supported on your OS, remove this flag.
+- If it says that `-s` is not supported on your OS, remove this flag.
+- If it tell you to install docker but you cannot or don't want to, remove `-d docker`.
+- If it says that some command is missing, install it.
+
+If everything goes well, the newly built binary will appear in `./usrbin`. It'll be picked up
+by shell bindings automatically.
+
+When you update shell bindings, they may refuse to work with the binary you've built earlier. In
+this case you'll need to rebuild.
+
+If you are using gitstatus through [Powerlevel10k](https://github.com/romkatv/powerlevel10k), the
+instructions are the same except that you don't need to clone gitstatus. Instead, change your
+current directory to `/path/to/powerlevel10k/gitstatus` (`/path/to/powerlevel10k` is the directory
+where you've installed Powerlevel10k) and run `./build -w -s -d docker` from there as described
+above.
+
+### Compiling for distribution
+
+It's currently neither easy nor recommended to package and distribute gitstatus. There are no
+instructions you can follow that would allow you to easily update your package when new versions of
+gitstatus are released. This may change in the future but not soon.
+
+## License
+
+GNU General Public License v3.0. See [LICENSE](LICENSE). Contributions are covered by the same
+license.
diff --git a/gitstatus/bin/gitstatusd-android-aarch64 b/gitstatus/bin/gitstatusd-android-aarch64
deleted file mode 100755
index 24ff2854..00000000
Binary files a/gitstatus/bin/gitstatusd-android-aarch64 and /dev/null differ
diff --git a/gitstatus/bin/gitstatusd-cygwin_nt-10.0-x86_64 b/gitstatus/bin/gitstatusd-cygwin_nt-10.0-x86_64
deleted file mode 100755
index 81ad0c61..00000000
Binary files a/gitstatus/bin/gitstatusd-cygwin_nt-10.0-x86_64 and /dev/null differ
diff --git a/gitstatus/bin/gitstatusd-cygwin_nt-6.1-x86_64 b/gitstatus/bin/gitstatusd-cygwin_nt-6.1-x86_64
deleted file mode 120000
index c0d9fb61..00000000
--- a/gitstatus/bin/gitstatusd-cygwin_nt-6.1-x86_64
+++ /dev/null
@@ -1 +0,0 @@
-gitstatusd-cygwin_nt-10.0-x86_64
\ No newline at end of file
diff --git a/gitstatus/bin/gitstatusd-cygwin_nt-6.3-x86_64 b/gitstatus/bin/gitstatusd-cygwin_nt-6.3-x86_64
deleted file mode 120000
index c0d9fb61..00000000
--- a/gitstatus/bin/gitstatusd-cygwin_nt-6.3-x86_64
+++ /dev/null
@@ -1 +0,0 @@
-gitstatusd-cygwin_nt-10.0-x86_64
\ No newline at end of file
diff --git a/gitstatus/bin/gitstatusd-darwin-x86_64 b/gitstatus/bin/gitstatusd-darwin-x86_64
deleted file mode 100755
index 8c6ede54..00000000
Binary files a/gitstatus/bin/gitstatusd-darwin-x86_64 and /dev/null differ
diff --git a/gitstatus/bin/gitstatusd-freebsd-amd64 b/gitstatus/bin/gitstatusd-freebsd-amd64
deleted file mode 100755
index b08e2df9..00000000
Binary files a/gitstatus/bin/gitstatusd-freebsd-amd64 and /dev/null differ
diff --git a/gitstatus/bin/gitstatusd-freebsd-x86_64 b/gitstatus/bin/gitstatusd-freebsd-x86_64
deleted file mode 120000
index ce481223..00000000
--- a/gitstatus/bin/gitstatusd-freebsd-x86_64
+++ /dev/null
@@ -1 +0,0 @@
-gitstatusd-freebsd-amd64
\ No newline at end of file
diff --git a/gitstatus/bin/gitstatusd-linux-aarch64 b/gitstatus/bin/gitstatusd-linux-aarch64
deleted file mode 100755
index 417cf52a..00000000
Binary files a/gitstatus/bin/gitstatusd-linux-aarch64 and /dev/null differ
diff --git a/gitstatus/bin/gitstatusd-linux-armv6l b/gitstatus/bin/gitstatusd-linux-armv6l
deleted file mode 120000
index 1990676d..00000000
--- a/gitstatus/bin/gitstatusd-linux-armv6l
+++ /dev/null
@@ -1 +0,0 @@
-gitstatusd-linux-armv7l
\ No newline at end of file
diff --git a/gitstatus/bin/gitstatusd-linux-armv7l b/gitstatus/bin/gitstatusd-linux-armv7l
deleted file mode 100755
index 18800d0d..00000000
Binary files a/gitstatus/bin/gitstatusd-linux-armv7l and /dev/null differ
diff --git a/gitstatus/bin/gitstatusd-linux-x86_64 b/gitstatus/bin/gitstatusd-linux-x86_64
deleted file mode 100755
index 7a812bd6..00000000
Binary files a/gitstatus/bin/gitstatusd-linux-x86_64 and /dev/null differ
diff --git a/gitstatus/bin/gitstatusd-linux-x86_64-static b/gitstatus/bin/gitstatusd-linux-x86_64-static
deleted file mode 100755
index 96486656..00000000
Binary files a/gitstatus/bin/gitstatusd-linux-x86_64-static and /dev/null differ
diff --git a/gitstatus/bin/gitstatusd-msys_nt-10.0-x86_64 b/gitstatus/bin/gitstatusd-msys_nt-10.0-x86_64
deleted file mode 100755
index 0241c83c..00000000
Binary files a/gitstatus/bin/gitstatusd-msys_nt-10.0-x86_64 and /dev/null differ
diff --git a/gitstatus/build b/gitstatus/build
new file mode 100755
index 00000000..ea96a25a
--- /dev/null
+++ b/gitstatus/build
@@ -0,0 +1,667 @@
+#!/bin/sh
+#
+# Type `build -h` for help and see https://github.com/romkatv/gitstatus
+# for full documentation.
+
+set -ue
+
+if [ -n "${ZSH_VERSION:-}" ]; then
+ emulate sh -o err_exit -o no_unset
+fi
+
+export LC_ALL=C
+
+if [ -z "${ZSH_VERSION-}" ] && command -v zsh >/dev/null 2>&1; then
+ # Avoid bash 3.*.
+ case "${BASH_VERSION-}" in
+ [0-3].*) exec zsh "$0" "$@";;
+ esac
+fi
+
+# Avoid ksh: https://github.com/romkatv/gitstatus/issues/282.
+if [ -n "${KSH_VERSION-}" ]; then
+ if [ -z "${ZSH_VERSION-}" ] && command -v zsh >/dev/null 2>&1; then
+ exec zsh "$0" "$@"
+ elif [ -z "${BASH_VERSION-}" ] && command -v bash >/dev/null 2>&1 &&
+ bash_version="$(bash --version 2>&1)"; then
+ case "$bash_version" in
+ *version\ [4-9]*|*version\ [1-9][0-9]*) exec bash "$0" "$@";;
+ esac
+ fi
+fi
+
+usage="$(command cat <<\END
+Usage: build [-m ARCH] [-c CPU] [-d CMD] [-i IMAGE] [-s] [-w]
+
+Options:
+
+ -m ARCH `uname -m` from the target machine; defaults to `uname -m`
+ from the local machine
+ -c CPU generate machine instructions for CPU of this type; this
+ value gets passed as `-march` (or `-mcpu` for ppc64le) to gcc;
+ inferred from ARCH if not set explicitly
+ -d CMD build in a Docker container and use CMD as the `docker`
+ command; e.g., `-d docker` or `-d podman`
+ -i IMAGE build in this Docker image; inferred from ARCH if not set
+ explicitly
+ -s install whatever software is necessary for build to
+ succeed; on some operating systems this option is not
+ supported; on others it can have partial effect
+ -w automatically download tarballs for dependencies if they
+ do not already exist in ./deps; dependencies are described
+ in ./build.info
+END
+)"
+
+build="$(command cat <<\END
+outdir="$(command pwd)"
+
+if command -v mktemp >/dev/null 2>&1; then
+ workdir="$(command mktemp -d "${TMPDIR:-/tmp}"/gitstatus-build.XXXXXXXXXX)"
+else
+ workdir="${TMPDIR:-/tmp}/gitstatus-build.tmp.$$"
+ command mkdir -- "$workdir"
+fi
+
+cd -- "$workdir"
+workdir="$(command pwd)"
+
+narg() { echo $#; }
+
+if [ "$(narg $workdir)" != 1 -o -z "${workdir##*:*}" -o -z "${workdir##*=*}" ]; then
+ >&2 echo "[error] cannot build in this directory: $workdir"
+ exit 1
+fi
+
+appname=gitstatusd
+libgit2_tmp="$outdir"/deps/"$appname".libgit2.tmp
+
+cleanup() {
+ trap - INT QUIT TERM ILL PIPE
+ cd /
+ if ! command rm -rf -- "$workdir" "$outdir"/usrbin/"$appname".tmp "$libgit2_tmp"; then
+ command sleep 5
+ command rm -rf -- "$workdir" "$outdir"/usrbin/"$appname".tmp "$libgit2_tmp"
+ fi
+}
+trap cleanup INT QUIT TERM ILL PIPE
+
+if [ -n "$gitstatus_install_tools" ]; then
+ case "$gitstatus_kernel" in
+ linux)
+ if command -v apk >/dev/null 2>&1; then
+ command apk update
+ command apk add binutils cmake gcc g++ git make musl-dev perl-utils
+ elif command -v apt-get >/dev/null 2>&1; then
+ apt-get update
+ apt-get install -y binutils cmake gcc g++ make wget
+ else
+ >&2 echo "[error] -s is not supported on this system"
+ exit 1
+ fi
+ ;;
+ freebsd|dragonfly)
+ command pkg install -y cmake gmake binutils git perl5 wget
+ ;;
+ openbsd)
+ command pkg_add cmake gmake gcc g++ git wget
+ ;;
+ netbsd)
+ command pkgin -y install cmake gmake binutils git
+ ;;
+ darwin)
+ if ! command -v make >/dev/null 2>&1 || ! command -v gcc >/dev/null 2>&1; then
+ >&2 echo "[error] please run 'xcode-select --install' and retry"
+ exit 1
+ fi
+ if command -v port >/dev/null 2>&1; then
+ sudo port -N install libiconv cmake wget
+ elif command -v brew >/dev/null 2>&1; then
+ for formula in libiconv cmake git wget; do
+ if command brew ls --version "$formula" &>/dev/null; then
+ command brew upgrade "$formula"
+ else
+ command brew install "$formula"
+ fi
+ done
+ else
+ >&2 echo "[error] please install MacPorts or Homebrew and retry"
+ exit 1
+ fi
+ ;;
+ msys*|mingw*)
+ command pacman -Syu --noconfirm
+ command pacman -S --needed --noconfirm binutils cmake gcc git make perl
+ ;;
+ *)
+ >&2 echo "[internal error] unhandled kernel: $gitstatus_kernel"
+ exit 1
+ ;;
+ esac
+fi
+
+cpus="$(command getconf _NPROCESSORS_ONLN 2>/dev/null)" ||
+ cpus="$(command sysctl -n hw.ncpu 2>/dev/null)" ||
+ cpus=8
+
+case "$gitstatus_cpu" in
+ powerpc64|powerpc64le)
+ archflag="-mcpu"
+ ;;
+ *)
+ archflag="-march"
+ ;;
+esac
+
+case "$gitstatus_arch" in
+ e2k)
+ nopltflag=""
+ ;;
+ *)
+ nopltflag="-fno-plt"
+ ;;
+esac
+
+cflags="$archflag=$gitstatus_cpu $nopltflag -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -fpie"
+ldflags=
+static_pie=
+
+if [ -z "${CC-}" ]; then
+ case "$gitstatus_kernel" in
+ freebsd) export CC=clang;;
+ *) export CC=cc;;
+ esac
+fi
+
+printf 'int main() {}\n' >"$workdir"/cc-test.c
+if 2>/dev/null "$CC" \
+ -ffile-prefix-map=x=y \
+ -Werror \
+ -c "$workdir"/cc-test.c \
+ -o "$workdir"/cc-test.o; then
+ cflags="$cflags -ffile-prefix-map=$workdir/="
+fi
+
+command rm -f -- "$workdir"/cc-test "$workdir"/cc-test.o
+if 2>/dev/null "$CC" \
+ -fstack-clash-protection \
+ -Werror \
+ -c "$workdir"/cc-test.c \
+ -o "$workdir"/cc-test.o; then
+ cflags="$cflags -fstack-clash-protection"
+fi
+
+command rm -f -- "$workdir"/cc-test "$workdir"/cc-test.o
+if 2>/dev/null "$CC" \
+ -fcf-protection \
+ -Werror \
+ -c "$workdir"/cc-test.c \
+ -o "$workdir"/cc-test.o; then
+ cflags="$cflags -fcf-protection"
+fi
+
+command rm -f -- "$workdir"/cc-test "$workdir"/cc-test.o
+if 2>/dev/null "$CC" \
+ -Wl,-O1,--sort-common,--as-needed,-z,relro,-z,now \
+ -Werror \
+ "$workdir"/cc-test.c \
+ -o "$workdir"/cc-test; then
+ ldflags="$ldflags -Wl,-O1,--sort-common,--as-needed,-z,relro,-z,now"
+fi
+
+command rm -f -- "$workdir"/cc-test "$workdir"/cc-test.o
+if 2>/dev/null "$CC" \
+ -fpie -static-pie \
+ -Werror \
+ "$workdir"/cc-test.c \
+ -o "$workdir"/cc-test; then
+ static_pie='-static-pie'
+fi
+
+if [ "$gitstatus_cpu" = x86-64 ]; then
+ cflags="$cflags -mtune=generic"
+fi
+
+libgit2_cmake_flags=
+libgit2_cflags="${CFLAGS-} $cflags -O3 -DNDEBUG"
+
+gitstatus_cxx=g++
+gitstatus_cxxflags="${CXXFLAGS-} $cflags -I${workdir}/libgit2/include -DGITSTATUS_ZERO_NSEC -D_GNU_SOURCE -D_GLIBCXX_ASSERTIONS"
+gitstatus_ldflags="${LDFLAGS-} $ldflags -L${workdir}/libgit2/build"
+gitstatus_ldlibs=
+gitstatus_make=make
+
+case "$gitstatus_kernel" in
+ linux)
+ gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
+ libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
+ ;;
+ freebsd)
+ gitstatus_cxx=clang++
+ gitstatus_make=gmake
+ gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
+ libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
+ ;;
+ dragonfly)
+ gitstatus_cxx=clang++12
+ gitstatus_make=gmake
+ gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
+ libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
+ ;;
+ openbsd)
+ gitstatus_cxx=eg++
+ gitstatus_make=gmake
+ gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
+ libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
+ ;;
+ netbsd)
+ gitstatus_make=gmake
+ gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
+ libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
+ ;;
+ darwin)
+ command mkdir -- "$workdir"/lib
+ if [ -e /opt/local/lib/libiconv.a ]; then
+ command ln -s -- /opt/local/lib/libiconv.a "$workdir"/lib
+ libgit2_cflags="$libgit2_cflags -I/opt/local/include"
+ gitstatus_cxxflags="$gitstatus_cxxflags -I/opt/local/include"
+ else
+ brew_prefix="$(command brew --prefix)"
+ command ln -s -- "$brew_prefix"/opt/libiconv/lib/libiconv.a "$workdir"/lib
+ libgit2_cflags="$libgit2_cflags -I"$brew_prefix"/opt/libiconv/include"
+ gitstatus_cxxflags="$gitstatus_cxxflags -I"$brew_prefix"/opt/libiconv/include"
+ fi
+ libgit2_cmake_flags="$libgit2_cmake_flags -DUSE_ICONV=ON"
+ gitstatus_ldlibs="$gitstatus_ldlibs -liconv"
+ gitstatus_ldflags="$gitstatus_ldflags -L${workdir}/lib"
+ libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=OFF"
+ ;;
+ msys*|mingw*)
+ gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
+ libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
+ ;;
+ cygwin*)
+ gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
+ libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
+ ;;
+ *)
+ >&2 echo "[internal error] unhandled kernel: $gitstatus_kernel"
+ exit 1
+ ;;
+esac
+
+for cmd in cat cmake git ld ln mkdir rm strip tar "$gitstatus_make"; do
+ if ! command -v "$cmd" >/dev/null 2>&1; then
+ if [ -n "$gitstatus_install_tools" ]; then
+ >&2 echo "[internal error] $cmd not found"
+ exit 1
+ else
+ >&2 echo "[error] command not found: $cmd"
+ exit 1
+ fi
+ fi
+done
+
+. "$outdir"/build.info
+if [ -z "${libgit2_version:-}" ]; then
+ >&2 echo "[internal error] libgit2_version not set"
+ exit 1
+fi
+if [ -z "${libgit2_sha256:-}" ]; then
+ >&2 echo "[internal error] libgit2_sha256 not set"
+ exit 1
+fi
+libgit2_tarball="$outdir"/deps/libgit2-"$libgit2_version".tar.gz
+if [ ! -e "$libgit2_tarball" ]; then
+ if [ -n "$gitstatus_download_deps" ]; then
+ if ! command -v wget >/dev/null 2>&1; then
+ if [ -n "$gitstatus_install_tools" ]; then
+ >&2 echo "[internal error] wget not found"
+ exit 1
+ else
+ >&2 echo "[error] command not found: wget"
+ exit 1
+ fi
+ fi
+ libgit2_url=https://github.com/romkatv/libgit2/archive/"$libgit2_version".tar.gz
+ if ! >"$libgit2_tmp" command wget --no-config -qO- -- "$libgit2_url" &&
+ ! >"$libgit2_tmp" command wget -qO- -- "$libgit2_url"; then
+ set -x
+ >&2 command which wget
+ >&2 command ls -lAd -- "$(command which wget)"
+ >&2 command ls -lAd -- "$outdir"
+ >&2 command ls -lA -- "$outdir"
+ >&2 command ls -lAd -- "$outdir"/deps
+ >&2 command ls -lA -- "$outdir"/deps
+ set +x
+ exit 1
+ fi
+ command mv -f -- "$libgit2_tmp" "$libgit2_tarball"
+ else
+ >&2 echo "[error] file not found: deps/libgit2-"$libgit2_version".tar.gz"
+ exit 1
+ fi
+fi
+
+libgit2_actual_sha256=
+if command -v shasum >/dev/null 2>/dev/null; then
+ libgit2_actual_sha256="$(command shasum -b -a 256 -- "$libgit2_tarball")"
+ libgit2_actual_sha256="${libgit2_actual_sha256%% *}"
+elif command -v sha256sum >/dev/null 2>/dev/null; then
+ libgit2_actual_sha256="$(command sha256sum -b -- "$libgit2_tarball")"
+ libgit2_actual_sha256="${libgit2_actual_sha256%% *}"
+elif command -v sha256 >/dev/null 2>/dev/null; then
+ libgit2_actual_sha256="$(command sha256 -- "$libgit2_tarball" &2 echo "[error] command not found: shasum or sha256sum"
+ exit 1
+fi
+
+if [ "$libgit2_actual_sha256" != "$libgit2_sha256" ]; then
+ >&2 echo "[error] sha256 mismatch"
+ >&2 echo ""
+ >&2 echo " file : deps/libgit2-$libgit2_version.tar.gz"
+ >&2 echo " expected: $libgit2_sha256"
+ >&2 echo " actual : $libgit2_actual_sha256"
+ exit 1
+fi
+
+cd -- "$workdir"
+command tar -xzf "$libgit2_tarball"
+command mv -- libgit2-"$libgit2_version" libgit2
+command mkdir libgit2/build
+cd libgit2/build
+
+CFLAGS="$libgit2_cflags" command cmake \
+ -DCMAKE_BUILD_TYPE=None \
+ -DZERO_NSEC=ON \
+ -DTHREADSAFE=ON \
+ -DUSE_BUNDLED_ZLIB=ON \
+ -DREGEX_BACKEND=builtin \
+ -DUSE_HTTP_PARSER=builtin \
+ -DUSE_SSH=OFF \
+ -DUSE_HTTPS=OFF \
+ -DBUILD_CLAR=OFF \
+ -DUSE_GSSAPI=OFF \
+ -DUSE_NTLMCLIENT=OFF \
+ -DBUILD_SHARED_LIBS=OFF \
+ -G "Unix Makefiles" \
+ $libgit2_cmake_flags \
+ ..
+command make -j "$cpus" VERBOSE=1
+
+APPNAME="$appname".tmp \
+ OBJDIR="$workdir"/gitstatus \
+ CXX="${CXX:-$gitstatus_cxx}" \
+ CXXFLAGS="$gitstatus_cxxflags" \
+ LDFLAGS="$gitstatus_ldflags" \
+ LDLIBS="$gitstatus_ldlibs" \
+ command "$gitstatus_make" -C "$outdir" -j "$cpus"
+
+app="$outdir"/usrbin/"$appname"
+
+command strip "$app".tmp
+
+command mkdir -- "$workdir"/repo
+printf '[init]\n defaultBranch = master\n' >"$workdir"/.gitconfig
+(
+ cd -- "$workdir"/repo
+ GIT_CONFIG_NOSYSTEM=1 HOME="$workdir" command git init
+ GIT_CONFIG_NOSYSTEM=1 HOME="$workdir" command git config user.name "Your Name"
+ GIT_CONFIG_NOSYSTEM=1 HOME="$workdir" command git config user.email "you@example.com"
+ GIT_CONFIG_NOSYSTEM=1 HOME="$workdir" command git commit \
+ --allow-empty --allow-empty-message --no-gpg-sign -m ''
+)
+
+resp="$(printf "hello\037$workdir/repo\036" | "$app".tmp)"
+case "$resp" in
+ hello*1*/repo*master*);;
+ *)
+ >&2 echo 'error: invalid gitstatusd response for a git repo'
+ exit 1
+ ;;
+esac
+
+resp="$(printf 'hello\037\036' | "$app".tmp)"
+case "$resp" in
+ hello*0*);;
+ *)
+ >&2 echo 'error: invalid gitstatusd response for a non-repo'
+ exit 1
+ ;;
+esac
+
+command mv -f -- "$app".tmp "$app"
+
+cleanup
+
+command cat >&2 <<-END
+ -------------------------------------------------
+ SUCCESS: created usrbin/$appname
+ END
+END
+)"
+
+docker_image=
+docker_cmd=
+
+gitstatus_arch=
+gitstatus_cpu=
+gitstatus_install_tools=
+gitstatus_download_deps=
+
+while getopts ':m:c:i:d:swh' opt "$@"; do
+ case "$opt" in
+ h)
+ printf '%s\n' "$usage"
+ exit
+ ;;
+ m)
+ if [ -n "$gitstatus_arch" ]; then
+ >&2 echo "[error] duplicate option: -$opt"
+ exit 1
+ fi
+ if [ -z "$OPTARG" ]; then
+ >&2 echo "[error] incorrect value of -$opt: $OPTARG"
+ exit 1
+ fi
+ gitstatus_arch="$OPTARG"
+ ;;
+ c)
+ if [ -n "$gitstatus_cpu" ]; then
+ >&2 echo "[error] duplicate option: -$opt"
+ exit 1
+ fi
+ if [ -z "$OPTARG" ]; then
+ >&2 echo "[error] incorrect value of -$opt: $OPTARG"
+ exit 1
+ fi
+ gitstatus_cpu="$OPTARG"
+ ;;
+ i)
+ if [ -n "$docker_image" ]; then
+ >&2 echo "[error] duplicate option: -$opt"
+ exit 1
+ fi
+ if [ -z "$OPTARG" ]; then
+ >&2 echo "[error] incorrect value of -$opt: $OPTARG"
+ exit 1
+ fi
+ docker_image="$OPTARG"
+ ;;
+ d)
+ if [ -n "$docker_cmd" ]; then
+ >&2 echo "[error] duplicate option: -$opt"
+ exit 1
+ fi
+ if [ -z "$OPTARG" ]; then
+ >&2 echo "[error] incorrect value of -$opt: $OPTARG"
+ exit 1
+ fi
+ docker_cmd="$OPTARG"
+ ;;
+ s)
+ if [ -n "$gitstatus_install_tools" ]; then
+ >&2 echo "[error] duplicate option: -$opt"
+ exit 1
+ fi
+ gitstatus_install_tools=1
+ ;;
+ w)
+ if [ -n "$gitstatus_download_deps" ]; then
+ >&2 echo "[error] duplicate option: -$opt"
+ exit 1
+ fi
+ gitstatus_download_deps=1
+ ;;
+ \?) >&2 echo "[error] invalid option: -$OPTARG" ; exit 1;;
+ :) >&2 echo "[error] missing required argument: -$OPTARG"; exit 1;;
+ *) >&2 echo "[internal error] unhandled option: -$opt" ; exit 1;;
+ esac
+done
+
+if [ "$OPTIND" -le $# ]; then
+ >&2 echo "[error] unexpected positional argument"
+ exit 1
+fi
+
+if [ -n "$docker_image" -a -z "$docker_cmd" ]; then
+ >&2 echo "[error] cannot use -i without -d"
+ exit 1
+fi
+
+if [ -z "$gitstatus_arch" ]; then
+ gitstatus_arch="$(uname -m)"
+ gitstatus_arch="$(printf '%s' "$gitstatus_arch" | tr '[A-Z]' '[a-z]')"
+fi
+
+if [ -z "$gitstatus_cpu" ]; then
+ case "$gitstatus_arch" in
+ armel) gitstatus_cpu=armv5;;
+ armv6l|armhf) gitstatus_cpu=armv6;;
+ armv7l) gitstatus_cpu=armv7;;
+ arm64|aarch64) gitstatus_cpu=armv8-a;;
+ ppc64|ppc64le) gitstatus_cpu=powerpc64le;;
+ riscv64) gitstatus_cpu=rv64imafdc;;
+ loongarch64) gitstatus_cpu=loongarch64;;
+ x86_64|amd64) gitstatus_cpu=x86-64;;
+ x86) gitstatus_cpu=i586;;
+ s390x) gitstatus_cpu=z900;;
+ e2k) gitstatus_cpu=native;;
+ i386|i586|i686) gitstatus_cpu="$gitstatus_arch";;
+ *)
+ >&2 echo '[error] unable to infer target CPU architecture'
+ >&2 echo 'Please specify explicitly with `-c CPU`.'
+ exit 1
+ ;;
+ esac
+fi
+
+gitstatus_kernel="$(uname -s)"
+gitstatus_kernel="$(printf '%s' "$gitstatus_kernel" | tr '[A-Z]' '[a-z]')"
+
+case "$gitstatus_kernel" in
+ linux)
+ if [ -n "$docker_cmd" ]; then
+ if [ -z "${docker_cmd##*/*}" ]; then
+ if [ ! -x "$docker_cmd" ]; then
+ >&2 echo "[error] not an executable file: $docker_cmd"
+ exit 1
+ fi
+ else
+ if ! command -v "$docker_cmd" >/dev/null 2>&1; then
+ >&2 echo "[error] command not found: $docker_cmd"
+ exit 1
+ fi
+ fi
+ if [ -z "$docker_image" ]; then
+ case "$gitstatus_arch" in
+ x86_64) docker_image=alpine:3.11.6;;
+ x86|i386|i586|i686) docker_image=i386/alpine:3.11.6;;
+ armv6l|armhf) docker_image=arm32v6/alpine:3.11.6;;
+ armv7l) docker_image=arm32v7/alpine:3.11.6;;
+ aarch64) docker_image=arm64v8/alpine:3.11.6;;
+ ppc64|ppc64le) docker_image=ppc64le/alpine:3.11.6;;
+ s390x) docker_image=s390x/alpine:3.11.6;;
+ *)
+ >&2 echo '[error] unable to infer docker image'
+ >&2 echo 'Please specify explicitly with `-i IMAGE`.'
+ exit 1
+ ;;
+ esac
+ fi
+ fi
+ ;;
+ freebsd|openbsd|netbsd|darwin|dragonfly)
+ if [ -n "$docker_cmd" ]; then
+ >&2 echo "[error] docker (-d) is not supported on $gitstatus_kernel"
+ exit 1
+ fi
+ ;;
+ msys_nt-*|mingw32_nt-*|mingw64_nt-*|cygwin_nt-*)
+ if ! printf '%s' "$gitstatus_kernel" | grep -Eqx '[^-]+-[0-9]+\.[0-9]+(-.*)?'; then
+ >&2 echo '[error] unsupported kernel, sorry!'
+ exit 1
+ fi
+ gitstatus_kernel="$(printf '%s' "$gitstatus_kernel" | sed 's/^\([^-]*-[0-9]*\.[0-9]*\).*/\1/')"
+ if [ -n "$docker_cmd" ]; then
+ >&2 echo '[error] docker (-d) is not supported on windows'
+ exit 1
+ fi
+ if [ -n "$gitstatus_install_tools" -a -z "${gitstatus_kernel##cygwin_nt-*}" ]; then
+ >&2 echo '[error] -s is not supported on cygwin'
+ exit 1
+ fi
+ ;;
+ *)
+ >&2 echo '[error] unsupported kernel, sorry!'
+ exit 1
+ ;;
+esac
+
+dir="$(dirname -- "$0")"
+cd -- "$dir"
+dir="$(pwd)"
+
+>&2 echo "Building gitstatusd..."
+>&2 echo ""
+>&2 echo " kernel := $gitstatus_kernel"
+>&2 echo " arch := $gitstatus_arch"
+>&2 echo " cpu := $gitstatus_cpu"
+[ -z "$docker_cmd" ] || >&2 echo " docker command := $docker_cmd"
+[ -z "$docker_image" ] || >&2 echo " docker image := $docker_image"
+if [ -n "$gitstatus_install_tools" ]; then
+ >&2 echo " install tools := yes"
+else
+ >&2 echo " install tools := no"
+fi
+if [ -n "$gitstatus_download_deps" ]; then
+ >&2 echo " download deps := yes"
+else
+ >&2 echo " download deps := no"
+fi
+
+if [ -n "$docker_cmd" ]; then
+ "$docker_cmd" run \
+ -e docker_cmd="$docker_cmd" \
+ -e docker_image="$docker_image" \
+ -e gitstatus_kernel="$gitstatus_kernel" \
+ -e gitstatus_arch="$gitstatus_arch" \
+ -e gitstatus_cpu="$gitstatus_cpu" \
+ -e gitstatus_install_tools="$gitstatus_install_tools" \
+ -e gitstatus_download_deps="$gitstatus_download_deps" \
+ -v "$dir":/out \
+ -w /out \
+ --rm \
+ -- "$docker_image" /bin/sh -uexc "$build"
+else
+ eval "$build"
+fi
diff --git a/gitstatus/build.info b/gitstatus/build.info
new file mode 100644
index 00000000..9bc9411d
--- /dev/null
+++ b/gitstatus/build.info
@@ -0,0 +1,22 @@
+# This value gets embedded in gitstatusd at build time. It is
+# read by ./Makefile. `gitstatusd --version` reports it back.
+#
+# This value is also read by shell bindings (indirectly, through
+# ./install) when using GITSTATUS_DAEMON or usrbin/gitstatusd.
+gitstatus_version="v1.5.5"
+
+# libgit2 is a build time dependency of gitstatusd. The values of
+# libgit2_version and libgit2_sha256 are read by ./build.
+#
+# If ./deps/libgit2-${libgit2_version}.tar.gz doesn't exist, build
+# downloads it from the following location:
+#
+# https://github.com/romkatv/libgit2/archive/${libgit2_version}.tar.gz
+#
+# Once downloaded, the tarball is stored at the path indicated
+# above so that repeated builds don't consume network bandwidth.
+#
+# If sha256 of ./deps/libgit2-${libgit2_version}.tar.gz doesn't match,
+# build gets aborted.
+libgit2_version="tag-2ecf33948a4df9ef45a66c68b8ef24a5e60eaac6"
+libgit2_sha256="4ce11d71ee576dbbc410b9fa33a9642809cc1fa687b315f7c23eeb825b251e93"
diff --git a/gitstatus/deps/.gitkeep b/gitstatus/deps/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/gitstatus/docs/listdir.md b/gitstatus/docs/listdir.md
new file mode 100644
index 00000000..0939cc18
--- /dev/null
+++ b/gitstatus/docs/listdir.md
@@ -0,0 +1,330 @@
+# Fast directory listing
+
+In order to find untracked files in a git repository, [gitstatusd](../README.md) needs to list the
+contents of every directory. gitstatusd does it 27% faster than a reasonable implementation that a
+seasoned C/C++ practitioner might write. This document explains the optimizations that went into it.
+As directory listing is a common operation, many other projects can benefit from applying these
+optimizations.
+
+## v1
+
+Given a path to a directory, `ListDir()` must produce the list of files in that directory. Moreover,
+the list must be sorted lexicographically to enable fast comparison with Git index.
+
+The following C++ implementation gets the job done. For simplicity, it returns an empty list on
+error.
+
+```c++
+vector ListDir(const char* dirname) {
+ vector entries;
+ if (DIR* dir = opendir(dirname)) {
+ while (struct dirent* ent = (errno = 0, readdir(dir))) {
+ if (!Dots(ent->d_name)) entries.push_back(ent->d_name);
+ }
+ if (errno) entries.clear();
+ sort(entries.begin(), entries.end());
+ closedir(dir);
+ }
+ return entries;
+}
+```
+
+Every directory has entries `"."` and `".."`, which we aren't interested in. We filter them out with
+a helper function `Dots()`.
+
+```c++
+bool Dots(const char* s) { return s[0] == '.' && (!s[1] || (s[1] == '.' && !s[2])); }
+```
+
+To check how fast `ListDir()` performs, we can run it many times on a typical directory. One million
+runs on a directory with 32 files with 16-character names takes 12.7 seconds.
+
+## v2
+
+Experienced C++ practitioners will scoff at our implementation of `ListDir()`. If it's meant to be
+efficient, returning `vector` is an unaffordable convenience. To avoid heap allocations we
+can use a simple arena that will allow us to reuse memory between different `ListDir()` calls.
+
+(Changed and added lines are marked with comments.)
+
+```c++
+void ListDir(const char* dirname, string& arena, vector& entries) { // +
+ entries.clear(); // +
+ if (DIR* dir = opendir(dirname)) {
+ arena.clear(); // +
+ while (struct dirent* ent = (errno = 0, readdir(dir))) {
+ if (!Dots(ent->d_name)) {
+ entries.push_back(reinterpret_cast(arena.size())); // +
+ arena.append(ent->d_name, strlen(ent->d_name) + 1); // +
+ }
+ }
+ if (errno) entries.clear();
+ for (char*& p : entries) p = &arena[reinterpret_cast(p)]; // +
+ sort(entries.begin(), entries.end(), // +
+ [](const char* a, const char* b) { return strcmp(a, b) < 0; }); // +
+ closedir(dir);
+ }
+}
+```
+
+To make performance comparison easier, we can normalize them relative to the baseline. v1 will get
+performance score of 100. A twice-as-fast alternative will be 200.
+
+| version | optimization | score |
+|---------|----------------------------|----------:|
+| v1 | baseline | 100.0 |
+| **v2** | **avoid heap allocations** | **112.7** |
+
+Avoiding heap allocations makes `ListDir()` 12.7% faster. Not bad. As an added bonus, those casts
+will fend off the occasional frontend developer who accidentally wanders into the codebase.
+
+## v3
+
+`opendir()` is an expensive call whose performance is linear in the number of subdirectories in the
+path because it needs to perform a lookup for every one of them. We can replace it with `openat()`,
+which takes a file descriptor to the parent directory and a name of the subdirectory. Just a single
+lookup, less CPU time. This optimization assumes that callers already have a descriptor to the
+parent directory, which is indeed the case for gitstatusd, and is often the case in other
+applications that traverse filesystem.
+
+```c++
+void ListDir(int parent_fd, const char* dirname, string& arena, vector& entries) { // +
+ entries.clear();
+ int dir_fd = openat(parent_fd, dirname, O_NOATIME | O_RDONLY | O_DIRECTORY | O_CLOEXEC); // +
+ if (dir_fd < 0) return; // +
+ if (DIR* dir = fdopendir(dir_fd)) {
+ arena.clear();
+ while (struct dirent* ent = (errno = 0, readdir(dir))) {
+ if (!Dots(ent->d_name)) {
+ entries.push_back(reinterpret_cast(arena.size()));
+ arena.append(ent->d_name, strlen(ent->d_name) + 1);
+ }
+ }
+ if (errno) entries.clear();
+ for (char*& p : entries) p = &arena[reinterpret_cast(p)];
+ sort(entries.begin(), entries.end(),
+ [](const char* a, const char* b) { return strcmp(a, b) < 0; });
+ closedir(dir);
+ } else { // +
+ close(dir_fd); // +
+ } // +
+}
+```
+
+This is worth about 3.5% in speed.
+
+| version | optimization | score |
+|---------|--------------------------------------|----------:|
+| v1 | baseline | 100.0 |
+| v2 | avoid heap allocations | 112.7 |
+| **v3** | **open directories with `openat()`** | **116.2** |
+
+## v4
+
+Copying file names to the arena isn't free but it doesn't seem like we can avoid it. Poking around
+we can see that the POSIX API we are using is implemented on Linux on top of `getdents64` system
+call. Its documentation isn't very encouraging:
+
+```text
+These are not the interfaces you are interested in. Look at
+readdir(3) for the POSIX-conforming C library interface. This page
+documents the bare kernel system call interfaces.
+
+Note: There are no glibc wrappers for these system calls.
+```
+
+Hmm... The API looks like something we can take advantage of, so let's try it anyway.
+
+First, we'll need a simple `Arena` class that can allocate 8KB blocks of memory.
+
+```c++
+class Arena {
+ public:
+ enum { kBlockSize = 8 << 10 };
+
+ char* Alloc() {
+ if (cur_ == blocks_.size()) blocks_.emplace_back(kBlockSize, 0);
+ return blocks_[cur_++].data();
+ }
+
+ void Clear() { cur_ = 0; }
+
+ private:
+ size_t cur_ = 0;
+ vector blocks_;
+};
+```
+
+Next, we need to define `struct dirent64_t` ourselves because there is no wrapper for the system
+call we are about to use.
+
+```c++
+struct dirent64_t {
+ ino64_t d_ino;
+ off64_t d_off;
+ unsigned short d_reclen;
+ unsigned char d_type;
+ char d_name[];
+};
+```
+
+Finally we can get to the implementation of `ListDir()`.
+
+```c++
+void ListDir(int parent_fd, Arena& arena, vector& entries) { // +
+ entries.clear();
+ int dir_fd = openat(parent_fd, dirname, O_NOATIME | O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ if (dir_fd < 0) return;
+ arena.Clear(); // +
+ while (true) { // +
+ char* buf = arena.Alloc(); // +
+ int n = syscall(SYS_getdents64, dir_fd, buf, Arena::kBlockSize); // +
+ if (n <= 0) { // +
+ if (n) entries.clear(); // +
+ break; // +
+ } // +
+ for (int pos = 0; pos < n;) { // +
+ auto* ent = reinterpret_cast(buf + pos); // +
+ if (!Dots(ent->d_name)) entries.push_back(ent->d_name); // +
+ pos += ent->d_reclen; // +
+ } // +
+ } // +
+ sort(entries.begin(), entries.end(),
+ [](const char* a, const char* b) { return strcmp(a, b) < 0; });
+ close(dir_fd);
+}
+```
+
+How are we doing with this one?
+
+| version | optimization | score |
+|---------|----------------------------------|----------:|
+| v1 | baseline | 100.0 |
+| v2 | avoid heap allocations | 112.7 |
+| v3 | open directories with `openat()` | 116.2 |
+| **v4** | **call `getdents64()` directly** | **137.8** |
+
+Solid 20% speedup. Worth the trouble. Unfortunately, we now have just one `reinterpret_cast` instead
+of two, and it's not nearly as scary-looking. Hopefully with the next iteration we can get back some
+of that evil vibe of low-level code.
+
+As a bonus, every element in `entries` has `d_type` at offset -1. This can be useful to the callers
+that need to distinguish between regular files and directories (gitstatusd, in fact, needs this).
+Note how `ListDir()` implements this feature at zero cost, as a lucky accident of `dirent64_t`
+memory layout.
+
+## v5
+
+The CPU profile of `ListDir()` reveals that almost all userspace CPU time is spent in `strcmp()`.
+Digging into the source code of `std::sort()` we can see that it uses Insertion Sort for short
+collections. Our 32-element vector falls under the threshold. Insertion Sort makes `O(N^2)`
+comparisons, hence a lot of CPU time in `strcmp()`. Switching to `qsort()` or
+[Timsort](https://en.wikipedia.org/wiki/Timsort) is of no use as all good sorting algorithms fall
+back to Insertion Sort.
+
+If we cannot make fewer comparisons, perhaps we can make each of them faster? `strcmp()` compares
+characters one at a time. It cannot read ahead as it can be illegal to touch memory past the first
+null byte. But _we_ know that it's safe to read a few extra bytes past the end of `d_name` for every
+entry except the last in the buffer. And since we own the buffer, we can overallocate it so that
+reading past the end of the last entry is also safe.
+
+Combining these ideas with the fact that file names on Linux are at most 255 bytes long, we can
+invoke `getdents64()` like this:
+
+```c++
+int n = syscall(SYS_getdents64, dir_fd, buf, Arena::kBlockSize - 256);
+```
+
+And then compare entries like this:
+
+```c++
+[](const char* a, const char* b) { return memcmp(a, b, 255) < 0; }
+```
+
+This version doesn't give any speedup compared to the previous but it opens an avenue for another
+optimization. The pointers we pass to `memcmp()` aren't aligned. To be more specific, their
+numerical values are `N * 8 + 3` for some `N`. When given such a pointer, `memcmp()` will check the
+first 5 bytes one by one, and only then switch to comparing 8 bytes at a time. If we can handle the
+first 5 bytes ourselves, we can pass aligned memory to `memcmp()` and take full advantage of its
+vectorized loop.
+
+Here's the implementation:
+
+```c++
+uint64_t Read64(const void* p) { // +
+ uint64_t x; // +
+ memcpy(&x, p, sizeof(x)); // +
+ return x; // +
+} // +
+
+void ByteSwap64(void* p) { // +
+ uint64_t x = __builtin_bswap64(Read64(p)); // +
+ memcpy(p, &x, sizeof(x)); // +
+} // +
+
+void ListDir(int parent_fd, Arena& arena, vector& entries) {
+ entries.clear();
+ int dir_fd = openat(parent_fd, dirname, O_NOATIME | O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ if (dir_fd < 0) return;
+ arena.Clear();
+ while (true) {
+ char* buf = arena.Alloc();
+ int n = syscall(SYS_getdents64, dir_fd, buf, Arena::kBlockSize - 256); // +
+ if (n <= 0) {
+ if (n) entries.clear();
+ break;
+ }
+ for (int pos = 0; pos < n;) {
+ auto* ent = reinterpret_cast(buf + pos);
+ if (!Dots(ent->d_name)) {
+ ByteSwap64(ent->d_name); // +
+ entries.push_back(ent->d_name);
+ }
+ pos += ent->d_reclen;
+ }
+ }
+ sort(entries.begin(), entries.end(), [](const char* a, const char* b) {
+ uint64_t x = Read64(a); // +
+ uint64_t y = Read64(b); // +
+ return x < y || (x == y && a != b && memcmp(a + 5, b + 5, 256) < 0); // +
+ });
+ for (char* p : entries) ByteSwap64(p); // +
+ close(dir_fd);
+}
+```
+
+This is for Little Endian architecture. Big Endian doesn't need `ByteSwap64()`, so it'll be a bit
+faster.
+
+| version | optimization | score |
+|---------|----------------------------------|----------:|
+| v1 | baseline | 100.0 |
+| v2 | avoid heap allocations | 112.7 |
+| v3 | open directories with `openat()` | 116.2 |
+| v4 | call `getdents64()` directly | 137.8 |
+| **v5** | **hand-optimize `strcmp()`** | **143.3** |
+
+Fast and respectably arcane.
+
+## Conclusion
+
+Through a series of incremental improvements we've sped up directory listing by 43.3% compared to a
+naive implementation (v1) and 27.2% compared to a reasonable implementation that a seasoned C/C++
+practitioner might write (v2).
+
+However, these numbers are based on an artificial benchmark while the real judge is always the real
+code. Our goal was to speed up gitstatusd. Benchmark was just a tool. Thankfully, the different
+versions of `ListDir()` have the same comparative performance within gitstatusd as in the benchmark.
+In truth, the directory chosen for the benchmark wasn't arbitrary. It was picked by sampling
+gitstatusd when it runs on [chromium](https://github.com/chromium/chromium) git repository.
+
+The final version of `ListDir()` spends 97% of its CPU time in the kernel. If we assume that it
+makes the minimum possible number of system calls and these calls are optimal (true to the best
+of my knowledge), it puts the upper bound on possible future performance improvements at just 3%.
+There is almost nothing left in `ListDir()` to optimize.
+
+
+
+(The CPU profile was created with [gperftools](https://github.com/gperftools/gperftools) and
+rendered with [pprof](https://github.com/google/pprof)).
diff --git a/gitstatus/gitstatus.plugin.sh b/gitstatus/gitstatus.plugin.sh
new file mode 100644
index 00000000..bfe16dc2
--- /dev/null
+++ b/gitstatus/gitstatus.plugin.sh
@@ -0,0 +1,474 @@
+# Bash bindings for gitstatus.
+
+[[ $- == *i* ]] || return # non-interactive shell
+
+# Starts gitstatusd in the background. Does nothing and succeeds if gitstatusd
+# is already running.
+#
+# Usage: gitstatus_start [OPTION]...
+#
+# -t FLOAT Fail the self-check on initialization if not getting a response from
+# gitstatusd for this this many seconds. Defaults to 5.
+#
+# -s INT Report at most this many staged changes; negative value means infinity.
+# Defaults to 1.
+#
+# -u INT Report at most this many unstaged changes; negative value means infinity.
+# Defaults to 1.
+#
+# -c INT Report at most this many conflicted changes; negative value means infinity.
+# Defaults to 1.
+#
+# -d INT Report at most this many untracked files; negative value means infinity.
+# Defaults to 1.
+#
+# -m INT Report -1 unstaged, untracked and conflicted if there are more than this many
+# files in the index. Negative value means infinity. Defaults to -1.
+#
+# -e Count files within untracked directories like `git status --untracked-files`.
+#
+# -U Unless this option is specified, report zero untracked files for repositories
+# with status.showUntrackedFiles = false.
+#
+# -W Unless this option is specified, report zero untracked files for repositories
+# with bash.showUntrackedFiles = false.
+#
+# -D Unless this option is specified, report zero staged, unstaged and conflicted
+# changes for repositories with bash.showDirtyState = false.
+#
+# -r INT Close git repositories that haven't been used for this many seconds. This is
+# meant to release resources such as memory and file descriptors. The next request
+# for a repo that's been closed is much slower than for a repo that hasn't been.
+# Negative value means infinity. The default is 3600 (one hour).
+function gitstatus_start() {
+ if [[ "$BASH_VERSION" < 4 ]]; then
+ >&2 printf 'gitstatus_start: need bash version >= 4.0, found %s\n' "$BASH_VERSION"
+ >&2 printf '\n'
+ >&2 printf 'To see the version of the current shell, type:\n'
+ >&2 printf '\n'
+ >&2 printf ' \033[32mecho\033[0m \033[33m"$BASH_VERSION"\033[0m\n'
+ >&2 printf '\n'
+ >&2 printf 'The output of `\033[32mbash\033[0m --version` may be different and is not relevant.\n'
+ return 1
+ fi
+
+ unset OPTIND
+ local opt timeout=5 max_dirty=-1 ttl=3600 extra_flags=
+ local max_num_staged=1 max_num_unstaged=1 max_num_conflicted=1 max_num_untracked=1
+ while getopts "t:s:u:c:d:m:r:eUWD" opt; do
+ case "$opt" in
+ t) timeout=$OPTARG;;
+ s) max_num_staged=$OPTARG;;
+ u) max_num_unstaged=$OPTARG;;
+ c) max_num_conflicted=$OPTARG;;
+ d) max_num_untracked=$OPTARG;;
+ m) max_dirty=$OPTARG;;
+ r) ttl=$OPTARG;;
+ e) extra_flags+='--recurse-untracked-dirs ';;
+ U) extra_flags+='--ignore-status-show-untracked-files ';;
+ W) extra_flags+='--ignore-bash-show-untracked-files ';;
+ D) extra_flags+='--ignore-bash-show-dirty-state ';;
+ *) return 1;;
+ esac
+ done
+
+ (( OPTIND == $# + 1 )) || { echo "usage: gitstatus_start [OPTION]..." >&2; return 1; }
+
+ [[ -z "${GITSTATUS_DAEMON_PID:-}" ]] || return 0 # already started
+
+ if [[ "${BASH_SOURCE[0]}" == */* ]]; then
+ local gitstatus_plugin_dir="${BASH_SOURCE[0]%/*}"
+ if [[ "$gitstatus_plugin_dir" != /* ]]; then
+ gitstatus_plugin_dir="$PWD"/"$gitstatus_plugin_dir"
+ fi
+ else
+ local gitstatus_plugin_dir="$PWD"
+ fi
+
+ local tmpdir req_fifo resp_fifo culprit
+
+ function gitstatus_start_impl() {
+ local log_level="${GITSTATUS_LOG_LEVEL:-}"
+ [[ -n "$log_level" || "${GITSTATUS_ENABLE_LOGGING:-0}" != 1 ]] || log_level=INFO
+
+ local uname_sm
+ uname_sm="$(command uname -sm)" || return
+ uname_sm="${uname_sm,,}"
+ local uname_s="${uname_sm% *}"
+ local uname_m="${uname_sm#* }"
+
+ if [[ "${GITSTATUS_NUM_THREADS:-0}" -gt 0 ]]; then
+ local threads="$GITSTATUS_NUM_THREADS"
+ else
+ local cpus
+ if ! command -v sysctl &>/dev/null || [[ "$uname_s" == linux ]] ||
+ ! cpus="$(command sysctl -n hw.ncpu)"; then
+ if ! command -v getconf &>/dev/null || ! cpus="$(command getconf _NPROCESSORS_ONLN)"; then
+ cpus=8
+ fi
+ fi
+ local threads=$((cpus > 16 ? 32 : cpus > 0 ? 2 * cpus : 16))
+ fi
+
+ local daemon_args=(
+ --parent-pid="$$"
+ --num-threads="$threads"
+ --max-num-staged="$max_num_staged"
+ --max-num-unstaged="$max_num_unstaged"
+ --max-num-conflicted="$max_num_conflicted"
+ --max-num-untracked="$max_num_untracked"
+ --dirty-max-index-size="$max_dirty"
+ --repo-ttl-seconds="$ttl"
+ $extra_flags)
+
+ if [[ -n "$TMPDIR" && ( ( -d "$TMPDIR" && -w "$TMPDIR" ) || ! ( -d /tmp && -w /tmp ) ) ]]; then
+ local tmpdir=$TMPDIR
+ else
+ local tmpdir=/tmp
+ fi
+ tmpdir="$(command mktemp -d "$tmpdir"/gitstatus.bash.$$.XXXXXXXXXX)" || return
+
+ if [[ -n "$log_level" ]]; then
+ GITSTATUS_DAEMON_LOG="$tmpdir"/daemon.log
+ [[ "$log_level" == INFO ]] || daemon_args+=(--log-level="$log_level")
+ else
+ GITSTATUS_DAEMON_LOG=/dev/null
+ fi
+
+ req_fifo="$tmpdir"/req.fifo
+ resp_fifo="$tmpdir"/resp.fifo
+ command mkfifo -- "$req_fifo" "$resp_fifo" || return
+
+ {
+ (
+ trap '' INT QUIT TSTP
+ [[ "$GITSTATUS_DAEMON_LOG" == /dev/null ]] || set -x
+ builtin cd /
+
+ (
+ local fd_in fd_out
+ exec {fd_in}<"$req_fifo" {fd_out}>>"$resp_fifo" || exit
+ echo "$BASHPID" >&"$fd_out"
+
+ local _gitstatus_bash_daemon _gitstatus_bash_version _gitstatus_bash_downloaded
+
+ function _gitstatus_set_daemon() {
+ _gitstatus_bash_daemon="$1"
+ _gitstatus_bash_version="$2"
+ _gitstatus_bash_downloaded="$3"
+ }
+
+ set -- -d "$gitstatus_plugin_dir" -s "$uname_s" -m "$uname_m" \
+ -p "printf '.\036' >&$fd_out" -e "$fd_out" -- _gitstatus_set_daemon
+ [[ "${GITSTATUS_AUTO_INSTALL:-1}" -ne 0 ]] || set -- -n "$@"
+ source "$gitstatus_plugin_dir"/install || return
+ [[ -n "$_gitstatus_bash_daemon" ]] || return
+ [[ -n "$_gitstatus_bash_version" ]] || return
+ [[ "$_gitstatus_bash_downloaded" == [01] ]] || return
+
+ local sig=(TERM ILL PIPE)
+
+ if (( UID == EUID )); then
+ local home=~
+ else
+ local user
+ user="$(command id -un)" || return
+ [[ "$user" =~ ^[a-zA-Z0-9_,.-]+$ ]] || return
+ eval "local home=~$user"
+ [[ -n "$home" ]] || return
+ fi
+
+ if [[ -x "$_gitstatus_bash_daemon" ]]; then
+ HOME="$home" "$_gitstatus_bash_daemon" \
+ -G "$_gitstatus_bash_version" "${daemon_args[@]}" <&"$fd_in" >&"$fd_out" &
+ local pid=$!
+ trap "trap - ${sig[*]}; kill $pid &>/dev/null" ${sig[@]}
+ wait "$pid"
+ local ret=$?
+ trap - ${sig[@]}
+ case "$ret" in
+ 0|129|130|131|137|141|143|159)
+ echo -nE $'}bye\x1f0\x1e' >&"$fd_out"
+ exit "$ret"
+ ;;
+ esac
+ fi
+
+ (( ! _gitstatus_bash_downloaded )) || return
+ [[ "${GITSTATUS_AUTO_INSTALL:-1}" -ne 0 ]] || return
+ [[ "$_gitstatus_bash_daemon" == \
+ "${GITSTATUS_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/gitstatus}"/* ]] || return
+
+ set -- -f "$@"
+ _gitstatus_bash_daemon=
+ _gitstatus_bash_version=
+ _gitstatus_bash_downloaded=
+ source "$gitstatus_plugin_dir"/install || return
+ [[ -n "$_gitstatus_bash_daemon" ]] || return
+ [[ -n "$_gitstatus_bash_version" ]] || return
+ [[ "$_gitstatus_bash_downloaded" == 1 ]] || return
+
+ HOME="$home" "$_gitstatus_bash_daemon" \
+ -G "$_gitstatus_bash_version" "${daemon_args[@]}" <&"$fd_in" >&"$fd_out" &
+ local pid=$!
+ trap "trap - ${sig[*]}; kill $pid &>/dev/null" ${sig[@]}
+ wait "$pid"
+ trap - ${sig[@]}
+ echo -nE $'}bye\x1f0\x1e' >&"$fd_out"
+ ) & disown
+ ) & disown
+ } 0"$GITSTATUS_DAEMON_LOG"
+
+ exec {_GITSTATUS_REQ_FD}>>"$req_fifo" {_GITSTATUS_RESP_FD}<"$resp_fifo" || return
+ command rm -f -- "$req_fifo" "$resp_fifo" || return
+ [[ "$GITSTATUS_DAEMON_LOG" != /dev/null ]] || command rmdir -- "$tmpdir" 2>/dev/null
+
+ IFS='' read -r -u $_GITSTATUS_RESP_FD GITSTATUS_DAEMON_PID || return
+ [[ "$GITSTATUS_DAEMON_PID" == [1-9]* ]] || return
+
+ local reply
+ echo -nE $'}hello\x1f\x1e' >&$_GITSTATUS_REQ_FD || return
+ local dl=
+ while true; do
+ reply=
+ if ! IFS='' read -rd $'\x1e' -u $_GITSTATUS_RESP_FD -t "$timeout" reply; then
+ culprit="$reply"
+ return 1
+ fi
+ [[ "$reply" == $'}hello\x1f0' ]] && break
+ if [[ -z "$dl" ]]; then
+ dl=1
+ if [[ -t 2 ]]; then
+ local spinner=('\b\033[33m-\033[0m' '\b\033[33m\\\033[0m' '\b\033[33m|\033[0m' '\b\033[33m/\033[0m')
+ >&2 printf '[\033[33mgitstatus\033[0m] fetching \033[32mgitstatusd\033[0m .. '
+ else
+ local spinner=('.')
+ >&2 printf '[gitstatus] fetching gitstatusd ..'
+ fi
+ fi
+ >&2 printf "${spinner[0]}"
+ spinner=("${spinner[@]:1}" "${spinner[0]}")
+ done
+
+ if [[ -n "$dl" ]]; then
+ if [[ -t 2 ]]; then
+ >&2 printf '\b[\033[32mok\033[0m]\n'
+ else
+ >&2 echo ' [ok]'
+ fi
+ fi
+
+ _GITSTATUS_DIRTY_MAX_INDEX_SIZE=$max_dirty
+ _GITSTATUS_CLIENT_PID="$BASHPID"
+ }
+
+ if ! gitstatus_start_impl; then
+ >&2 printf '\n'
+ >&2 printf '[\033[31mERROR\033[0m]: gitstatus failed to initialize.\n'
+ if [[ -n "${culprit-}" ]]; then
+ >&2 printf '\n%s\n' "$culprit"
+ fi
+ [[ -z "${req_fifo:-}" ]] || command rm -f "$req_fifo"
+ [[ -z "${resp_fifo:-}" ]] || command rm -f "$resp_fifo"
+ unset -f gitstatus_start_impl
+ gitstatus_stop
+ return 1
+ fi
+
+ export _GITSTATUS_CLIENT_PID _GITSTATUS_REQ_FD _GITSTATUS_RESP_FD GITSTATUS_DAEMON_PID
+ unset -f gitstatus_start_impl
+}
+
+# Stops gitstatusd if it's running.
+function gitstatus_stop() {
+ if [[ "${_GITSTATUS_CLIENT_PID:-$BASHPID}" == "$BASHPID" ]]; then
+ [[ -z "${_GITSTATUS_REQ_FD:-}" ]] || exec {_GITSTATUS_REQ_FD}>&- || true
+ [[ -z "${_GITSTATUS_RESP_FD:-}" ]] || exec {_GITSTATUS_RESP_FD}>&- || true
+ [[ -z "${GITSTATUS_DAEMON_PID:-}" ]] || kill "$GITSTATUS_DAEMON_PID" &>/dev/null || true
+ fi
+ unset _GITSTATUS_REQ_FD _GITSTATUS_RESP_FD GITSTATUS_DAEMON_PID
+ unset _GITSTATUS_DIRTY_MAX_INDEX_SIZE _GITSTATUS_CLIENT_PID
+}
+
+# Retrieves status of a git repository from a directory under its working tree.
+#
+# Usage: gitstatus_query [OPTION]...
+#
+# -d STR Directory to query. Defaults to $PWD. Has no effect if GIT_DIR is set.
+# -t FLOAT Timeout in seconds. Will block for at most this long. If no results
+# are available by then, will return error.
+# -p Don't compute anything that requires reading Git index. If this option is used,
+# the following parameters will be 0: VCS_STATUS_INDEX_SIZE,
+# VCS_STATUS_{NUM,HAS}_{STAGED,UNSTAGED,UNTRACKED,CONFLICTED}.
+#
+# On success sets VCS_STATUS_RESULT to one of the following values:
+#
+# norepo-sync The directory doesn't belong to a git repository.
+# ok-sync The directory belongs to a git repository.
+#
+# If VCS_STATUS_RESULT is ok-sync, additional variables are set:
+#
+# VCS_STATUS_WORKDIR Git repo working directory. Not empty.
+# VCS_STATUS_COMMIT Commit hash that HEAD is pointing to. Either 40 hex digits or
+# empty if there is no HEAD (empty repo).
+# VCS_STATUS_COMMIT_ENCODING Encoding of the HEAD's commit message. Empty value means UTF-8.
+# VCS_STATUS_COMMIT_SUMMARY The first paragraph of the HEAD's commit message as one line.
+# VCS_STATUS_LOCAL_BRANCH Local branch name or empty if not on a branch.
+# VCS_STATUS_REMOTE_NAME The remote name, e.g. "upstream" or "origin".
+# VCS_STATUS_REMOTE_BRANCH Upstream branch name. Can be empty.
+# VCS_STATUS_REMOTE_URL Remote URL. Can be empty.
+# VCS_STATUS_ACTION Repository state, A.K.A. action. Can be empty.
+# VCS_STATUS_INDEX_SIZE The number of files in the index.
+# VCS_STATUS_NUM_STAGED The number of staged changes.
+# VCS_STATUS_NUM_CONFLICTED The number of conflicted changes.
+# VCS_STATUS_NUM_UNSTAGED The number of unstaged changes.
+# VCS_STATUS_NUM_UNTRACKED The number of untracked files.
+# VCS_STATUS_HAS_STAGED 1 if there are staged changes, 0 otherwise.
+# VCS_STATUS_HAS_CONFLICTED 1 if there are conflicted changes, 0 otherwise.
+# VCS_STATUS_HAS_UNSTAGED 1 if there are unstaged changes, 0 if there aren't, -1 if
+# unknown.
+# VCS_STATUS_NUM_STAGED_NEW The number of staged new files. Note that renamed files
+# are reported as deleted plus new.
+# VCS_STATUS_NUM_STAGED_DELETED The number of staged deleted files. Note that renamed files
+# are reported as deleted plus new.
+# VCS_STATUS_NUM_UNSTAGED_DELETED The number of unstaged deleted files. Note that renamed files
+# are reported as deleted plus new.
+# VCS_STATUS_HAS_UNTRACKED 1 if there are untracked files, 0 if there aren't, -1 if
+# unknown.
+# VCS_STATUS_COMMITS_AHEAD Number of commits the current branch is ahead of upstream.
+# Non-negative integer.
+# VCS_STATUS_COMMITS_BEHIND Number of commits the current branch is behind upstream.
+# Non-negative integer.
+# VCS_STATUS_STASHES Number of stashes. Non-negative integer.
+# VCS_STATUS_TAG The last tag (in lexicographical order) that points to the same
+# commit as HEAD.
+# VCS_STATUS_PUSH_REMOTE_NAME The push remote name, e.g. "upstream" or "origin".
+# VCS_STATUS_PUSH_REMOTE_URL Push remote URL. Can be empty.
+# VCS_STATUS_PUSH_COMMITS_AHEAD Number of commits the current branch is ahead of push remote.
+# Non-negative integer.
+# VCS_STATUS_PUSH_COMMITS_BEHIND Number of commits the current branch is behind push remote.
+# Non-negative integer.
+# VCS_STATUS_NUM_SKIP_WORKTREE The number of files in the index with skip-worktree bit set.
+# Non-negative integer.
+# VCS_STATUS_NUM_ASSUME_UNCHANGED The number of files in the index with assume-unchanged bit set.
+# Non-negative integer.
+#
+# The point of reporting -1 via VCS_STATUS_HAS_* is to allow the command to skip scanning files in
+# large repos. See -m flag of gitstatus_start.
+#
+# gitstatus_query returns an error if gitstatus_start hasn't been called in the same
+# shell or the call had failed.
+function gitstatus_query() {
+ unset OPTIND
+ local opt dir= timeout=() no_diff=0
+ while getopts "d:c:t:p" opt "$@"; do
+ case "$opt" in
+ d) dir=$OPTARG;;
+ t) timeout=(-t "$OPTARG");;
+ p) no_diff=1;;
+ *) return 1;;
+ esac
+ done
+ (( OPTIND == $# + 1 )) || { echo "usage: gitstatus_query [OPTION]..." >&2; return 1; }
+
+ [[ -n "${GITSTATUS_DAEMON_PID-}" ]] || return # not started
+
+ local req_id="$RANDOM.$RANDOM.$RANDOM.$RANDOM"
+ if [[ -z "${GIT_DIR:-}" ]]; then
+ [[ "$dir" == /* ]] || dir="$(pwd -P)/$dir" || return
+ elif [[ "$GIT_DIR" == /* ]]; then
+ dir=:"$GIT_DIR"
+ else
+ dir=:"$(pwd -P)/$GIT_DIR" || return
+ fi
+ echo -nE "$req_id"$'\x1f'"$dir"$'\x1f'"$no_diff"$'\x1e' >&$_GITSTATUS_REQ_FD || return
+
+ local -a resp
+ while true; do
+ IFS=$'\x1f' read -rd $'\x1e' -a resp -u $_GITSTATUS_RESP_FD "${timeout[@]}" || return
+ [[ "${resp[0]}" == "$req_id" ]] && break
+ done
+
+ if [[ "${resp[1]}" == 1 ]]; then
+ VCS_STATUS_RESULT=ok-sync
+ VCS_STATUS_WORKDIR="${resp[2]}"
+ VCS_STATUS_COMMIT="${resp[3]}"
+ VCS_STATUS_LOCAL_BRANCH="${resp[4]}"
+ VCS_STATUS_REMOTE_BRANCH="${resp[5]}"
+ VCS_STATUS_REMOTE_NAME="${resp[6]}"
+ VCS_STATUS_REMOTE_URL="${resp[7]}"
+ VCS_STATUS_ACTION="${resp[8]}"
+ VCS_STATUS_INDEX_SIZE="${resp[9]}"
+ VCS_STATUS_NUM_STAGED="${resp[10]}"
+ VCS_STATUS_NUM_UNSTAGED="${resp[11]}"
+ VCS_STATUS_NUM_CONFLICTED="${resp[12]}"
+ VCS_STATUS_NUM_UNTRACKED="${resp[13]}"
+ VCS_STATUS_COMMITS_AHEAD="${resp[14]}"
+ VCS_STATUS_COMMITS_BEHIND="${resp[15]}"
+ VCS_STATUS_STASHES="${resp[16]}"
+ VCS_STATUS_TAG="${resp[17]}"
+ VCS_STATUS_NUM_UNSTAGED_DELETED="${resp[18]}"
+ VCS_STATUS_NUM_STAGED_NEW="${resp[19]:-0}"
+ VCS_STATUS_NUM_STAGED_DELETED="${resp[20]:-0}"
+ VCS_STATUS_PUSH_REMOTE_NAME="${resp[21]:-}"
+ VCS_STATUS_PUSH_REMOTE_URL="${resp[22]:-}"
+ VCS_STATUS_PUSH_COMMITS_AHEAD="${resp[23]:-0}"
+ VCS_STATUS_PUSH_COMMITS_BEHIND="${resp[24]:-0}"
+ VCS_STATUS_NUM_SKIP_WORKTREE="${resp[25]:-0}"
+ VCS_STATUS_NUM_ASSUME_UNCHANGED="${resp[26]:-0}"
+ VCS_STATUS_COMMIT_ENCODING="${resp[27]-}"
+ VCS_STATUS_COMMIT_SUMMARY="${resp[28]-}"
+ VCS_STATUS_HAS_STAGED=$((VCS_STATUS_NUM_STAGED > 0))
+ if (( _GITSTATUS_DIRTY_MAX_INDEX_SIZE >= 0 &&
+ VCS_STATUS_INDEX_SIZE > _GITSTATUS_DIRTY_MAX_INDEX_SIZE_ )); then
+ VCS_STATUS_HAS_UNSTAGED=-1
+ VCS_STATUS_HAS_CONFLICTED=-1
+ VCS_STATUS_HAS_UNTRACKED=-1
+ else
+ VCS_STATUS_HAS_UNSTAGED=$((VCS_STATUS_NUM_UNSTAGED > 0))
+ VCS_STATUS_HAS_CONFLICTED=$((VCS_STATUS_NUM_CONFLICTED > 0))
+ VCS_STATUS_HAS_UNTRACKED=$((VCS_STATUS_NUM_UNTRACKED > 0))
+ fi
+ else
+ VCS_STATUS_RESULT=norepo-sync
+ unset VCS_STATUS_WORKDIR
+ unset VCS_STATUS_COMMIT
+ unset VCS_STATUS_LOCAL_BRANCH
+ unset VCS_STATUS_REMOTE_BRANCH
+ unset VCS_STATUS_REMOTE_NAME
+ unset VCS_STATUS_REMOTE_URL
+ unset VCS_STATUS_ACTION
+ unset VCS_STATUS_INDEX_SIZE
+ unset VCS_STATUS_NUM_STAGED
+ unset VCS_STATUS_NUM_UNSTAGED
+ unset VCS_STATUS_NUM_CONFLICTED
+ unset VCS_STATUS_NUM_UNTRACKED
+ unset VCS_STATUS_HAS_STAGED
+ unset VCS_STATUS_HAS_UNSTAGED
+ unset VCS_STATUS_HAS_CONFLICTED
+ unset VCS_STATUS_HAS_UNTRACKED
+ unset VCS_STATUS_COMMITS_AHEAD
+ unset VCS_STATUS_COMMITS_BEHIND
+ unset VCS_STATUS_STASHES
+ unset VCS_STATUS_TAG
+ unset VCS_STATUS_NUM_UNSTAGED_DELETED
+ unset VCS_STATUS_NUM_STAGED_NEW
+ unset VCS_STATUS_NUM_STAGED_DELETED
+ unset VCS_STATUS_PUSH_REMOTE_NAME
+ unset VCS_STATUS_PUSH_REMOTE_URL
+ unset VCS_STATUS_PUSH_COMMITS_AHEAD
+ unset VCS_STATUS_PUSH_COMMITS_BEHIND
+ unset VCS_STATUS_NUM_SKIP_WORKTREE
+ unset VCS_STATUS_NUM_ASSUME_UNCHANGED
+ unset VCS_STATUS_COMMIT_ENCODING
+ unset VCS_STATUS_COMMIT_SUMMARY
+ fi
+}
+
+# Usage: gitstatus_check.
+#
+# Returns 0 if and only if gitstatus_start has succeeded previously.
+# If it returns non-zero, gitstatus_query is guaranteed to return non-zero.
+function gitstatus_check() {
+ [[ -n "$GITSTATUS_DAEMON_PID" ]]
+}
diff --git a/gitstatus/gitstatus.plugin.zsh b/gitstatus/gitstatus.plugin.zsh
index 7a0034f8..b74396d3 100644
--- a/gitstatus/gitstatus.plugin.zsh
+++ b/gitstatus/gitstatus.plugin.zsh
@@ -15,6 +15,8 @@
# VCS_STATUS_COMMIT=c000eddcff0fb38df2d0137efe24d9d2d900f209
# VCS_STATUS_COMMITS_AHEAD=0
# VCS_STATUS_COMMITS_BEHIND=0
+# VCS_STATUS_COMMIT_ENCODING=''
+# VCS_STATUS_COMMIT_SUMMARY='pull upstream changes from gitstatus'
# VCS_STATUS_HAS_CONFLICTED=0
# VCS_STATUS_HAS_STAGED=0
# VCS_STATUS_HAS_UNSTAGED=1
@@ -55,9 +57,9 @@ autoload -Uz add-zsh-hook || return
zmodload zsh/datetime zsh/system || return
zmodload -F zsh/files b:zf_rm || return
-typeset -g _gitstatus_plugin_dir=${${(%):-%x}:A:h}
+typeset -g _gitstatus_plugin_dir"${1:-}"="${${(%):-%x}:A:h}"
-# Retrives status of a git repo from a directory under its working tree.
+# Retrieves status of a git repo from a directory under its working tree.
#
## Usage: gitstatus_query [OPTION]... NAME
#
@@ -88,6 +90,8 @@ typeset -g _gitstatus_plugin_dir=${${(%):-%x}:A:h}
# VCS_STATUS_WORKDIR Git repo working directory. Not empty.
# VCS_STATUS_COMMIT Commit hash that HEAD is pointing to. Either 40 hex digits or
# empty if there is no HEAD (empty repo).
+# VCS_STATUS_COMMIT_ENCODING Encoding of the HEAD's commit message. Empty value means UTF-8.
+# VCS_STATUS_COMMIT_SUMMARY The first paragraph of the HEAD's commit message as one line.
# VCS_STATUS_LOCAL_BRANCH Local branch name or empty if not on a branch.
# VCS_STATUS_REMOTE_NAME The remote name, e.g. "upstream" or "origin".
# VCS_STATUS_REMOTE_BRANCH Upstream branch name. Can be empty.
@@ -138,9 +142,11 @@ typeset -g _gitstatus_plugin_dir=${${(%):-%x}:A:h}
#
# It's illegal to call gitstatus_query if the last asynchronous call with the same NAME hasn't
# completed yet. If you need to issue concurrent requests, use different NAME arguments.
-function gitstatus_query() {
+function gitstatus_query"${1:-}"() {
emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
+ local fsuf=${${(%):-%N}#gitstatus_query}
+
unset VCS_STATUS_RESULT
local opt dir callback OPTARG
@@ -166,22 +172,40 @@ function gitstatus_query() {
done
if (( OPTIND != ARGC )); then
- print -ru2 -- "gitstatus_start: exactly one positional argument is required"
+ print -ru2 -- "gitstatus_query: exactly one positional argument is required"
return 1
fi
local name=$*[OPTIND]
if [[ $name != [[:IDENT:]]## ]]; then
- print -ru2 -- "gitstatus_start: invalid positional argument: $name"
+ print -ru2 -- "gitstatus_query: invalid positional argument: $name"
return 1
fi
(( _GITSTATUS_STATE_$name == 2 )) || return
if [[ -z $GIT_DIR ]]; then
- [[ $dir == /* ]] || dir=${(%):-%/}/$dir
+ if [[ $dir != /* ]]; then
+ if [[ $PWD == /* && $PWD -ef . ]]; then
+ dir=$PWD/$dir
+ else
+ dir=${dir:a}
+ fi
+ fi
else
- [[ $GIT_DIR == /* ]] && dir=:$GIT_DIR || dir=:${(%):-%/}/$GIT_DIR
+ if [[ $GIT_DIR == /* ]]; then
+ dir=:$GIT_DIR
+ elif [[ $PWD == /* && $PWD -ef . ]]; then
+ dir=:$PWD/$GIT_DIR
+ else
+ dir=:${GIT_DIR:a}
+ fi
+ fi
+
+ if [[ $dir != (|:)/* ]]; then
+ typeset -g VCS_STATUS_RESULT=norepo-sync
+ _gitstatus_clear$fsuf
+ return 0
fi
local -i req_fd=${(P)${:-_GITSTATUS_REQ_FD_$name}}
@@ -192,10 +216,10 @@ function gitstatus_query() {
if (( timeout == 0 )); then
typeset -g VCS_STATUS_RESULT=tout
- _gitstatus_clear
+ _gitstatus_clear$fsuf
else
while true; do
- _gitstatus_process_response $name $timeout $req_id || return
+ _gitstatus_process_response$fsuf $name $timeout $req_id || return
[[ $VCS_STATUS_RESULT == *-async ]] || break
done
fi
@@ -215,8 +239,11 @@ function gitstatus_query() {
#
# If a callback gets called, VCS_STATUS_* parameters are set as in gitstatus_query.
# VCS_STATUS_RESULT is either norepo-async or ok-async.
-function gitstatus_process_results() {
+function gitstatus_process_results"${1:-}"() {
emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
+
+ local fsuf=${${(%):-%N}#gitstatus_process_results}
+
local opt OPTARG
local -i OPTIND
local -F timeout=-1
@@ -249,18 +276,18 @@ function gitstatus_process_results() {
(( _GITSTATUS_STATE_$name == 2 )) || return
while (( _GITSTATUS_NUM_INFLIGHT_$name )); do
- _gitstatus_process_response $name $timeout '' || return
+ _gitstatus_process_response$fsuf $name $timeout '' || return
[[ $VCS_STATUS_RESULT == *-async ]] || break
done
return 0
}
-function _gitstatus_clear() {
+function _gitstatus_clear"${1:-}"() {
unset VCS_STATUS_{WORKDIR,COMMIT,LOCAL_BRANCH,REMOTE_BRANCH,REMOTE_NAME,REMOTE_URL,ACTION,INDEX_SIZE,NUM_STAGED,NUM_UNSTAGED,NUM_CONFLICTED,NUM_UNTRACKED,HAS_STAGED,HAS_UNSTAGED,HAS_CONFLICTED,HAS_UNTRACKED,COMMITS_AHEAD,COMMITS_BEHIND,STASHES,TAG,NUM_UNSTAGED_DELETED,NUM_STAGED_NEW,NUM_STAGED_DELETED,PUSH_REMOTE_NAME,PUSH_REMOTE_URL,PUSH_COMMITS_AHEAD,PUSH_COMMITS_BEHIND,NUM_SKIP_WORKTREE,NUM_ASSUME_UNCHANGED}
}
-function _gitstatus_process_response() {
+function _gitstatus_process_response"${1:-}"() {
local name=$1 timeout req_id=$3 buf
local -i resp_fd=_GITSTATUS_RESP_FD_$name
local -i dirty_max_index_size=_GITSTATUS_DIRTY_MAX_INDEX_SIZE_$name
@@ -270,17 +297,17 @@ function _gitstatus_process_response() {
if (( $? == 4 )); then
if [[ -n $req_id ]]; then
typeset -g VCS_STATUS_RESULT=tout
- _gitstatus_clear
+ _gitstatus_clear$fsuf
fi
return 0
else
- gitstatus_stop $name
+ gitstatus_stop$fsuf $name
return 1
fi
}
while [[ $buf != *$'\x1e' ]]; do
if ! sysread -i $resp_fd 'buf[$#buf+1]'; then
- gitstatus_stop $name
+ gitstatus_stop$fsuf $name
return 1
fi
done
@@ -318,7 +345,9 @@ function _gitstatus_process_response() {
VCS_STATUS_PUSH_COMMITS_AHEAD \
VCS_STATUS_PUSH_COMMITS_BEHIND \
VCS_STATUS_NUM_SKIP_WORKTREE \
- VCS_STATUS_NUM_ASSUME_UNCHANGED in "${(@)resp[3,27]}"; do
+ VCS_STATUS_NUM_ASSUME_UNCHANGED \
+ VCS_STATUS_COMMIT_ENCODING \
+ VCS_STATUS_COMMIT_SUMMARY in "${(@)resp[3,29]}"; do
done
typeset -gi VCS_STATUS_{INDEX_SIZE,NUM_STAGED,NUM_UNSTAGED,NUM_CONFLICTED,NUM_UNTRACKED,COMMITS_AHEAD,COMMITS_BEHIND,STASHES,NUM_UNSTAGED_DELETED,NUM_STAGED_NEW,NUM_STAGED_DELETED,PUSH_COMMITS_AHEAD,PUSH_COMMITS_BEHIND,NUM_SKIP_WORKTREE,NUM_ASSUME_UNCHANGED}
typeset -gi VCS_STATUS_HAS_STAGED=$((VCS_STATUS_NUM_STAGED > 0))
@@ -339,7 +368,7 @@ function _gitstatus_process_response() {
else
typeset -g VCS_STATUS_RESULT=norepo-async
fi
- _gitstatus_clear
+ _gitstatus_clear$fsuf
fi
(( --_GITSTATUS_NUM_INFLIGHT_$name ))
[[ $VCS_STATUS_RESULT == *-async ]] && emulate zsh -c "${resp[1]#* }"
@@ -348,6 +377,106 @@ function _gitstatus_process_response() {
return 0
}
+function _gitstatus_daemon"${1:-}"() {
+ local -i pipe_fd
+ exec 0<&- {pipe_fd}>&1 1>>$daemon_log 2>&1 || return
+ local pgid=$sysparams[pid]
+ [[ $pgid == <1-> ]] || return
+ builtin cd -q / || return
+
+ {
+ {
+ trap '' PIPE
+
+ local uname_sm
+ uname_sm="${${(L)$(command uname -sm)}//ฤฑ/i}" || return
+ [[ $uname_sm == [^' ']##' '[^' ']## ]] || return
+ local uname_s=${uname_sm% *}
+ local uname_m=${uname_sm#* }
+
+ if [[ $GITSTATUS_NUM_THREADS == <1-> ]]; then
+ args+=(-t $GITSTATUS_NUM_THREADS)
+ else
+ local cpus
+ if (( ! $+commands[sysctl] )) || [[ $uname_s == linux ]] ||
+ ! cpus="$(command sysctl -n hw.ncpu)"; then
+ if (( ! $+commands[getconf] )) || ! cpus="$(command getconf _NPROCESSORS_ONLN)"; then
+ cpus=8
+ fi
+ fi
+ args+=(-t $((cpus > 16 ? 32 : cpus > 0 ? 2 * cpus : 16)))
+ fi
+
+ command mkfifo -- $file_prefix.fifo || return
+ print -rnu $pipe_fd -- ${(l:20:)pgid} || return
+ exec <$file_prefix.fifo || return
+ zf_rm -- $file_prefix.fifo || return
+
+ local _gitstatus_zsh_daemon _gitstatus_zsh_version _gitstatus_zsh_downloaded
+
+ function _gitstatus_set_daemon$fsuf() {
+ _gitstatus_zsh_daemon="$1"
+ _gitstatus_zsh_version="$2"
+ _gitstatus_zsh_downloaded="$3"
+ }
+
+ local gitstatus_plugin_dir_var=_gitstatus_plugin_dir$fsuf
+ local gitstatus_plugin_dir=${(P)gitstatus_plugin_dir_var}
+ builtin set -- -d $gitstatus_plugin_dir -s $uname_s -m $uname_m \
+ -p "printf '\\001' >&$pipe_fd" -e $pipe_fd -- _gitstatus_set_daemon$fsuf
+ [[ ${GITSTATUS_AUTO_INSTALL:-1} == (|-|+)<1-> ]] || builtin set -- -n "$@"
+ builtin source $gitstatus_plugin_dir/install || return
+ [[ -n $_gitstatus_zsh_daemon ]] || return
+ [[ -n $_gitstatus_zsh_version ]] || return
+ [[ $_gitstatus_zsh_downloaded == [01] ]] || return
+
+ if (( UID == EUID )); then
+ local home=~
+ else
+ local user
+ user="$(command id -un)" || return
+ local home=${userdirs[$user]}
+ [[ -n $home ]] || return
+ fi
+
+ if [[ -x $_gitstatus_zsh_daemon ]]; then
+ HOME=$home $_gitstatus_zsh_daemon -G $_gitstatus_zsh_version "${(@)args}" >&$pipe_fd
+ local -i ret=$?
+ [[ $ret == (0|129|130|131|137|141|143|159) ]] && return ret
+ fi
+
+ (( ! _gitstatus_zsh_downloaded )) || return
+ [[ ${GITSTATUS_AUTO_INSTALL:-1} == (|-|+)<1-> ]] || return
+ [[ $_gitstatus_zsh_daemon == \
+ ${GITSTATUS_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/gitstatus}/* ]] || return
+
+ builtin set -- -f "$@"
+ _gitstatus_zsh_daemon=
+ _gitstatus_zsh_version=
+ _gitstatus_zsh_downloaded=
+ builtin source $gitstatus_plugin_dir/install || return
+ [[ -n $_gitstatus_zsh_daemon ]] || return
+ [[ -n $_gitstatus_zsh_version ]] || return
+ [[ $_gitstatus_zsh_downloaded == 1 ]] || return
+
+ HOME=$home $_gitstatus_zsh_daemon -G $_gitstatus_zsh_version "${(@)args}" >&$pipe_fd
+ } always {
+ local -i ret=$?
+ zf_rm -f -- $file_prefix.lock $file_prefix.fifo
+ kill -- -$pgid
+ }
+ } &!
+
+ (( lock_fd == -1 )) && return
+
+ {
+ if zsystem flock -- $file_prefix.lock && command sleep 5 && [[ -e $file_prefix.lock ]]; then
+ zf_rm -f -- $file_prefix.lock $file_prefix.fifo
+ kill -- -$pgid
+ fi
+ } &!
+}
+
# Starts gitstatusd in the background. Does nothing and succeeds if gitstatusd is already running.
#
# Usage: gitstatus_start [OPTION]... NAME
@@ -380,10 +509,12 @@ function _gitstatus_process_response() {
#
# -D Unless this option is specified, report zero staged, unstaged and conflicted
# changes for repositories with bash.showDirtyState = false.
-function gitstatus_start() {
+function gitstatus_start"${1:-}"() {
emulate -L zsh -o no_aliases -o no_bg_nice -o extended_glob -o typeset_silent || return
print -rnu2 || return
+ local fsuf=${${(%):-%N}#gitstatus_start}
+
local opt OPTARG
local -i OPTIND
local -F timeout=5
@@ -409,7 +540,7 @@ function gitstatus_start() {
args+=(-$opt $OPTARG)
[[ $opt == m ]] && dirty_max_index_size=OPTARG
;;
- e|U|W|D) args+=$opt;;
+ e|U|W|D) args+=-$opt;;
+(e|U|W|D)) args=(${(@)args:#-$opt});;
\?) print -ru2 -- "gitstatus_start: invalid option: $OPTARG" ; return 1;;
:) print -ru2 -- "gitstatus_start: missing required argument: $OPTARG"; return 1;;
@@ -429,279 +560,274 @@ function gitstatus_start() {
fi
local -i lock_fd resp_fd stderr_fd
- local file_prefix xtrace=/dev/null daemon_log=/dev/null
- if (( _GITSTATUS_STATE_$name )); then
- (( async )) && return
- (( _GITSTATUS_STATE_$name == 2 )) && return
- lock_fd=_GITSTATUS_LOCK_FD_$name
- resp_fd=_GITSTATUS_RESP_FD_$name
- xtrace=${(P)${:-GITSTATUS_XTRACE_$name}}
- daemon_log=${(P)${:-GITSTATUS_DAEMON_LOG_$name}}
- file_prefix=${(P)${:-_GITSTATUS_FILE_PREFIX_$name}}
- else
- typeset -gi _GITSTATUS_START_COUNTER
- local log_level=$GITSTATUS_LOG_LEVEL
- local file_prefix=${${TMPDIR:-/tmp}:A}/gitstatus.$name.$EUID
- file_prefix+=.$sysparams[pid].$EPOCHSECONDS.$((++_GITSTATUS_START_COUNTER))
- (( GITSTATUS_ENABLE_LOGGING )) && : ${log_level:=INFO}
- if [[ -n $log_level ]]; then
- xtrace=$file_prefix.xtrace.log
- daemon_log=$file_prefix.daemon.log
- fi
- args+=(-v ${log_level:-FATAL})
- typeset -g GITSTATUS_XTRACE_$name=$xtrace
- typeset -g GITSTATUS_DAEMON_LOG_$name=$daemon_log
- typeset -g _GITSTATUS_FILE_PREFIX_$name=$file_prefix
- typeset -gi _GITSTATUS_CLIENT_PID_$name="sysparams[pid]"
- typeset -gi _GITSTATUS_DIRTY_MAX_INDEX_SIZE_$name=dirty_max_index_size
- fi
+ local file_prefix xtrace=/dev/null daemon_log=/dev/null culprit
- () {
- if [[ $xtrace != /dev/null && -o no_xtrace ]]; then
- exec {stderr_fd}>&2 || return
- exec 2>>$xtrace || return
- setopt xtrace
- fi
-
- setopt monitor || return
-
- if (( ! _GITSTATUS_STATE_$name )); then
- if [[ -r /proc/version && "$($file_prefix.lock || return
- zsystem flock -f lock_fd $file_prefix.lock || return
- [[ $lock_fd == <1-> ]] || return
+ local tmpdir=/tmp
fi
- typeset -gi _GITSTATUS_LOCK_FD_$name=lock_fd
-
- {
- () {
- typeset -gi GITSTATUS_DAEMON_PID_$name="${sysparams[procsubstpid]:--1}"
- sysopen -r -o cloexec -u resp_fd -- $1 || return
- [[ $resp_fd == <1-> ]] || return
- typeset -gi _GITSTATUS_RESP_FD_$name=resp_fd
- } <(
- exec 2>&3 3>&- || return
- local pgid=$sysparams[pid]
- [[ $pgid == <1-> ]] || return
-
- {
- {
- trap '' PIPE
-
- if [[ -z $GITSTATUS_DAEMON || $GITSTATUS_NUM_THREADS != <1-> ]]; then
- local kernel
- kernel="${(L)$(uname -s)}" || return
- [[ -n $kernel ]] || return
- fi
-
- if [[ $GITSTATUS_DAEMON == /* ]]; then
- local daemons=($GITSTATUS_DAEMON)
- elif (( $+commands[$GITSTATUS_DAEMON] )); then
- local daemons=($commands[$GITSTATUS_DAEMON])
- elif [[ -n $GITSTATUS_DAEMON ]]; then
- local daemons=($_gitstatus_plugin_dir/{usrbin,bin}/$GITSTATUS_DAEMON)
- else
- local -aU os
- case $kernel in
- linux)
- os=("${(L)$(uname -o 2>/dev/null)}") || os=()
- [[ $os[1] == android ]] || os=(linux)
- ;;
- cygwin_nt-*) os=($kernel cygwin_nt-10.0);;
- mingw*|msys*) os=($kernel msys_nt-10.0);;
- *) os=($kernel);;
- esac
- local arch
- arch="${(L)$(uname -m)}" || return
- [[ -n $arch ]] || return
- local daemons=(
- $_gitstatus_plugin_dir/{usrbin,bin}/gitstatusd-${^os}-$arch{,-static})
- fi
-
- local files=(${^daemons}(N:A))
- daemons=(${^files}(N*))
-
- if (( stderr_fd && $#daemons != $#files )); then
- unsetopt xtrace
- print -ru2 -- ''
- print -ru2 -- 'ERROR: missing execute permissions on gitstatusd file(s):'
- print -ru2 -- ''
- print -ru2 -- ' '${(pj:\n :)${files:|daemons}}
- print -ru2 -- ''
- setopt xtrace
- fi
-
- (( $#daemons )) || return
-
- if [[ $GITSTATUS_NUM_THREADS == <1-> ]]; then
- args+=(-t $GITSTATUS_NUM_THREADS)
- else
- local cpus
- if (( ! $+commands[sysctl] )) || [[ $kernel == linux ]] ||
- ! cpus="$(sysctl -n hw.ncpu)"; then
- if (( ! $+commands[getconf] )) || ! cpus="$(getconf _NPROCESSORS_ONLN)"; then
- cpus=8
- fi
- fi
- args+=(-t $((cpus > 16 ? 32 : cpus > 0 ? 2 * cpus : 16)))
- fi
-
- mkfifo -- $file_prefix.fifo || return
- print -rn -- ${(l:20:)pgid} || return
- exec <$file_prefix.fifo || return
- zf_rm -- $file_prefix.fifo || return
-
- local daemon
- for daemon in $daemons; do
- $daemon "${(@)args}"
- local -i ret=$?
- (( ret == 0 || ret == 10 || ret > 128 )) && return ret
- done
- } always {
- local -i ret=$?
- kill -- -$pgid
- }
- } &!
-
- (( lock_fd == -1 )) && return
-
- {
- if zsystem flock -- $file_prefix.lock && [[ -e $file_prefix.lock ]]; then
- zf_rm -f -- $file_prefix.lock $file_prefix.fifo
- kill -- -$pgid
- fi
- } &!
- ) || return
- } 3>>$daemon_log /dev/null || return
-
- typeset -gi _GITSTATUS_STATE_$name=1
+ local file_prefix=${tmpdir:A}/gitstatus.$name.$EUID
+ file_prefix+=.$sysparams[pid].$EPOCHSECONDS.$((++_GITSTATUS_START_COUNTER))
+ (( GITSTATUS_ENABLE_LOGGING )) && : ${log_level:=INFO}
+ if [[ -n $log_level ]]; then
+ xtrace=$file_prefix.xtrace.log
+ daemon_log=$file_prefix.daemon.log
+ fi
+ args+=(-v ${log_level:-FATAL})
+ typeset -g GITSTATUS_XTRACE_$name=$xtrace
+ typeset -g GITSTATUS_DAEMON_LOG_$name=$daemon_log
+ typeset -g _GITSTATUS_FILE_PREFIX_$name=$file_prefix
+ typeset -gi _GITSTATUS_CLIENT_PID_$name="sysparams[pid]"
+ typeset -gi _GITSTATUS_DIRTY_MAX_INDEX_SIZE_$name=dirty_max_index_size
fi
- if (( ! async )); then
- (( _GITSTATUS_CLIENT_PID_$name == sysparams[pid] )) || return
+ () {
+ if [[ $xtrace != /dev/null && -o no_xtrace ]]; then
+ exec {stderr_fd}>&2 || return
+ exec 2>>$xtrace || return
+ setopt xtrace
+ fi
- local pgid
- while (( $#pgid < 20 )); do
- [[ -t $resp_fd ]]
- sysread -s $((20 - $#pgid)) -t $timeout -i $resp_fd 'pgid[$#pgid+1]' || return
- done
- [[ $pgid == ' '#<1-> ]] || return
- typeset -gi GITSTATUS_DAEMON_PID_$name=pgid
+ setopt monitor || return
- sysopen -w -o cloexec -u req_fd -- $file_prefix.fifo || return
- [[ $req_fd == <1-> ]] || return
- typeset -gi _GITSTATUS_REQ_FD_$name=req_fd
-
- function _gitstatus_process_response_$name() {
- emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
- local name=${${(%):-%N}#_gitstatus_process_response_}
- if (( ARGC == 1 )); then
- _gitstatus_process_response $name 0 ''
+ if (( ! _GITSTATUS_STATE_$name )); then
+ if [[ -r /proc/version && "$($file_prefix.lock || return
+ zsystem flock -f lock_fd $file_prefix.lock || return
+ [[ $lock_fd == <1-> ]] || return
fi
- }
- if ! zle -F $resp_fd _gitstatus_process_response_$name; then
- unfunction _gitstatus_process_response_$name
- return 1
+
+ typeset -gi _GITSTATUS_LOCK_FD_$name=lock_fd
+
+ if [[ $OSTYPE == cygwin* && -d /proc/self/fd ]]; then
+ # Work around bugs in Cygwin 32-bit.
+ #
+ # This hangs:
+ #
+ # emulate -L zsh
+ # () { exec {fd}< $1 } <(:)
+ # =true # hangs here
+ #
+ # This hangs:
+ #
+ # sysopen -r -u fd <(:)
+ local -i fd
+ exec {fd}< <(_gitstatus_daemon$fsuf) || return
+ {
+ [[ -r /proc/self/fd/$fd ]] || return
+ sysopen -r -o cloexec -u resp_fd /proc/self/fd/$fd || return
+ } always {
+ exec {fd} >&- || return
+ }
+ else
+ sysopen -r -o cloexec -u resp_fd <(_gitstatus_daemon$fsuf) || return
+ fi
+
+ typeset -gi GITSTATUS_DAEMON_PID_$name="${sysparams[procsubstpid]:--1}"
+
+ [[ $resp_fd == <1-> ]] || return
+ typeset -gi _GITSTATUS_RESP_FD_$name=resp_fd
+ typeset -gi _GITSTATUS_STATE_$name=1
fi
- function _gitstatus_cleanup_$name() {
- emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
- local name=${${(%):-%N}#_gitstatus_cleanup_}
+ if (( ! async )); then
(( _GITSTATUS_CLIENT_PID_$name == sysparams[pid] )) || return
- gitstatus_stop $name
- }
- if ! add-zsh-hook zshexit _gitstatus_cleanup_$name; then
- unfunction _gitstatus_cleanup_$name
- return 1
+
+ local pgid
+ while (( $#pgid < 20 )); do
+ [[ -t $resp_fd ]]
+ sysread -s $((20 - $#pgid)) -t $timeout -i $resp_fd 'pgid[$#pgid+1]' || return
+ done
+ [[ $pgid == ' '#<1-> ]] || return
+ typeset -gi GITSTATUS_DAEMON_PID_$name=pgid
+
+ sysopen -w -o cloexec -u req_fd -- $file_prefix.fifo || return
+ [[ $req_fd == <1-> ]] || return
+ typeset -gi _GITSTATUS_REQ_FD_$name=req_fd
+
+ print -nru $req_fd -- $'}hello\x1f\x1e' || return
+ local expected=$'}hello\x1f0\x1e' actual
+ if (( $+functions[p10k] )) && [[ ! -t 1 && ! -t 0 ]]; then
+ local -F deadline='EPOCHREALTIME + 4'
+ else
+ local -F deadline='1'
+ fi
+ while true; do
+ [[ -t $resp_fd ]]
+ sysread -s 1 -t $timeout -i $resp_fd actual || return
+ [[ $expected == $actual* ]] && break
+ if [[ $actual != $'\1' ]]; then
+ [[ -t $resp_fd ]]
+ while sysread -t $timeout -i $resp_fd 'actual[$#actual+1]'; do
+ [[ -t $resp_fd ]]
+ done
+ culprit=$actual
+ return 1
+ fi
+ (( EPOCHREALTIME < deadline )) && continue
+ if (( deadline > 0 )); then
+ deadline=0
+ if (( stderr_fd )); then
+ unsetopt xtrace
+ exec 2>&$stderr_fd {stderr_fd}>&-
+ stderr_fd=0
+ fi
+ if (( $+functions[p10k] )); then
+ p10k clear-instant-prompt || return
+ fi
+ if [[ $name == POWERLEVEL9K ]]; then
+ local label=powerlevel10k
+ else
+ local label=gitstatus
+ fi
+ if [[ -t 2 ]]; then
+ local spinner=($'\b%3F-%f' $'\b%3F\\%f' $'\b%3F|%f' $'\b%3F/%f')
+ print -Prnu2 -- "[%3F$label%f] fetching %2Fgitstatusd%f .. "
+ else
+ local spinner=('.')
+ print -rnu2 -- "[$label] fetching gitstatusd .."
+ fi
+ fi
+ print -Prnu2 -- $spinner[1]
+ spinner=($spinner[2,-1] $spinner[1])
+ done
+
+ if (( deadline == 0 )); then
+ if [[ -t 2 ]]; then
+ print -Pru2 -- $'\b[%2Fok%f]'
+ else
+ print -ru2 -- ' [ok]'
+ fi
+ if [[ $xtrace != /dev/null && -o no_xtrace ]]; then
+ exec {stderr_fd}>&2 || return
+ exec 2>>$xtrace || return
+ setopt xtrace
+ fi
+ fi
+
+ while (( $#actual < $#expected )); do
+ [[ -t $resp_fd ]]
+ sysread -s $(($#expected - $#actual)) -t $timeout -i $resp_fd 'actual[$#actual+1]' || return
+ done
+ [[ $actual == $expected ]] || return
+
+ function _gitstatus_process_response_$name-$fsuf() {
+ emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
+ local pair=${${(%):-%N}#_gitstatus_process_response_}
+ local name=${pair%%-*}
+ local fsuf=${pair#*-}
+ [[ $name == POWERLEVEL9K && $fsuf == _p9k_ ]] && eval $__p9k_intro_base
+ if (( ARGC == 1 )); then
+ _gitstatus_process_response$fsuf $name 0 ''
+ else
+ gitstatus_stop$fsuf $name
+ fi
+ }
+ if ! zle -F $resp_fd _gitstatus_process_response_$name-$fsuf; then
+ unfunction _gitstatus_process_response_$name-$fsuf
+ return 1
+ fi
+
+ function _gitstatus_cleanup_$name-$fsuf() {
+ emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
+ local pair=${${(%):-%N}#_gitstatus_cleanup_}
+ local name=${pair%%-*}
+ local fsuf=${pair#*-}
+ (( _GITSTATUS_CLIENT_PID_$name == sysparams[pid] )) || return
+ gitstatus_stop$fsuf $name
+ }
+ if ! add-zsh-hook zshexit _gitstatus_cleanup_$name-$fsuf; then
+ unfunction _gitstatus_cleanup_$name-$fsuf
+ return 1
+ fi
+
+ if (( lock_fd != -1 )); then
+ zf_rm -- $file_prefix.lock || return
+ zsystem flock -u $lock_fd || return
+ fi
+ unset _GITSTATUS_LOCK_FD_$name
+
+ typeset -gi _GITSTATUS_STATE_$name=2
fi
+ }
+ } always {
+ local -i err=$?
+ (( stderr_fd )) && exec 2>&$stderr_fd {stderr_fd}>&-
+ (( err == 0 )) && return
- print -nru $req_fd -- $'hello\x1f\x1e' || return
- local expected=$'hello\x1f0\x1e' actual
- while (( $#actual < $#expected )); do
- [[ -t $resp_fd ]]
- sysread -s $(($#expected - $#actual)) -t $timeout -i $resp_fd 'actual[$#actual+1]' || return
- done
- [[ $actual == $expected ]] || return
+ gitstatus_stop$fsuf $name
- if (( lock_fd != -1 )); then
- zf_rm -- $file_prefix.lock || return
- zsystem flock -u $lock_fd || return
- fi
- unset _GITSTATUS_LOCK_FD_$name
-
- typeset -gi _GITSTATUS_STATE_$name=2
+ setopt prompt_percent no_prompt_subst no_prompt_bang
+ (( $+functions[p10k] )) && p10k clear-instant-prompt
+ print -ru2 -- ''
+ print -Pru2 -- '[%F{red}ERROR%f]: gitstatus failed to initialize.'
+ print -ru2 -- ''
+ if [[ -n $culprit ]]; then
+ print -ru2 -- $culprit
+ return err
+ fi
+ if [[ -s $xtrace ]]; then
+ print -ru2 -- ''
+ print -Pru2 -- " Zsh log (%U${xtrace//\%/%%}%u):"
+ print -Pru2 -- '%F{yellow}'
+ print -lru2 -- "${(@)${(@f)$(<$xtrace)}/#/ }"
+ print -Pnru2 -- '%f'
+ fi
+ if [[ -s $daemon_log ]]; then
+ print -ru2 -- ''
+ print -Pru2 -- " Daemon log (%U${daemon_log//\%/%%}%u):"
+ print -Pru2 -- '%F{yellow}'
+ print -lru2 -- "${(@)${(@f)$(<$daemon_log)}/#/ }"
+ print -Pnru2 -- '%f'
+ fi
+ if [[ $GITSTATUS_LOG_LEVEL == DEBUG ]]; then
+ print -ru2 -- ''
+ print -ru2 -- ' System information:'
+ print -Pru2 -- '%F{yellow}'
+ print -ru2 -- " zsh: $ZSH_VERSION"
+ print -ru2 -- " uname -a: $(command uname -a)"
+ print -Pru2 -- '%f'
+ print -ru2 -- ' If you need help, open an issue and attach this whole error message to it:'
+ print -ru2 -- ''
+ print -Pru2 -- ' %Uhttps://github.com/romkatv/gitstatus/issues/new%u'
+ else
+ print -ru2 -- ''
+ local home=~
+ local zshrc=${${${(q)${ZDOTDIR:-~}}/#${(q)home}/'~'}//\%/%%}/.zshrc
+ print -Pru2 -- " Add the following parameter to %U$zshrc%u for extra diagnostics on error:"
+ print -ru2 -- ''
+ print -Pru2 -- ' %BGITSTATUS_LOG_LEVEL=DEBUG%b'
+ print -ru2 -- ''
+ print -ru2 -- ' Restart Zsh to retry gitstatus initialization:'
+ print -ru2 -- ''
+ print -Pru2 -- ' %F{green}%Uexec%u zsh%f'
fi
}
-
- local -i err=$?
- (( stderr_fd )) && exec 2>&$stderr_fd
- (( err == 0 )) && return
-
- gitstatus_stop $name
-
- setopt prompt_percent no_prompt_subst no_prompt_bang
- print -Pru2 -- '[%F{red}ERROR%f]: gitstatus failed to initialize.'
- print -ru2 -- ''
- print -ru2 -- ' Your Git prompt may disappear or become slow.'
- if [[ -s $xtrace ]]; then
- print -ru2 -- ''
- print -ru2 -- " The content of ${(q-)xtrace} (gitstatus_start xtrace):"
- print -Pru2 -- '%F{yellow}'
- >&2 awk '{print " " $0}' <$xtrace
- print -Pru2 -- "%F{red} ^ this command failed ($err)%f"
- fi
- if [[ -s $daemon_log ]]; then
- print -ru2 -- ''
- print -ru2 -- " The content of ${(q-)daemon_log} (gitstatus daemon log):"
- print -Pru2 -- '%F{yellow}'
- >&2 awk '{print " " $0}' <$daemon_log
- print -Pnru2 -- '%f'
- fi
- if [[ $GITSTATUS_LOG_LEVEL == DEBUG ]]; then
- print -ru2 -- ''
- print -ru2 -- ' Your system information:'
- print -Pru2 -- '%F{yellow}'
- print -ru2 -- " zsh: $ZSH_VERSION"
- print -ru2 -- " uname -a: $(uname -a)"
- print -Pru2 -- '%f'
- print -ru2 -- ' If you need help, open an issue and attach this whole error message to it:'
- print -ru2 -- ''
- print -Pru2 -- ' %F{green}https://github.com/romkatv/gitstatus/issues/new%f'
- else
- print -ru2 -- ''
- print -ru2 -- ' Run the following command to retry with extra diagnostics:'
- print -Pru2 -- '%F{green}'
- local env="GITSTATUS_LOG_LEVEL=DEBUG"
- if [[ -n $GITSTATUS_NUM_THREADS ]]; then
- env+=" GITSTATUS_NUM_THREADS=${(q)GITSTATUS_NUM_THREADS}"
- fi
- if [[ -n $GITSTATUS_DAEMON ]]; then
- env+=" GITSTATUS_DAEMON=${(q)GITSTATUS_DAEMON}"
- fi
- print -nru2 -- " ${env} gitstatus_start ${(@q-)*}"
- print -Pru2 -- '%f'
- print -ru2 -- ''
- local zshrc=${(D)ZDOTDIR:-~}/.zshrc
- print -ru2 -- " If this command produces no output, add the following parameter to $zshrc:"
- print -ru2 -- ''
- print -Pru2 -- '%F{green} GITSTATUS_LOG_LEVEL=DEBUG%f'
- print -ru2 -- ''
- print -ru2 -- ' With this parameter gitstatus will print additional information on error.'
- fi
-
- return err
}
# Stops gitstatusd if it's running.
#
# Usage: gitstatus_stop NAME.
-function gitstatus_stop() {
+function gitstatus_stop"${1:-}"() {
emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
+ local fsuf=${${(%):-%N}#gitstatus_stop}
+
if (( ARGC != 1 )); then
print -ru2 -- "gitstatus_stop: exactly one positional argument is required"
return 1
@@ -729,8 +855,8 @@ function gitstatus_stop() {
local daemon_pid=${(P)daemon_pid_var}
local file_prefix=${(P)file_prefix_var}
- local cleanup=_gitstatus_cleanup_$name
- local process=_gitstatus_process_response_$name
+ local cleanup=_gitstatus_cleanup_$name-$fsuf
+ local process=_gitstatus_process_response_$name-$fsuf
if (( $+functions[$cleanup] )); then
add-zsh-hook -d zshexit $cleanup
@@ -752,16 +878,18 @@ function gitstatus_stop() {
unset $inflight_var $file_prefix_var $dirty_max_index_size_var
unset VCS_STATUS_RESULT
- _gitstatus_clear
+ _gitstatus_clear$fsuf
}
# Usage: gitstatus_check NAME.
#
# Returns 0 if and only if `gitstatus_start NAME` has succeeded previously.
# If it returns non-zero, gitstatus_query NAME is guaranteed to return non-zero.
-function gitstatus_check() {
+function gitstatus_check"${1:-}"() {
emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
+ local fsuf=${${(%):-%N}#gitstatus_check}
+
if (( ARGC != 1 )); then
print -ru2 -- "gitstatus_check: exactly one positional argument is required"
return 1
diff --git a/gitstatus/gitstatus.prompt.sh b/gitstatus/gitstatus.prompt.sh
new file mode 100644
index 00000000..f54c11ac
--- /dev/null
+++ b/gitstatus/gitstatus.prompt.sh
@@ -0,0 +1,111 @@
+# Simple Bash prompt with Git status.
+
+# Source gitstatus.plugin.sh from $GITSTATUS_DIR or from the same directory
+# in which the current script resides if the variable isn't set.
+if [[ -n "${GITSTATUS_DIR-}" ]]; then
+ source "$GITSTATUS_DIR" || return
+elif [[ "${BASH_SOURCE[0]}" == */* ]]; then
+ source "${BASH_SOURCE[0]%/*}/gitstatus.plugin.sh" || return
+else
+ source gitstatus.plugin.sh || return
+fi
+
+# Sets GITSTATUS_PROMPT to reflect the state of the current git repository.
+# The value is empty if not in a git repository. Forwards all arguments to
+# gitstatus_query.
+#
+# Example value of GITSTATUS_PROMPT: master โฃ42โก42 โ 42โข42 *42 merge ~42 +42 !42 ?42
+#
+# master current branch
+# โฃ42 local branch is 42 commits behind the remote
+# โก42 local branch is 42 commits ahead of the remote
+# โ 42 local branch is 42 commits behind the push remote
+# โข42 local branch is 42 commits ahead of the push remote
+# *42 42 stashes
+# merge merge in progress
+# ~42 42 merge conflicts
+# +42 42 staged changes
+# !42 42 unstaged changes
+# ?42 42 untracked files
+function gitstatus_prompt_update() {
+ GITSTATUS_PROMPT=""
+
+ gitstatus_query "$@" || return 1 # error
+ [[ "$VCS_STATUS_RESULT" == ok-sync ]] || return 0 # not a git repo
+
+ local reset=$'\001\e[0m\002' # no color
+ local clean=$'\001\e[38;5;076m\002' # green foreground
+ local untracked=$'\001\e[38;5;014m\002' # teal foreground
+ local modified=$'\001\e[38;5;011m\002' # yellow foreground
+ local conflicted=$'\001\e[38;5;196m\002' # red foreground
+
+ local p
+
+ local where # branch name, tag or commit
+ if [[ -n "$VCS_STATUS_LOCAL_BRANCH" ]]; then
+ where="$VCS_STATUS_LOCAL_BRANCH"
+ elif [[ -n "$VCS_STATUS_TAG" ]]; then
+ p+="${reset}#"
+ where="$VCS_STATUS_TAG"
+ else
+ p+="${reset}@"
+ where="${VCS_STATUS_COMMIT:0:8}"
+ fi
+
+ (( ${#where} > 32 )) && where="${where:0:12}โฆ${where: -12}" # truncate long branch names and tags
+ p+="${clean}${where}"
+
+ # โฃ42 if behind the remote.
+ (( VCS_STATUS_COMMITS_BEHIND )) && p+=" ${clean}โฃ${VCS_STATUS_COMMITS_BEHIND}"
+ # โก42 if ahead of the remote; no leading space if also behind the remote: โฃ42โก42.
+ (( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && p+=" "
+ (( VCS_STATUS_COMMITS_AHEAD )) && p+="${clean}โก${VCS_STATUS_COMMITS_AHEAD}"
+ # โ 42 if behind the push remote.
+ (( VCS_STATUS_PUSH_COMMITS_BEHIND )) && p+=" ${clean}โ ${VCS_STATUS_PUSH_COMMITS_BEHIND}"
+ (( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && p+=" "
+ # โข42 if ahead of the push remote; no leading space if also behind: โ 42โข42.
+ (( VCS_STATUS_PUSH_COMMITS_AHEAD )) && p+="${clean}โข${VCS_STATUS_PUSH_COMMITS_AHEAD}"
+ # *42 if have stashes.
+ (( VCS_STATUS_STASHES )) && p+=" ${clean}*${VCS_STATUS_STASHES}"
+ # 'merge' if the repo is in an unusual state.
+ [[ -n "$VCS_STATUS_ACTION" ]] && p+=" ${conflicted}${VCS_STATUS_ACTION}"
+ # ~42 if have merge conflicts.
+ (( VCS_STATUS_NUM_CONFLICTED )) && p+=" ${conflicted}~${VCS_STATUS_NUM_CONFLICTED}"
+ # +42 if have staged changes.
+ (( VCS_STATUS_NUM_STAGED )) && p+=" ${modified}+${VCS_STATUS_NUM_STAGED}"
+ # !42 if have unstaged changes.
+ (( VCS_STATUS_NUM_UNSTAGED )) && p+=" ${modified}!${VCS_STATUS_NUM_UNSTAGED}"
+ # ?42 if have untracked files. It's really a question mark, your font isn't broken.
+ (( VCS_STATUS_NUM_UNTRACKED )) && p+=" ${untracked}?${VCS_STATUS_NUM_UNTRACKED}"
+
+ GITSTATUS_PROMPT="${p}${reset}"
+}
+
+# Start gitstatusd in the background.
+gitstatus_stop && gitstatus_start -s -1 -u -1 -c -1 -d -1
+
+# On every prompt, fetch git status and set GITSTATUS_PROMPT.
+if [[ -z "${PROMPT_COMMAND[*]}" ]]; then
+ PROMPT_COMMAND=gitstatus_prompt_update
+elif [[ ! "${PROMPT_COMMAND[*]}" =~ [[:space:]\;]?gitstatus_prompt_update[[:space:]\;]? ]]; then
+ # Note: If PROMPT_COMMAND is an array, this will modify its first element.
+ PROMPT_COMMAND=$'gitstatus_prompt_update\n'"$PROMPT_COMMAND"
+fi
+
+# Retain 3 trailing components of the current directory.
+PROMPT_DIRTRIM=3
+
+# Enable promptvars so that ${GITSTATUS_PROMPT} in PS1 is expanded.
+shopt -s promptvars
+
+# Customize prompt. Put $GITSTATUS_PROMPT in it reflect git status.
+#
+# Example:
+#
+# user@host ~/projects/skynet master โก42
+# $ โ
+PS1='\[\033[01;32m\]\u@\h\[\033[00m\] ' # green user@host
+PS1+='\[\033[01;34m\]\w\[\033[00m\]' # blue current working directory
+PS1+='${GITSTATUS_PROMPT:+ $GITSTATUS_PROMPT}' # git status (requires promptvars option)
+PS1+='\n\[\033[01;$((31+!$?))m\]\$\[\033[00m\] ' # green/red (success/error) $/# (normal/root)
+PS1+='\[\e]0;\u@\h: \w\a\]' # terminal title: user@host: dir
diff --git a/gitstatus/gitstatus.prompt.zsh b/gitstatus/gitstatus.prompt.zsh
new file mode 100644
index 00000000..6ad64856
--- /dev/null
+++ b/gitstatus/gitstatus.prompt.zsh
@@ -0,0 +1,111 @@
+# Simple Zsh prompt with Git status.
+
+# Source gitstatus.plugin.zsh from $GITSTATUS_DIR or from the same directory
+# in which the current script resides if the variable isn't set.
+source "${GITSTATUS_DIR:-${${(%):-%x}:h}}/gitstatus.plugin.zsh" || return
+
+# Sets GITSTATUS_PROMPT to reflect the state of the current git repository. Empty if not
+# in a git repository. In addition, sets GITSTATUS_PROMPT_LEN to the number of columns
+# $GITSTATUS_PROMPT will occupy when printed.
+#
+# Example:
+#
+# GITSTATUS_PROMPT='master โฃ42โก42 โ 42โข42 *42 merge ~42 +42 !42 ?42'
+# GITSTATUS_PROMPT_LEN=39
+#
+# master current branch
+# โฃ42 local branch is 42 commits behind the remote
+# โก42 local branch is 42 commits ahead of the remote
+# โ 42 local branch is 42 commits behind the push remote
+# โข42 local branch is 42 commits ahead of the push remote
+# *42 42 stashes
+# merge merge in progress
+# ~42 42 merge conflicts
+# +42 42 staged changes
+# !42 42 unstaged changes
+# ?42 42 untracked files
+function gitstatus_prompt_update() {
+ emulate -L zsh
+ typeset -g GITSTATUS_PROMPT=''
+ typeset -gi GITSTATUS_PROMPT_LEN=0
+
+ # Call gitstatus_query synchronously. Note that gitstatus_query can also be called
+ # asynchronously; see documentation in gitstatus.plugin.zsh.
+ gitstatus_query 'MY' || return 1 # error
+ [[ $VCS_STATUS_RESULT == 'ok-sync' ]] || return 0 # not a git repo
+
+ local clean='%76F' # green foreground
+ local modified='%178F' # yellow foreground
+ local untracked='%39F' # blue foreground
+ local conflicted='%196F' # red foreground
+
+ local p
+
+ local where # branch name, tag or commit
+ if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then
+ where=$VCS_STATUS_LOCAL_BRANCH
+ elif [[ -n $VCS_STATUS_TAG ]]; then
+ p+='%f#'
+ where=$VCS_STATUS_TAG
+ else
+ p+='%f@'
+ where=${VCS_STATUS_COMMIT[1,8]}
+ fi
+
+ (( $#where > 32 )) && where[13,-13]="โฆ" # truncate long branch names and tags
+ p+="${clean}${where//\%/%%}" # escape %
+
+ # โฃ42 if behind the remote.
+ (( VCS_STATUS_COMMITS_BEHIND )) && p+=" ${clean}โฃ${VCS_STATUS_COMMITS_BEHIND}"
+ # โก42 if ahead of the remote; no leading space if also behind the remote: โฃ42โก42.
+ (( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && p+=" "
+ (( VCS_STATUS_COMMITS_AHEAD )) && p+="${clean}โก${VCS_STATUS_COMMITS_AHEAD}"
+ # โ 42 if behind the push remote.
+ (( VCS_STATUS_PUSH_COMMITS_BEHIND )) && p+=" ${clean}โ ${VCS_STATUS_PUSH_COMMITS_BEHIND}"
+ (( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && p+=" "
+ # โข42 if ahead of the push remote; no leading space if also behind: โ 42โข42.
+ (( VCS_STATUS_PUSH_COMMITS_AHEAD )) && p+="${clean}โข${VCS_STATUS_PUSH_COMMITS_AHEAD}"
+ # *42 if have stashes.
+ (( VCS_STATUS_STASHES )) && p+=" ${clean}*${VCS_STATUS_STASHES}"
+ # 'merge' if the repo is in an unusual state.
+ [[ -n $VCS_STATUS_ACTION ]] && p+=" ${conflicted}${VCS_STATUS_ACTION}"
+ # ~42 if have merge conflicts.
+ (( VCS_STATUS_NUM_CONFLICTED )) && p+=" ${conflicted}~${VCS_STATUS_NUM_CONFLICTED}"
+ # +42 if have staged changes.
+ (( VCS_STATUS_NUM_STAGED )) && p+=" ${modified}+${VCS_STATUS_NUM_STAGED}"
+ # !42 if have unstaged changes.
+ (( VCS_STATUS_NUM_UNSTAGED )) && p+=" ${modified}!${VCS_STATUS_NUM_UNSTAGED}"
+ # ?42 if have untracked files. It's really a question mark, your font isn't broken.
+ (( VCS_STATUS_NUM_UNTRACKED )) && p+=" ${untracked}?${VCS_STATUS_NUM_UNTRACKED}"
+
+ GITSTATUS_PROMPT="${p}%f"
+
+ # The length of GITSTATUS_PROMPT after removing %f and %F.
+ GITSTATUS_PROMPT_LEN="${(m)#${${GITSTATUS_PROMPT//\%\%/x}//\%(f|<->F)}}"
+}
+
+# Start gitstatusd instance with name "MY". The same name is passed to
+# gitstatus_query in gitstatus_prompt_update. The flags with -1 as values
+# enable staged, unstaged, conflicted and untracked counters.
+gitstatus_stop 'MY' && gitstatus_start -s -1 -u -1 -c -1 -d -1 'MY'
+
+# On every prompt, fetch git status and set GITSTATUS_PROMPT.
+autoload -Uz add-zsh-hook
+add-zsh-hook precmd gitstatus_prompt_update
+
+# Enable/disable the right prompt options.
+setopt no_prompt_bang prompt_percent prompt_subst
+
+# Customize prompt. Put $GITSTATUS_PROMPT in it to reflect git status.
+#
+# Example:
+#
+# user@host ~/projects/skynet master โก42
+# % โ
+#
+# The current directory gets truncated from the left if the whole prompt doesn't fit on the line.
+PROMPT='%70F%n@%m%f ' # green user@host
+PROMPT+='%39F%$((-GITSTATUS_PROMPT_LEN-1))<โฆ<%~%<<%f' # blue current working directory
+PROMPT+='${GITSTATUS_PROMPT:+ $GITSTATUS_PROMPT}' # git status
+PROMPT+=$'\n' # new line
+PROMPT+='%F{%(?.76.196)}%#%f ' # %/# (normal/root); green/red (ok/error)
diff --git a/gitstatus/install b/gitstatus/install
new file mode 100755
index 00000000..76f339e0
--- /dev/null
+++ b/gitstatus/install
@@ -0,0 +1,476 @@
+#!/bin/sh
+#
+# This script does not have a stable API.
+
+_gitstatus_install_daemon_found() {
+ local installed="$1"
+ shift
+ [ $# = 0 ] || "$@" "$daemon" "$version" "$installed"
+}
+
+_gitstatus_install_main() {
+ if [ -n "${ZSH_VERSION:-}" ]; then
+ emulate -L sh -o no_unset
+ else
+ set -u
+ fi
+
+ local argv1="$1"
+ shift
+
+ local no_check= no_install= uname_s= uname_m= gitstatus_dir= dl_status= e=
+ local opt= OPTARG= OPTIND=1
+
+ while getopts ':s:m:d:p:e:fnh' opt "$@"; do
+ case "$opt" in
+ h)
+ command cat <<\END
+Usage: install [-s KERNEL] [-m ARCH] [-d DIR] [-p CMD] [-e ERRFD] [-f|-n] [-- CMD [ARG]...]
+
+If positional arguments are specified, call this on success:
+
+ CMD [ARG]... DAEMON VERSION INSTALLED
+
+DAEMON is path to gitstatusd. VERSION is a glob pattern for the
+version this daemon should support; it's supposed to be passed as
+-G to gitstatusd. INSTALLED is 1 if gitstatusd has just been
+downloaded and 0 otherwise.
+
+Options:
+
+ -s KERNEL use this instead of lowercase `uname -s`
+ -m ARCH use this instead of lowercase `uname -m`
+ -d DIR use this instead of `dirname "$0"`
+ -p CMD eval this every second while downloading gitstatusd
+ -e ERRFD write error messages to this file descriptor
+ -f download gitstatusd even if there is one locally
+ -n do not download gitstatusd (fail instead)
+END
+ return
+ ;;
+ n)
+ if [ -n "$no_install" ]; then
+ >&2 echo "[gitstatus] error: duplicate option: -$opt"
+ return 1
+ fi
+ no_install=1
+ ;;
+ f)
+ if [ -n "$no_check" ]; then
+ >&2 echo "[gitstatus] error: duplicate option: -$opt"
+ return 1
+ fi
+ no_check=1
+ ;;
+ d)
+ if [ -n "$gitstatus_dir" ]; then
+ >&2 echo "[gitstatus] error: duplicate option: -$opt"
+ return 1
+ fi
+ if [ -z "$OPTARG" ]; then
+ >&2 echo "[error] incorrect value of -$opt: $OPTARG"
+ return 1
+ fi
+ gitstatus_dir="$OPTARG"
+ ;;
+ p)
+ if [ -n "$dl_status" ]; then
+ >&2 echo "[gitstatus] error: duplicate option: -$opt"
+ return 1
+ fi
+ if [ -z "$OPTARG" ]; then
+ >&2 echo "[error] incorrect value of -$opt: $OPTARG"
+ return 1
+ fi
+ dl_status="$OPTARG"
+ ;;
+ e)
+ if [ -n "$e" ]; then
+ >&2 echo "[gitstatus] error: duplicate option: -$opt"
+ return 1
+ fi
+ if [ -z "$OPTARG" ]; then
+ >&2 echo "[error] incorrect value of -$opt: $OPTARG"
+ return 1
+ fi
+ e="$OPTARG"
+ ;;
+ m)
+ if [ -n "$uname_m" ]; then
+ >&2 echo "[gitstatus] error: duplicate option: -$opt"
+ return 1
+ fi
+ if [ -z "$OPTARG" ]; then
+ >&2 echo "[error] incorrect value of -$opt: $OPTARG"
+ return 1
+ fi
+ uname_m="$OPTARG"
+ ;;
+ s)
+ if [ -n "$uname_s" ]; then
+ >&2 echo "[gitstatus] error: duplicate option: -$opt"
+ return 1
+ fi
+ if [ -z "$OPTARG" ]; then
+ >&2 echo "[error] incorrect value of -$opt: $OPTARG"
+ return 1
+ fi
+ uname_s="$OPTARG"
+ ;;
+ \?) >&2 echo "[gitstatus] error: invalid option: -$OPTARG" ; return 1;;
+ :) >&2 echo "[gitstatus] error: missing required argument: -$OPTARG"; return 1;;
+ *) >&2 echo "[gitstatus] internal error: unhandled option: -$opt" ; return 1;;
+ esac
+ done
+
+ shift "$((OPTIND - 1))"
+
+ : "${e:=2}"
+ : "${gitstatus_dir:=$argv1}"
+
+ if [ -n "$no_check" -a -n "$no_install" ]; then
+ >&2 echo "[gitstatus] error: incompatible options: -f, -n"
+ return 1
+ fi
+
+ if [ -z "$uname_s" ]; then
+ uname_s="$(command uname -s)" || return
+ uname_s="$(printf '%s' "$uname_s" | command tr '[A-Z]' '[a-z]')" || return
+ fi
+ if [ -z "$uname_m" ]; then
+ uname_m="$(command uname -m)" || return
+ uname_m="$(printf '%s' "$uname_m" | command tr '[A-Z]' '[a-z]')" || return
+ fi
+
+ local daemon="${GITSTATUS_DAEMON:-}"
+ local cache_dir="${GITSTATUS_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/gitstatus}"
+
+ if [ -z "$no_check" ]; then
+ if [ -n "${daemon##/*}" ]; then
+ >&2 echo "[gitstatus] error: GITSTATUS_DAEMON is not absolute path: $daemon"
+ return 1
+ fi
+ if [ -z "$daemon" -a -e "$gitstatus_dir"/usrbin/gitstatusd ]; then
+ daemon="$gitstatus_dir"/usrbin/gitstatusd
+ fi
+ if [ -n "$daemon" ]; then
+ local gitstatus_version= libgit2_version=
+ if ! . "$gitstatus_dir"/build.info; then
+ >&2 echo "[gitstatus] internal error: failed to source build.info"
+ return 1
+ fi
+ if [ -z "$gitstatus_version" ]; then
+ >&2 echo "[gitstatus] internal error: empty gitstatus_version in build.info"
+ return 1
+ fi
+ local version="$gitstatus_version"
+ _gitstatus_install_daemon_found 0 "$@"
+ return
+ fi
+ fi
+
+ while IFS= read -r line; do
+ line="${line###*}"
+ [ -n "$line" ] || continue
+
+ local uname_s_glob= uname_m_glob= file= version= sha256=
+ eval "$line" || return
+
+ if [ -z "$uname_s_glob" -o \
+ -z "$uname_m_glob" -o \
+ -z "$file" -o \
+ -z "$version" -o \
+ -z "$sha256" ]; then
+ >&2 echo "[gitstatus] internal error: invalid install.info line: $line"
+ return 1
+ fi
+
+ case "$uname_s" in
+ $uname_s_glob) ;;
+ *) continue;;
+ esac
+ case "$uname_m" in
+ $uname_m_glob) ;;
+ *) continue;;
+ esac
+
+ # Found a match. The while loop will terminate during this iteration.
+
+ if [ -z "$no_check" ]; then
+ # Check if a suitable gitstatusd already exists.
+ local daemon="$gitstatus_dir"/usrbin/"$file"
+ if [ ! -e "$daemon" ]; then
+ daemon="$cache_dir"/"$file"
+ [ -e "$daemon" ] || daemon=
+ fi
+ if [ -n "$daemon" ]; then
+ _gitstatus_install_daemon_found 0 "$@"
+ return
+ fi
+ fi
+
+ # No suitable gitstatusd exists. Need to download.
+
+ if [ -n "$no_install" ]; then
+ >&2 echo "[gitstatus] error: no gitstatusd found and installation is disabled"
+ return 1
+ fi
+
+ local daemon="$cache_dir"/"$file"
+
+ if [ -n "${cache_dir##/*}" ]; then
+ >&2 echo "[gitstatus] error: GITSTATUS_CACHE_DIR is not absolute: $cache_dir"
+ return 1
+ fi
+ if [ ! -d "$cache_dir" ] && ! mkdir -p -- "$cache_dir" || [ ! -w "$cache_dir" ]; then
+ local dir="$cache_dir"
+ while true; do
+ if [ -e "$dir" ]; then
+ if [ ! -d "$dir" ]; then
+ >&"$e" printf 'Not a directory: \033[4;31m%s\033[0m\n' "$dir"
+ >&"$e" printf '\n'
+ >&"$e" printf 'Delete it, then restart your shell.\n'
+ elif [ ! -w "$dir" ]; then
+ >&"$e" printf 'Directory is not writable: \033[4;31m%s\033[0m\n' "$dir"
+ >&"$e" printf '\n'
+ >&"$e" printf 'Make it writable, then restart your shell.\n'
+ fi
+ break
+ fi
+ if [ "$dir" = / ] || [ "$dir" = . ]; then
+ break
+ fi
+ dir="$(dirname -- "$dir")"
+ done
+ return 1
+ fi
+
+ if [ -n "${TMPDIR-}" -a '(' '(' -d "${TMPDIR-}" -a -w "${TMPDIR-}" ')' -o '!' '(' -d /tmp -a -w /tmp ')' ')' ]; then
+ local tmp="$TMPDIR"
+ else
+ local tmp=/tmp
+ fi
+ if ! command -v mktemp >/dev/null 2>&1 ||
+ ! tmpdir="$(command mktemp -d "$tmp"/gitstatus-install.XXXXXXXXXX)"; then
+ tmpdir="$tmp/gitstatus-install.tmp.$$"
+ if ! mkdir -p -- "$tmpdir"; then
+ if [ "$tmp" = /tmp ]; then
+ local label='directory'
+ else
+ local label='directory (\033[1mTMPDIR\033[m)'
+ fi
+ if [ ! -e "$tmp" ]; then
+ >&"$e" printf 'Temporary '"$label"' does not exist: \033[4;31m%s\033[0m\n' "$tmp"
+ >&"$e" printf '\n'
+ >&"$e" printf 'Create it, then restart your shell.\n'
+ elif [ ! -d "$tmp" ]; then
+ >&"$e" printf 'Not a '"$label"': \033[4;31m%s\033[0m\n' "$tmp"
+ >&"$e" printf '\n'
+ >&"$e" printf 'Make it a directory, then restart your shell.\n'
+ elif [ ! -w "$tmp" ]; then
+ >&"$e" printf 'Temporary '"$label"' is not writable: \033[4;31m%s\033[0m\n' "$tmp"
+ >&"$e" printf '\n'
+ >&"$e" printf 'Make it writable, then restart your shell.\n'
+ fi
+ return 1
+ fi
+ fi
+
+ if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
+ >&"$e" printf 'Please install \033[32mcurl\033[0m or \033[32mwget\033[0m, then restart your shell.\n'
+ return 1
+ fi
+
+ (
+ run_cmd() {
+ command -v "$1" >/dev/null 2>/dev/null || return 127
+ local trapped= pid die ret
+ trap 'trapped=1' $sig
+ # The only reason for suppressing stderr is that `curl -f` cannot be silenced:
+ # `-s` doesn't work despite what the docs say.
+ command "$@" 2>/dev/null &
+ ret="$?"
+ if [ "$ret" = 0 ]; then
+ pid="$!"
+ die="trap - $sig; kill -- $pid 2>/dev/null; wait -- $pid 2>/dev/null; exit 1"
+ trap "$die" $sig
+ [ -z "$trapped" ] || eval "$die"
+ wait -- "$pid" 2>/dev/null
+ ret="$?"
+ fi
+ trap - $sig
+ [ -z "$trapped" ] || exit
+ return "$ret"
+ }
+
+ check_sha256() {
+ local data_file="$tmpdir"/"$1".tar.gz
+ local hash_file="$tmpdir"/"$1".tar.gz.sha256
+ local hash=
+ {
+ command -v shasum >/dev/null 2>/dev/null &&
+ run_cmd shasum -b -a 256 -- "$data_file" >"$hash_file" /dev/null 2>/dev/null &&
+ run_cmd sha256sum -b -- "$data_file" >"$hash_file" /dev/null 2>/dev/null &&
+ run_cmd sha256 -- "$data_file" >"$hash_file" /dev/null 2>/dev/null; then
+ if ! run_cmd sleep "$1"; then
+ echo -n >"$tmpdir"/"$1".status
+ return 1
+ fi
+ fi
+ local cmd part url ret
+ for cmd in 'curl -kfsSL' 'wget -qO-' 'curl -q -kfsSL' 'wget --no-config -qO-'; do
+ part=0
+ while true; do
+ if [ "$part" = 2 ]; then
+ ret=1
+ break
+ elif [ "$part" = 0 ]; then
+ url="$2"
+ else
+ url="$2"."$part"
+ fi
+ run_cmd $cmd -- "$url" >>"$tmpdir"/"$1".tar.gz
+ ret="$?"
+ [ "$ret" = 0 ] || break
+ check_sha256 "$1" && break
+ part=$((part+1))
+ done
+ [ "$ret" = 0 ] && break
+ run_cmd rm -f -- "$tmpdir"/"$1".tar.gz && continue
+ ret="$?"
+ break
+ done
+ echo -n >"$tmpdir"/"$1".status
+ return "$ret"
+ }
+
+ local trapped=
+ trap 'trapped=1' $sig
+ fetch 1 "$url1" &
+ local pid1="$!"
+ fetch 2 "$url2" &
+ local pid2="$!"
+
+ local die="trap - $sig; kill -- $pid1 $pid2 2>/dev/null; wait -- $pid1 $pid2 2>/dev/null; exit 1"
+ trap "$die" $sig
+ [ -z "$trapped" ] || eval "$die"
+
+ local n=
+ while true; do
+ [ -z "$dl_status" ] || eval "$dl_status" || eval "$die"
+ if command -v sleep >/dev/null 2>/dev/null; then
+ command sleep 1
+ elif command -v true >/dev/null 2>/dev/null; then
+ command true
+ fi
+ if [ -n "$pid1" -a -e "$tmpdir"/1.status ]; then
+ wait -- "$pid1" 2>/dev/null
+ local ret="$?"
+ pid1=
+ if [ "$ret" = 0 ]; then
+ if [ -n "$pid2" ]; then
+ kill -- "$pid2" 2>/dev/null
+ wait -- "$pid2" 2>/dev/null
+ fi
+ n=1
+ break
+ elif [ -z "$pid2" ]; then
+ break
+ else
+ die="trap - $sig; kill -- $pid2 2>/dev/null; wait -- $pid2 2>/dev/null; exit 1"
+ trap "$die" $sig
+ fi
+ elif [ -n "$pid2" -a -e "$tmpdir"/2.status ]; then
+ wait -- "$pid2" 2>/dev/null
+ local ret="$?"
+ pid2=
+ if [ "$ret" = 0 ]; then
+ if [ -n "$pid1" ]; then
+ kill -- "$pid1" 2>/dev/null
+ wait -- "$pid1" 2>/dev/null
+ fi
+ n=2
+ break
+ elif [ -z "$pid1" ]; then
+ break
+ else
+ die="trap - $sig; kill -- $pid1 2>/dev/null; wait -- $pid1 2>/dev/null; exit 1"
+ trap "$die" $sig
+ fi
+ fi
+ done
+
+ trap - $sig
+
+ if [ -z "$n" ]; then
+ >&"$e" printf 'Failed to download \033[32m%s\033[0m from any mirror:\n' "$file"
+ >&"$e" printf '\n'
+ >&"$e" printf ' 1. \033[4m%s\033[0m\n' "$url1"
+ >&"$e" printf ' 2. \033[4m%s\033[0m\n' "$url2"
+ >&"$e" printf '\n'
+ >&"$e" printf 'Check your internet connection, then restart your shell.\n'
+ exit 1
+ fi
+
+ command tar -C "$tmpdir" -xzf "$tmpdir"/"$n".tar.gz || exit
+
+ local tmpfile
+ if ! command -v mktemp >/dev/null 2>&1 ||
+ ! tmpfile="$(command mktemp "$cache_dir"/gitstatusd.XXXXXXXXXX)"; then
+ tmpfile="$cache_dir"/gitstatusd.tmp.$$
+ fi
+
+ command mv -f -- "$tmpdir"/"$file" "$tmpfile" || exit
+ command mv -f -- "$tmpfile" "$cache_dir"/"$file" && exit
+ command rm -f -- "$cache_dir"/"$file"
+ command mv -f -- "$tmpfile" "$cache_dir"/"$file" && exit
+ command rm -f -- "$tmpfile"
+ exit 1
+ )
+
+ local ret="$?"
+ command rm -rf -- "$tmpdir"
+ [ "$ret" = 0 ] || return
+
+ _gitstatus_install_daemon_found 1 "$@"
+ return
+ done <"$gitstatus_dir"/install.info
+
+ >&"$e" printf 'There is no prebuilt \033[32mgitstatusd\033[0m for \033[1m%s\033[0m.\n' "$uname_s $uname_m"
+ >&"$e" printf '\n'
+ >&"$e" printf 'See: \033[4mhttps://github.com/romkatv/gitstatus#compiling\033[0m\n'
+ return 1
+}
+
+if [ -z "${0##*/*}" ]; then
+ _gitstatus_install_main "${0%/*}" "$@"
+else
+ _gitstatus_install_main . "$@"
+fi
diff --git a/gitstatus/install.info b/gitstatus/install.info
new file mode 100644
index 00000000..45807be4
--- /dev/null
+++ b/gitstatus/install.info
@@ -0,0 +1,34 @@
+# 3
+#
+# This file is used by ./install and indirectly by shell bindings.
+#
+# The first line is read by powerlevel10k instant prompt. It must
+# be updated whenever the content of this file changes. The actual
+# value doesn't matter as long as it's unique. Consecutive integers
+# work fine.
+
+# Official gitstatusd binaries.
+uname_s_glob="cygwin_nt-10.0"; uname_m_glob="i686"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="5a8a809dcebdb6aa9b47d37e086c0485424a9d9c136770eec3c26cedf5bb75e3";
+uname_s_glob="cygwin_nt-10.0"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="c84cade0d6b86e04c27a6055f45851f6b46d6b88ba58772f7ca8ef4d295c800f";
+uname_s_glob="darwin"; uname_m_glob="arm64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="eae979e990ca37c56ee39fadd0c3f392cbbd0c6bdfb9a603010be60d9e48910a";
+uname_s_glob="darwin"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="9fd3913ec1b6b856ab6e08a99a2343f0e8e809eb6b62ca4b0963163656c668e6";
+uname_s_glob="freebsd"; uname_m_glob="amd64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="8e57ad642251e5acfa430aed82cd4ffe103db0bfadae4a15ccaf462c455d0442";
+uname_s_glob="linux"; uname_m_glob="aarch64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="32b57eb28bf6d80b280e4020a0045184f8ca897b20b570c12948aa6838673225";
+uname_s_glob="linux"; uname_m_glob="armv6l"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="4bf5a0d0a082f544a48536ad3675930d5d2cc6a8cf906710045e0788f51192b3";
+uname_s_glob="linux"; uname_m_glob="armv7l"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="2b9deb29f86c8209114b71b94fc2e1ed936a1658808a1bee46f4a82fd6a1f8cc";
+uname_s_glob="linux"; uname_m_glob="armv8l"; file="gitstatusd-${uname_s}-aarch64"; version="v1.5.4"; sha256="32b57eb28bf6d80b280e4020a0045184f8ca897b20b570c12948aa6838673225";
+uname_s_glob="linux"; uname_m_glob="i686"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="56d55e2e9a202d3072fa612d8fa1faa61243ffc86418a7fa64c2c9d9a82e0f64";
+uname_s_glob="linux"; uname_m_glob="ppc64le"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="1afd072c8c26ef6ec2d9ac11cef96c84cd6f10e859665a6ffcfb6112c758547e";
+uname_s_glob="linux"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="9633816e7832109e530c9e2532b11a1edae08136d63aa7e40246c0339b7db304";
+uname_s_glob="msys_nt-10.0"; uname_m_glob="i686"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="7f9b849fc52e7a95b9b933e25121ad5ae990a1871aad6616922ad7bcf1eebf20";
+uname_s_glob="msys_nt-10.0"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="5d3c626b5ee564dbc13ddba89752dc58b0efe925b26dbd8b2304849d9ba01732";
+
+# Fallbacks to official gitstatusd binaries.
+uname_s_glob="cygwin_nt-*"; uname_m_glob="i686"; file="gitstatusd-cygwin_nt-10.0-${uname_m}"; version="v1.5.2"; sha256="5a8a809dcebdb6aa9b47d37e086c0485424a9d9c136770eec3c26cedf5bb75e3";
+uname_s_glob="cygwin_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-cygwin_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="c84cade0d6b86e04c27a6055f45851f6b46d6b88ba58772f7ca8ef4d295c800f";
+uname_s_glob="mingw32_nt-*"; uname_m_glob="i686"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="7f9b849fc52e7a95b9b933e25121ad5ae990a1871aad6616922ad7bcf1eebf20";
+uname_s_glob="mingw32_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="5d3c626b5ee564dbc13ddba89752dc58b0efe925b26dbd8b2304849d9ba01732";
+uname_s_glob="mingw64_nt-*"; uname_m_glob="i686"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="7f9b849fc52e7a95b9b933e25121ad5ae990a1871aad6616922ad7bcf1eebf20";
+uname_s_glob="mingw64_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="5d3c626b5ee564dbc13ddba89752dc58b0efe925b26dbd8b2304849d9ba01732";
+uname_s_glob="msys_nt-*"; uname_m_glob="i686"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="7f9b849fc52e7a95b9b933e25121ad5ae990a1871aad6616922ad7bcf1eebf20";
+uname_s_glob="msys_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="5d3c626b5ee564dbc13ddba89752dc58b0efe925b26dbd8b2304849d9ba01732";
diff --git a/gitstatus/mbuild b/gitstatus/mbuild
new file mode 100755
index 00000000..40316fdf
--- /dev/null
+++ b/gitstatus/mbuild
@@ -0,0 +1,406 @@
+#!/usr/bin/env zsh
+#
+# This script does not have a stable API.
+#
+# Usage: mbuild [-b git-ref] [kernel-arch]...
+#
+# Builds a bunch of gitstatusd-* binaries. Without arguments builds binaries
+# for all platforms. git-ref defaults to master.
+#
+# Before using this script you need to set up build servers and list them
+# in ~/.ssh/config. There should be a Host entry for every value of `assets`
+# association defined below. VMs and cloud instances work as well as physical
+# machines, including localhost. As long as the machine has been set up as
+# described below and you can SSH to it without password, it should work.
+#
+# ===[ Build Server Setup ]===
+#
+# Linux
+#
+# - Install docker.
+# $ apt install docker.io # adjust appropriately if there is no `apt`
+# $ usermod -aG docker $USER # not needed if going to build as root
+# - Install git.
+# $ apt install git # adjust appropriately if there is no `apt`
+#
+# macOS
+#
+# - Install compiler tools:
+# $ xcode-select --install
+# - Install homebrew: https://brew.sh/.
+# $ bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
+#
+# FreeBSD
+#
+# - Install git.
+# $ pkg install git
+#
+# Windows
+#
+# - Disable Windows Defender (optional).
+# ps> Set-MpPreference -DisableRealtimeMonitoring $true
+# - Install 64-bit and 32-bit msys2: https://www.msys2.org/wiki/MSYS2-installation/.
+# - Open each of them after installation, type `pacman -Syu --noconfirm` and close the window.
+# - Then run in powershell while having no msys2 or cygwin windows open:
+# ps> C:\msys32\autorebase.bat
+# ps> C:\msys64\autorebase.bat
+# - Install 64-bit and 32-bit cygwin: https://cygwin.com/install.html.
+# - Choose to install 32-bit to c:/cygwin32 instead of the default c:/cygwin.
+# - Select these packages: binutils, cmake, gcc-core, gcc-g++, git, make, perl, wget.
+#
+# IMPORTANT: Install msys2 and cygwin one at a time.
+#
+# IMPORTANT: msys2 builder can reboot the build machine.
+#
+# Option 1: OpenSSH for Windows
+#
+# - Install OpenSSH: https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse.
+# ps> Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
+# ps> Start-Service sshd
+# ps> Set-Service -Name sshd -StartupType 'Automatic'
+# - Enable publickey authentication: https://stackoverflow.com/a/50502015/1095235.
+# ps> cd $env:USERPROFILE
+# ps> mkdir .ssh
+# ps> notepad.exe .ssh/authorized_keys
+# - Paste your public key, save, close.
+# ps> icacls .ssh/authorized_keys /inheritance:r
+# ps> notepad.exe C:\ProgramData\ssh\sshd_config
+# - Comment out these two lines, save, close:
+# # Match Group administrators
+# # AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys
+# ps> Restart-Service sshd
+#
+# Option 2: OpenSSH from WSL
+#
+# - Install WSL.
+# - Install Ubuntu.
+# - Install sshd.
+# $ apt install openssh-server
+# $ dpkg-reconfigure openssh-server
+# $ cat >/etc/ssh/sshd_config <<\END
+# ClientAliveInterval 60
+# AcceptEnv TERM LANG LC_*
+# PermitRootLogin no
+# AllowTcpForwarding no
+# AllowAgentForwarding no
+# AllowStreamLocalForwarding no
+# AuthenticationMethods publickey
+# END
+# service ssh --full-restart
+# - Add your public ssh key to ~/.ssh/authorized_keys.
+# - Make `sshd` start when Windows boots.
+
+'emulate' '-L' 'zsh' '-o' 'no_aliases' '-o' 'err_return'
+setopt no_unset extended_glob pipe_fail prompt_percent typeset_silent \
+ no_prompt_subst no_prompt_bang pushd_silent warn_create_global
+
+if [[ $ZSH_VERSION != (5.<1->*|<6->.*) || $ZSH_VERSION == 5.4(|.*) ]]; then
+ print -ru2 -- "[error] unsupported zsh version: $ZSH_VERSION"
+ return 1
+fi
+
+zmodload zsh/system
+
+local -r git_url='https://github.com/romkatv/gitstatus.git'
+
+local -rA assets=(
+ # target kernel-arch hostname of the build machine
+ cygwin_nt-10.0-i686 build-windows-x86_64
+ cygwin_nt-10.0-x86_64 build-windows-x86_64
+ msys_nt-10.0-i686 build-windows-x86_64
+ msys_nt-10.0-x86_64 build-windows-x86_64
+ darwin-arm64 build-macos-arm64
+ darwin-x86_64 build-macos-x86_64
+ freebsd-amd64 build-freebsd-amd64
+ linux-aarch64 build-linux-aarch64
+ linux-armv6l build-linux-armv7l
+ linux-armv7l build-linux-armv7l
+ linux-i686 build-linux-x86_64
+ linux-ppc64le build-linux-ppc64le
+ linux-x86_64 build-linux-x86_64
+)
+
+local -rA protocol=(
+ 'cygwin_nt-10.0-*' windows
+ 'msys_nt-10.0-*' windows
+ 'darwin-*' unix
+ 'freebsd-*' unix
+ 'linux-*' unix
+)
+
+local -r rootdir=${ZSH_SCRIPT:h}
+local -r logs=$rootdir/logs
+local -r locks=$rootdir/locks
+local -r binaries=$rootdir/usrbin
+
+function usage() {
+ print -r -- 'usage: mbuild [-b REF] [KERNEL-ARCH]...'
+}
+
+local OPTARG opt git_ref=master
+local -i OPTIND
+while getopts ":b:h" opt; do
+ case $opt in
+ h) usage; return 0;;
+ b) [[ -n $OPTARG ]]; git_ref=$OPTARG;;
+ \?) print -ru2 -- "mbuild: invalid option: -$OPTARG" ; return 1;;
+ :) print -ru2 -- "mbuild: missing required argument: -$OPTARG"; return 1;;
+ *) print -ru2 -- "mbuild: invalid option: -$opt" ; return 1;;
+ esac
+done
+
+shift $((OPTIND - 1))
+
+(( $# )) || set -- ${(ko)assets}
+set -- ${(u)@}
+
+local platform
+for platform; do
+ if (( ! $+assets[$platform] )); then
+ print -ru2 -- "mbuild: invalid platform: $platform"
+ return 1
+ fi
+done
+
+local build='
+ rm -rf gitstatus
+ git clone --recursive --shallow-submodules --depth=1 -b '$git_ref' '$git_url'
+ cd gitstatus
+ if command -v zsh >/dev/null 2>&1; then
+ sh=zsh
+ elif command -v dash >/dev/null 2>&1; then
+ sh=dash
+ elif command -v ash >/dev/null 2>&1; then
+ sh=ash
+ else
+ sh=sh
+ fi
+ $sh -x ./build -m '
+
+function build-unix() {
+ local intro flags=(-sw)
+ case $2 in
+ linux-ppc64le) ;;
+ linux-*) flags+=(-d docker);;
+ darwin-arm64) intro='PATH="/opt/homebrew/bin:$PATH"';;
+ darwin-*) intro='PATH="/usr/local/bin:$PATH"';;
+ esac
+ ssh $1 -- /bin/sh -uex <<<"
+ $intro
+ cd /tmp
+ $build ${2##*-} ${(j: :)${(@q)flags}}"
+ scp $1:/tmp/gitstatus/usrbin/gitstatusd $binaries/gitstatusd-$2
+}
+
+function build-windows() {
+ local shell=$(ssh $1 'echo $0')
+ if [[ $shell == '$0'* ]]; then
+ local c='c:'
+ else
+ local c='/mnt/c'
+ fi
+
+ local tmp env bin intro flags=(-w)
+ case $2 in
+ cygwin_nt-10.0-i686) bin='cygwin32/bin' ;|
+ cygwin_nt-10.0-x86_64) bin='cygwin64/bin' ;|
+ msys_nt-10.0-i686) bin='msys32/usr/bin';|
+ msys_nt-10.0-x86_64) bin='msys64/usr/bin';|
+ cygwin_nt-10.0-*)
+ tmp='/cygdrive/c/tmp'
+ ;|
+ msys_nt-10.0-*)
+ tmp='/c/tmp'
+ env='MSYSTEM=MSYS'
+ # TODO: fix this (some errors about PGP keys).
+ # flags+=(-s)
+ # intro='pacman -S --needed --noconfirm git; '
+ intro+='PATH="$PATH:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl"'
+ while true; do
+ # TODO: run autorebase only when getting an error that can be fixed by autorebasing.
+ break
+ local out
+ out="$(ssh $1 cmd.exe "$c/${bin%%/*}/autorebase.bat" 2>&1)"
+ [[ $out == *"The following DLLs couldn't be rebased"* ]] || break
+ # Reboot to get rid of whatever is using those DLLs.
+ ssh $1 powershell.exe <<<'Restart-Computer -Force' || true
+ sleep 30
+ while ! ssh $1 <<<''; do sleep 5; done
+ done
+ () {
+ while true; do
+ # TODO: fix this (some errors about PGP keys).
+ break
+ local -i fd
+ exec {fd}< <(
+ ssh $1 $c/$bin/env.exe $env c:/$bin/bash.exe -l 2>&1 <<<"
+ pacman -Syu --noconfirm
+ exit")
+ {
+ local line
+ while true; do
+ IFS= read -u $fd -r line || return 0
+ if [[ $line == *"warning: terminate MSYS2"* ]]; then
+ # At this point the machine is hosed. A rogue process with a corrupted name
+ # is eating all CPU. The top SSH connection won't terminate on its own.
+ ssh $1 powershell.exe <<<'Restart-Computer -Force' || true
+ sleep 30
+ while ! ssh $1 <<<''; do sleep 5; done
+ break
+ fi
+ done
+ } always {
+ exec {fd}<&-
+ kill -- -$sysparams[procsubstpid] 2>/dev/null || true
+ }
+ done
+ } "$@"
+ ;|
+ esac
+
+ ssh $1 $c/$bin/env.exe $env c:/$bin/bash.exe -l <<<"
+ set -uex
+ $intro
+ mkdir -p -- $tmp
+ cd -- $tmp
+ $build ${2##*-} ${(j: :)${(@q)flags}}
+ exit"
+ scp $1:$c/tmp/gitstatus/usrbin/gitstatusd $binaries/gitstatusd-$2
+ chmod +x $binaries/gitstatusd-$2
+}
+
+if [[ -r /proc/version && "$(/dev/null
+ (
+ trap '' TERM PIPE
+ local fd
+ while true; do
+ sysopen -wo create,excl -u fd -- $1 && break
+ sleep 1
+ done
+ exec {fd}>&-
+ while true; do
+ print || break
+ done
+ rm -- $1
+ ) &!
+ )
+ local REPLY
+ IFS= read -ru $fd
+ }
+else
+ function flock() {
+ : >>$1
+ zsystem flock $1
+ }
+fi
+
+function build() (
+ setopt xtrace
+ local platform=$1
+ local machine=$assets[$platform]
+ flock $locks/$machine
+ build-${protocol[(k)$platform]} $machine $platform
+ local tmp=gitstatusd-$platform.tmp.$$.tar.gz
+ ( cd -q -- $binaries; tar --owner=0 --group=0 -I 'gzip -9' -cf $tmp gitstatusd-$platform )
+ mv -f -- $binaries/$tmp $binaries/gitstatusd-$platform.tar.gz
+ # Make sure the last command is a built-in (important for flock).
+ :
+)
+
+function mbuild() {
+ local platform pid pids=()
+ for platform; do
+ build $platform &>$logs/$platform &
+ print -r -- "starting build for $platform on $assets[$platform] (pid $!)"
+ pids+=($platform $!)
+ done
+ local failed=()
+ for platform pid in $pids; do
+ print -rn -- "$platform => "
+ if wait $pid; then
+ print -r -- "ok"
+ else
+ print -r -- "error"
+ failed+=$platform
+ fi
+ done
+ (( $#failed )) || return 0
+ print
+ print -r -- "Error logs:"
+ print
+ for platform in $failed; do
+ print -r -- " $platform => $logs/$platform"
+ done
+ return 1
+}
+
+# Copied from https://github.com/romkatv/run-process-tree.
+function run-process-tree() {
+ zmodload zsh/parameter zsh/param/private || return
+ local -P opt=(${(kv)options[@]}) || return
+ local -P pat=(${patchars[@]}) || return
+ local -P dis_pat=(${dis_patchars[@]}) || return
+ emulate -L zsh -o err_return || return
+ setopt monitor traps_async pipe_fail no_unset
+ zmodload zsh/system
+
+ if (( $# == 0 )); then
+ print -ru2 -- 'usage: run-process-tree command [arg]...'
+ return 1
+ fi
+
+ local -P stdout REPLY
+ exec {stdout}>&1
+ {
+ {
+ local -Pi pipe
+ local -P gid=$sysparams[pid]
+ local -P sig=(ABRT EXIT HUP ILL INT PIPE QUIT TERM ZERR)
+ local -P trap=(trap "trap - $sig; kill -- -$sysparams[pid]" $sig)
+
+ exec {pipe}>&1 1>&$stdout
+ $trap
+
+ {
+ $trap
+ while sleep 1 && print -u $pipe .; do; done
+ } 2>/dev/null &
+ local -Pi watchdog=$!
+
+ {
+ trap - ZERR
+ exec {pipe}>&-
+ enable -p -- $pat
+ disable -p -- $dis_pat
+ options=($opt zle off monitor off)
+ "$@"
+ } &
+ local -Pi ret
+ wait $! || ret=$?
+
+ trap "exit $ret" TERM
+ kill $watchdog
+ wait $watchdog
+ return ret
+ } | while read; do; done || return
+ } always {
+ exec {stdout}>&-
+ }
+}
+
+mkdir -p -- $logs $locks $binaries
+
+() {
+ run-process-tree mbuild $@
+ exit
+} "$@"
diff --git a/gitstatus/pull-upstream.zsh b/gitstatus/pull-upstream.zsh
deleted file mode 100755
index 0ec22438..00000000
--- a/gitstatus/pull-upstream.zsh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/usr/bin/zsh
-
-emulate -L zsh
-setopt err_exit no_unset pipe_fail extended_glob xtrace
-
-: ${GITSTATUS_DIR:=${${(%):-%x}:A:h}}
-: ${GITSTATUS_URL:=https://github.com/romkatv/gitstatus.git}
-
-readonly GITSTATUS_DIR GITSTATUS_URL
-readonly -a IGNORE=(pull-upstream.zsh README.md)
-
-() {
- local repo && repo="$(mktemp -d ${TMPDIR:-/tmp}/gitstatus-pull-upstream.XXXXXXXXXX)"
- trap "rm -rf ${(q)repo}" EXIT
- git clone --depth 1 $GITSTATUS_URL $repo
-
- local dst
- for dst in $GITSTATUS_DIR/**/*(.,@); do
- local f=${dst#$GITSTATUS_DIR/}
- (( ! ${IGNORE[(I)$f]} )) || continue
- local src=$repo/$f
- [[ -f $src ]] && {
- mkdir -p ${dst:h} && cp -f $src $dst || return
- } || {
- rm -f $dst
- }
- done
-}
diff --git a/gitstatus/src/algorithm.h b/gitstatus/src/algorithm.h
new file mode 100644
index 00000000..b87b13f0
--- /dev/null
+++ b/gitstatus/src/algorithm.h
@@ -0,0 +1,37 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#ifndef ROMKATV_GITSTATUS_ALGORITHM_H_
+#define ROMKATV_GITSTATUS_ALGORITHM_H_
+
+#include
+
+namespace gitstatus {
+
+// Requires: Iter is a BidirectionalIterator.
+//
+// Returns iterator pointing to the last value in [begin, end) that compares equal to the value, or
+// begin if none compare equal.
+template
+Iter FindLast(Iter begin, Iter end, const T& val) {
+ while (begin != end && !(*--end == val)) {}
+ return end;
+}
+
+} // namespace gitstatus
+
+#endif // ROMKATV_GITSTATUS_ALGORITHM_H_
diff --git a/gitstatus/src/arena.cc b/gitstatus/src/arena.cc
new file mode 100644
index 00000000..4c137639
--- /dev/null
+++ b/gitstatus/src/arena.cc
@@ -0,0 +1,118 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#include "arena.h"
+
+#include
+#include
+
+#include "bits.h"
+#include "check.h"
+
+namespace gitstatus {
+
+namespace {
+
+size_t Clamp(size_t min, size_t val, size_t max) { return std::min(max, std::max(min, val)); }
+
+static const uintptr_t kSingularity = reinterpret_cast(&kSingularity);
+
+} // namespace
+
+// Triple singularity. We are all fucked.
+Arena::Block Arena::g_empty_block = {kSingularity, kSingularity, kSingularity};
+
+Arena::Arena(Arena::Options opt) : opt_(std::move(opt)), top_(&g_empty_block) {
+ CHECK(opt_.min_block_size <= opt_.max_block_size);
+}
+
+Arena::Arena(Arena&& other) : Arena() { *this = std::move(other); }
+
+Arena::~Arena() {
+ // See comments in Makefile for the reason sized deallocation is not used.
+ for (const Block& b : blocks_) ::operator delete(reinterpret_cast(b.start));
+}
+
+Arena& Arena::operator=(Arena&& other) {
+ if (this != &other) {
+ // In case std::vector ever gets small object optimization.
+ size_t idx = other.reusable_ ? other.top_ - other.blocks_.data() : 0;
+ opt_ = other.opt_;
+ blocks_ = std::move(other.blocks_);
+ reusable_ = other.reusable_;
+ top_ = reusable_ ? blocks_.data() + idx : &g_empty_block;
+ other.blocks_.clear();
+ other.reusable_ = 0;
+ other.top_ = &g_empty_block;
+ }
+ return *this;
+}
+
+void Arena::Reuse(size_t num_blocks) {
+ reusable_ = std::min(reusable_, num_blocks);
+ for (size_t i = reusable_; i != blocks_.size(); ++i) {
+ const Block& b = blocks_[i];
+ // See comments in Makefile for the reason sized deallocation is not used.
+ ::operator delete(reinterpret_cast(b.start));
+ }
+ blocks_.resize(reusable_);
+ if (reusable_) {
+ top_ = blocks_.data();
+ top_->tip = top_->start;
+ } else {
+ top_ = &g_empty_block;
+ }
+}
+
+void Arena::AddBlock(size_t size, size_t alignment) {
+ if (alignment > alignof(std::max_align_t)) {
+ size += alignment - 1;
+ } else {
+ size = std::max(size, alignment);
+ }
+ if (size <= top_->size() && top_ < blocks_.data() + reusable_ - 1) {
+ assert(blocks_.front().size() == top_->size());
+ ++top_;
+ top_->tip = top_->start;
+ return;
+ }
+ if (size <= opt_.max_alloc_threshold) {
+ size =
+ std::max(size, Clamp(opt_.min_block_size, NextPow2(top_->size() + 1), opt_.max_block_size));
+ }
+
+ auto p = reinterpret_cast(::operator new(size));
+ blocks_.push_back(Block{p, p, p + size});
+ if (reusable_) {
+ if (size < blocks_.front().size()) {
+ top_ = &blocks_.back();
+ return;
+ }
+ if (size > blocks_.front().size()) reusable_ = 0;
+ }
+ std::swap(blocks_.back(), blocks_[reusable_]);
+ top_ = &blocks_[reusable_++];
+}
+
+void* Arena::AllocateSlow(size_t size, size_t alignment) {
+ assert(alignment && !(alignment & (alignment - 1)));
+ AddBlock(size, alignment);
+ assert(Align(top_->tip, alignment) + size <= top_->end);
+ return Allocate(size, alignment);
+}
+
+} // namespace gitstatus
diff --git a/gitstatus/src/arena.h b/gitstatus/src/arena.h
new file mode 100644
index 00000000..569833ca
--- /dev/null
+++ b/gitstatus/src/arena.h
@@ -0,0 +1,273 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#ifndef ROMKATV_GITSTATUS_ARENA_H_
+#define ROMKATV_GITSTATUS_ARENA_H_
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "string_view.h"
+
+namespace gitstatus {
+
+// Thread-compatible. Very fast and very flexible w.r.t. allocation size and alignment.
+//
+// Natural API extensions:
+//
+// // Donates a block to the arena. When the time comes, it'll be freed with
+// // free(p, size, userdata).
+// void Donate(void* p, size_t size, void* userdata, void(*free)(void*, size_t, void*));
+class Arena {
+ public:
+ struct Options {
+ // The first call to Allocate() will allocate a block of this size. There is one exception when
+ // the first requested allocation size is larger than this limit. Subsequent blocks will be
+ // twice as large as the last until they saturate at max_block_size.
+ size_t min_block_size = 64;
+
+ // Allocate blocks at most this large. There is one exception when the requested allocation
+ // size is larger than this limit.
+ size_t max_block_size = 8 << 10;
+
+ // When the size of the first allocation in a block is larger than this threshold, the block
+ // size will be equal to the allocation size. This is meant to reduce memory waste when making
+ // many allocations with sizes slightly over max_block_size / 2. With max_alloc_threshold equal
+ // to max_block_size / N, the upper bound on wasted memory when making many equally-sized
+ // allocations is 100.0 / (N + 1) percent. When making allocations of different sizes, the upper
+ // bound on wasted memory is 50%.
+ size_t max_alloc_threshold = 1 << 10;
+
+ // Natural extensions:
+ //
+ // void* userdata;
+ // void (*alloc)(size_t size, size_t alignment, void* userdata);
+ // void (*free)(void* p, size_t size, void* userdata);
+ };
+
+ // Requires: opt.min_block_size <= opt.max_block_size.
+ //
+ // Doesn't allocate any memory.
+ Arena(Options opt);
+ Arena() : Arena(Options()) {}
+ Arena(Arena&&);
+ ~Arena();
+
+ Arena& operator=(Arena&& other);
+
+ // Requires: alignment is a power of 2.
+ //
+ // Result is never null and always aligned. If size is zero, the result may be equal to the last.
+ // Alignment above alignof(std::max_align_t) is supported. There is no requirement for alignment
+ // to be less than size or to divide it.
+ inline void* Allocate(size_t size, size_t alignment) {
+ assert(alignment && !(alignment & (alignment - 1)));
+ uintptr_t p = Align(top_->tip, alignment);
+ uintptr_t e = p + size;
+ if (e <= top_->end) {
+ top_->tip = e;
+ return reinterpret_cast(p);
+ }
+ return AllocateSlow(size, alignment);
+ }
+
+ template
+ inline T* Allocate(size_t n) {
+ static_assert(!std::is_reference(), "");
+ return static_cast(Allocate(n * sizeof(T), alignof(T)));
+ }
+
+ template
+ inline T* Allocate() {
+ return Allocate(1);
+ }
+
+ inline char* MemDup(const char* p, size_t len) {
+ char* res = Allocate(len);
+ std::memcpy(res, p, len);
+ return res;
+ }
+
+ // Copies the null-terminated string (including the trailing null character) to the arena and
+ // returns a pointer to the copy.
+ inline char* StrDup(const char* s) {
+ size_t len = std::strlen(s);
+ return MemDup(s, len + 1);
+ }
+
+ // Guarantees: !StrDup(p, len)[len].
+ inline char* StrDup(const char* p, size_t len) {
+ char* res = Allocate(len + 1);
+ std::memcpy(res, p, len);
+ res[len] = 0;
+ return res;
+ }
+
+ // Guarantees: !StrDup(s)[s.len].
+ inline char* StrDup(StringView s) {
+ return StrDup(s.ptr, s.len);
+ }
+
+ template
+ inline char* StrCat(const Ts&... ts) {
+ return [&](std::initializer_list ss) {
+ size_t len = 0;
+ for (StringView s : ss) len += s.len;
+ char* p = Allocate(len + 1);
+ for (StringView s : ss) {
+ std::memcpy(p, s.ptr, s.len);
+ p += s.len;
+ }
+ *p = 0;
+ return p - len;
+ }({ts...});
+ }
+
+ // Copies/moves `val` to the arena and returns a pointer to it.
+ template
+ inline std::remove_const_t>* Dup(T&& val) {
+ return DirectInit>>(std::forward(val));
+ }
+
+ // The same as `new T{args...}` but on the arena.
+ template
+ inline T* DirectInit(Args&&... args) {
+ T* res = Allocate();
+ ::new (const_cast(static_cast(res))) T(std::forward(args)...);
+ return res;
+ }
+
+ // The same as `new T(args...)` but on the arena.
+ template
+ inline T* BraceInit(Args&&... args) {
+ T* res = Allocate();
+ ::new (const_cast(static_cast(res))) T{std::forward(args)...};
+ return res;
+ }
+
+ // Tip() and TipSize() allow you to allocate the remainder of the current block. They can be
+ // useful if you are flexible w.r.t. the allocation size.
+ //
+ // Invariant:
+ //
+ // const void* tip = Tip();
+ // void* p = Allocate(TipSize(), 1); // grab the remainder of the current block
+ // assert(p == tip);
+ const void* Tip() const { return reinterpret_cast(top_->tip); }
+ size_t TipSize() const { return top_->end - top_->tip; }
+
+ // Invalidates all allocations (without running destructors of allocated objects) and frees all
+ // blocks except at most the specified number of blocks. The retained blocks will be used to
+ // fulfil future allocation requests.
+ void Reuse(size_t num_blocks = std::numeric_limits::max());
+
+ private:
+ struct Block {
+ size_t size() const { return end - start; }
+ uintptr_t start;
+ uintptr_t tip;
+ uintptr_t end;
+ };
+
+ inline static size_t Align(size_t n, size_t m) { return (n + m - 1) & ~(m - 1); };
+
+ void AddBlock(size_t size, size_t alignment);
+ bool ReuseBlock(size_t size, size_t alignment);
+
+ __attribute__((noinline)) void* AllocateSlow(size_t size, size_t alignment);
+
+ Options opt_;
+ std::vector blocks_;
+ // Invariant: !blocks_.empty() <= reusable_ && reusable_ <= blocks_.size().
+ size_t reusable_ = 0;
+ // Invariant: (top_ == &g_empty_block) == blocks_.empty().
+ // Invariant: blocks_.empty() || top_ == &blocks_.back() || top_ < blocks_.data() + reusable_.
+ Block* top_;
+
+ static Block g_empty_block;
+};
+
+// Copies of ArenaAllocator use the same thread-compatible Arena without synchronization.
+template
+class ArenaAllocator {
+ public:
+ using value_type = T;
+ using pointer = T*;
+ using const_pointer = const T*;
+ using reference = T&;
+ using const_reference = const T&;
+ using size_type = size_t;
+ using difference_type = ptrdiff_t;
+ using propagate_on_container_move_assignment = std::true_type;
+ template
+ struct rebind {
+ using other = ArenaAllocator;
+ };
+ using is_always_equal = std::false_type;
+
+ ArenaAllocator(Arena* arena = nullptr) : arena_(*arena) {}
+
+ Arena& arena() const { return arena_; }
+
+ pointer address(reference x) const { return &x; }
+ const_pointer address(const_reference x) const { return &x; }
+ pointer allocate(size_type n, const void* hint = nullptr) { return arena_.Allocate(n); }
+ void deallocate(T* p, std::size_t n) {}
+ size_type max_size() const { return std::numeric_limits::max() / sizeof(value_type); }
+
+ template
+ void construct(U* p, Args&&... args) {
+ ::new (const_cast(static_cast(p))) U(std::forward(args)...);
+ }
+
+ template
+ void destroy(U* p) {
+ p->~U();
+ }
+
+ bool operator==(const ArenaAllocator& other) const { return &arena_ == &other.arena_; }
+ bool operator!=(const ArenaAllocator& other) const { return &arena_ != &other.arena_; }
+
+ private:
+ Arena& arena_;
+};
+
+template
+struct LazyWithArena;
+
+template class C, class T1, class A>
+struct LazyWithArena> {
+ using type = C::value_type>>;
+};
+
+template class C, class T1, class T2, class A>
+struct LazyWithArena> {
+ using type = C::value_type>>;
+};
+
+template
+using WithArena = typename LazyWithArena::type;
+
+} // namespace gitstatus
+
+#endif // ROMKATV_GITSTATUS_DIR_H_
diff --git a/gitstatus/src/bits.h b/gitstatus/src/bits.h
new file mode 100644
index 00000000..c1a7dcb6
--- /dev/null
+++ b/gitstatus/src/bits.h
@@ -0,0 +1,29 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#ifndef ROMKATV_GITSTATUS_BITS_H_
+#define ROMKATV_GITSTATUS_BITS_H_
+
+#include
+
+namespace gitstatus {
+
+inline size_t NextPow2(size_t n) { return n < 2 ? 1 : (~size_t{0} >> __builtin_clzll(n - 1)) + 1; }
+
+} // namespace gitstatus
+
+#endif // ROMKATV_GITSTATUS_BITS_H_
diff --git a/gitstatus/src/check.h b/gitstatus/src/check.h
new file mode 100644
index 00000000..682675a2
--- /dev/null
+++ b/gitstatus/src/check.h
@@ -0,0 +1,61 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#ifndef ROMKATV_GITSTATUS_CHECK_H_
+#define ROMKATV_GITSTATUS_CHECK_H_
+
+#include "logging.h"
+
+#include
+
+// The argument must be an expression convertible to bool.
+// Does nothing if the expression evaluates to true. Otherwise
+// it's equivalent to LOG(FATAL).
+#define CHECK(cond...) \
+ static_cast(0), (!!(cond)) ? static_cast(0) : LOG(FATAL) << #cond << ": "
+
+#define VERIFY(cond...) \
+ static_cast(0), ::gitstatus::internal_check::Thrower(!(cond)) \
+ ? static_cast(0) \
+ : LOG(ERROR) << #cond << ": "
+
+namespace gitstatus {
+
+struct Exception : std::exception {
+ const char* what() const noexcept override { return "Exception"; }
+};
+
+namespace internal_check {
+
+class Thrower {
+ public:
+ Thrower(bool should_throw) : throw_(should_throw) {}
+ Thrower(Thrower&&) = delete;
+ explicit operator bool() const { return !throw_; }
+ ~Thrower() noexcept(false) {
+ if (throw_) throw Exception();
+ }
+
+ private:
+ bool throw_;
+};
+
+} // namespace internal_check
+
+} // namespace gitstatus
+
+#endif // ROMKATV_GITSTATUS_CHECK_H_
diff --git a/gitstatus/src/check_dir_mtime.cc b/gitstatus/src/check_dir_mtime.cc
new file mode 100644
index 00000000..bb60ffe5
--- /dev/null
+++ b/gitstatus/src/check_dir_mtime.cc
@@ -0,0 +1,157 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#include "check_dir_mtime.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include "check.h"
+#include "dir.h"
+#include "logging.h"
+#include "print.h"
+#include "scope_guard.h"
+#include "stat.h"
+
+namespace gitstatus {
+
+namespace {
+
+constexpr char kDirPrefix[] = ".gitstatus.";
+
+void Touch(const char* path) {
+ int fd = creat(path, 0444);
+ VERIFY(fd >= 0) << Errno();
+ CHECK(!close(fd)) << Errno();
+}
+
+bool StatChanged(const char* path, const struct stat& prev) {
+ struct stat cur;
+ VERIFY(!lstat(path, &cur)) << Errno();
+ return !StatEq(prev, cur);
+}
+
+void RemoveStaleDirs(const char* root_dir) {
+ int dir_fd = open(root_dir, O_DIRECTORY | O_CLOEXEC);
+ if (dir_fd < 0) return;
+ ON_SCOPE_EXIT(&) { CHECK(!close(dir_fd)) << Errno(); };
+
+ Arena arena;
+ std::vector entries;
+ const std::time_t now = std::time(nullptr);
+ if (!ListDir(dir_fd, arena, entries,
+ /* precompose_unicode = */ false,
+ /* case_sensitive = */ true)) {
+ return;
+ }
+
+ std::string path = root_dir;
+ const size_t root_dir_len = path.size();
+
+ for (const char* entry : entries) {
+ if (std::strlen(entry) < std::strlen(kDirPrefix)) continue;
+ if (std::memcmp(entry, kDirPrefix, std::strlen(kDirPrefix))) continue;
+
+ struct stat st;
+ if (fstatat(dir_fd, entry, &st, AT_SYMLINK_NOFOLLOW)) {
+ LOG(WARN) << "Cannot stat " << Print(entry) << " in " << Print(root_dir) << ": " << Errno();
+ continue;
+ }
+ if (MTim(st).tv_sec + 10 > now) continue;
+
+ path.resize(root_dir_len);
+ path += entry;
+ size_t dir_len = path.size();
+
+ path += "/b/1";
+ if (unlink(path.c_str()) && errno != ENOENT) {
+ LOG(WARN) << "Cannot unlink " << Print(path) << ": " << Errno();
+ continue;
+ }
+
+ for (const char* d : {"/a/1", "/a", "/b", ""}) {
+ path.resize(dir_len);
+ path += d;
+ if (rmdir(path.c_str()) && errno != ENOENT) {
+ LOG(WARN) << "Cannot remove " << Print(path) << ": " << Errno();
+ break;
+ }
+ }
+ }
+}
+
+} // namespace
+
+bool CheckDirMtime(const char* root_dir) {
+ try {
+ RemoveStaleDirs(root_dir);
+
+ std::string tmp = std::string() + root_dir + kDirPrefix + "XXXXXX";
+ VERIFY(mkdtemp(&tmp[0])) << Errno();
+ ON_SCOPE_EXIT(&) { rmdir(tmp.c_str()); };
+
+ std::string a_dir = tmp + "/a";
+ VERIFY(!mkdir(a_dir.c_str(), 0755)) << Errno();
+ ON_SCOPE_EXIT(&) { rmdir(a_dir.c_str()); };
+ struct stat a_st;
+ VERIFY(!lstat(a_dir.c_str(), &a_st)) << Errno();
+
+ std::string b_dir = tmp + "/b";
+ VERIFY(!mkdir(b_dir.c_str(), 0755)) << Errno();
+ ON_SCOPE_EXIT(&) { rmdir(b_dir.c_str()); };
+ struct stat b_st;
+ VERIFY(!lstat(b_dir.c_str(), &b_st)) << Errno();
+
+ while (sleep(1)) {
+ // zzzz
+ }
+
+ std::string a1 = a_dir + "/1";
+ VERIFY(!mkdir(a1.c_str(), 0755)) << Errno();
+ ON_SCOPE_EXIT(&) { rmdir(a1.c_str()); };
+ if (!StatChanged(a_dir.c_str(), a_st)) {
+ LOG(WARN) << "Creating a directory doesn't change mtime of the parent: " << Print(root_dir);
+ return false;
+ }
+
+ std::string b1 = b_dir + "/1";
+ Touch(b1.c_str());
+ ON_SCOPE_EXIT(&) { unlink(b1.c_str()); };
+ if (!StatChanged(b_dir.c_str(), b_st)) {
+ LOG(WARN) << "Creating a file doesn't change mtime of the parent: " << Print(root_dir);
+ return false;
+ }
+
+ LOG(INFO) << "All mtime checks have passes. Enabling untracked cache: " << Print(root_dir);
+ return true;
+ } catch (const Exception&) {
+ LOG(WARN) << "Error while testing for mtime capability: " << Print(root_dir);
+ return false;
+ }
+}
+
+} // namespace gitstatus
diff --git a/gitstatus/src/check_dir_mtime.h b/gitstatus/src/check_dir_mtime.h
new file mode 100644
index 00000000..c9204e95
--- /dev/null
+++ b/gitstatus/src/check_dir_mtime.h
@@ -0,0 +1,31 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#ifndef ROMKATV_GITSTATUS_CHECK_DIR_MTIME_H_
+#define ROMKATV_GITSTATUS_CHECK_DIR_MTIME_H_
+
+namespace gitstatus {
+
+// Similar to `git update-index --test-untracked-cache` but performs all tests
+// in parallel, so the total testing time is one second regardless of the number
+// of tests. It also performs fewer tests because gitstatus imposes fewer
+// requirements on the filesystem in order to take advantage of untracked cache.
+bool CheckDirMtime(const char* root_dir);
+
+} // namespace gitstatus
+
+#endif // ROMKATV_GITSTATUS_CHECK_DIR_MTIME_H_
diff --git a/gitstatus/src/dir.cc b/gitstatus/src/dir.cc
new file mode 100644
index 00000000..39cf1c2c
--- /dev/null
+++ b/gitstatus/src/dir.cc
@@ -0,0 +1,237 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#include "dir.h"
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef __linux__
+#include
+#include
+#endif
+
+#ifdef __APPLE__
+#include
+#endif
+
+#include "bits.h"
+#include "check.h"
+#include "scope_guard.h"
+#include "string_cmp.h"
+#include "tribool.h"
+
+namespace gitstatus {
+
+namespace {
+
+bool Dots(const char* name) {
+ if (name[0] == '.') {
+ if (name[1] == 0) return true;
+ if (name[1] == '.' && name[2] == 0) return true;
+ }
+ return false;
+}
+
+} // namespace
+
+// The linux-specific implementation is about 20% faster than the generic (posix) implementation.
+#ifdef __linux__
+
+uint64_t Read64(const void* p) {
+ uint64_t res;
+ std::memcpy(&res, p, 8);
+ return res;
+}
+
+void Write64(uint64_t x, void* p) { std::memcpy(p, &x, 8); }
+
+void SwapBytes(char** begin, char** end) {
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ for (; begin != end; ++begin) Write64(__builtin_bswap64(Read64(*begin)), *begin);
+#elif __BYTE_ORDER__ != __ORDER_BIG_ENDIAN__
+#error "sorry, not implemented"
+#endif
+}
+
+template
+void SortEntries(char** begin, char** end) {
+ static_assert(kCaseSensitive, "");
+ SwapBytes(begin, end);
+ std::sort(begin, end, [](const char* a, const char* b) {
+ uint64_t x = Read64(a);
+ uint64_t y = Read64(b);
+ // Add 5 for good luck.
+ return x < y || (x == y && std::memcmp(a + 5, b + 5, 256) < 0);
+ });
+ SwapBytes(begin, end);
+}
+
+template <>
+void SortEntries(char** begin, char** end) {
+ std::sort(begin, end, StrLt());
+}
+
+bool ListDir(int dir_fd, Arena& arena, std::vector& entries, bool precompose_unicode,
+ bool case_sensitive) {
+ struct linux_dirent64 {
+ ino64_t d_ino;
+ off64_t d_off;
+ unsigned short d_reclen;
+ unsigned char d_type;
+ char d_name[];
+ };
+
+ constexpr size_t kBufSize = 8 << 10;
+ const size_t orig_size = entries.size();
+
+ while (true) {
+ char* buf = static_cast(arena.Allocate(kBufSize, alignof(linux_dirent64)));
+ // Save 256 bytes for the rainy day.
+ int n = syscall(SYS_getdents64, dir_fd, buf, kBufSize - 256);
+ if (n < 0) {
+ entries.resize(orig_size);
+ return false;
+ }
+ for (int pos = 0; pos < n;) {
+ auto* ent = reinterpret_cast(buf + pos);
+ if (!Dots(ent->d_name)) entries.push_back(ent->d_name);
+ pos += ent->d_reclen;
+ }
+ if (n == 0) break;
+ // The following optimization relies on SYS_getdents64 always returning as many
+ // entries as would fit. This is not guaranteed by the specification and I don't
+ // know if this is true in practice. The optimization has no measurable effect on
+ // gitstatus performance, so it's turned off.
+ //
+ // if (n + sizeof(linux_dirent64) + 512 <= kBufSize) break;
+ }
+
+ if (case_sensitive) {
+ SortEntries(entries.data() + orig_size, entries.data() + entries.size());
+ } else {
+ SortEntries(entries.data() + orig_size, entries.data() + entries.size());
+ }
+
+ return true;
+}
+
+#else // __linux__
+
+namespace {
+
+char* DirentDup(Arena& arena, const struct dirent& ent, size_t len) {
+ char* p = arena.Allocate(len + 2);
+ *p++ = ent.d_type;
+ std::memcpy(p, ent.d_name, len + 1);
+ return p;
+}
+
+#ifdef __APPLE__
+
+std::atomic g_iconv_error(true);
+
+Tribool IConvTry(char* inp, size_t ins, char* outp, size_t outs) {
+ if (outs == 0) return Tribool::kUnknown;
+ iconv_t ic = iconv_open("UTF-8", "UTF-8-MAC");
+ if (ic == (iconv_t)-1) {
+ if (g_iconv_error.load(std::memory_order_relaxed) &&
+ g_iconv_error.exchange(false, std::memory_order_relaxed)) {
+ LOG(ERROR) << "iconv_open(\"UTF-8\", \"UTF-8-MAC\") failed";
+ }
+ return Tribool::kFalse;
+ }
+ ON_SCOPE_EXIT(&) { CHECK(iconv_close(ic) == 0) << Errno(); };
+ --outs;
+ if (iconv(ic, &inp, &ins, &outp, &outs) >= 0) {
+ *outp = 0;
+ return Tribool::kTrue;
+ }
+ return errno == E2BIG ? Tribool::kUnknown : Tribool::kFalse;
+}
+
+char* DirenvConvert(Arena& arena, struct dirent& ent, bool do_convert) {
+ if (!do_convert) return DirentDup(arena, ent, std::strlen(ent.d_name));
+
+ size_t len = 0;
+ do_convert = false;
+ for (unsigned char c; (c = ent.d_name[len]); ++len) {
+ if (c & 0x80) do_convert = true;
+ }
+ if (!do_convert) return DirentDup(arena, ent, len);
+
+ size_t n = NextPow2(len + 2);
+ while (true) {
+ char* p = arena.Allocate(n);
+ switch (IConvTry(ent.d_name, len, p + 1, n - 1)) {
+ case Tribool::kFalse:
+ return DirentDup(arena, ent, len);
+ case Tribool::kTrue:
+ *p = ent.d_type;
+ return p + 1;
+ case Tribool::kUnknown:
+ break;
+ }
+ n *= 2;
+ }
+}
+
+#else // __APPLE__
+
+char* DirenvConvert(Arena& arena, struct dirent& ent, bool do_convert) {
+ return DirentDup(arena, ent, std::strlen(ent.d_name));
+}
+
+#endif // __APPLE__
+
+} // namespace
+
+bool ListDir(int dir_fd, Arena& arena, std::vector& entries, bool precompose_unicode,
+ bool case_sensitive) {
+ const size_t orig_size = entries.size();
+ dir_fd = dup(dir_fd);
+ if (dir_fd < 0) return false;
+ DIR* dir = fdopendir(dir_fd);
+ if (!dir) {
+ CHECK(!close(dir_fd)) << Errno();
+ return false;
+ }
+ ON_SCOPE_EXIT(&) { CHECK(!closedir(dir)) << Errno(); };
+ while (struct dirent* ent = (errno = 0, readdir(dir))) {
+ if (Dots(ent->d_name)) continue;
+ entries.push_back(DirenvConvert(arena, *ent, precompose_unicode));
+ }
+ if (errno) {
+ entries.resize(orig_size);
+ return false;
+ }
+ StrSort(entries.data() + orig_size, entries.data() + entries.size(), case_sensitive);
+ return true;
+}
+
+#endif // __linux__
+
+} // namespace gitstatus
diff --git a/gitstatus/src/dir.h b/gitstatus/src/dir.h
new file mode 100644
index 00000000..2a7533a3
--- /dev/null
+++ b/gitstatus/src/dir.h
@@ -0,0 +1,50 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#ifndef ROMKATV_GITSTATUS_DIR_H_
+#define ROMKATV_GITSTATUS_DIR_H_
+
+#include
+#include
+
+#include "arena.h"
+
+namespace gitstatus {
+
+// On error, leaves entries unchanged and returns false. Does not throw.
+//
+// On success, appends names of files from the specified directory to entries and returns true.
+// Every appended entry is a null-terminated string. At -1 offset is its d_type. All elements
+// point into the arena. They are sorted either by strcmp or strcasecmp depending on case_sensitive.
+//
+// Does not close dir_fd.
+//
+// There are two distinct implementations of ListDir -- one for Linux and another for everything
+// else. The linux-specific implementation is 20% faster.
+//
+// The reason sorting is bundled with directory listing is performance on Linux. The API of
+// getdents64 allows for much faster sorting than what can be done with a plain vector.
+// For the POSIX implementation there is no need to bundle sorting in this way. In fact, it's
+// done at the end with a generic StrSort() call.
+//
+// For best results, reuse the arena and vector for multiple calls to avoid heap allocations.
+bool ListDir(int dir_fd, Arena& arena, std::vector& entries, bool precompose_unicode,
+ bool case_sensitive);
+
+} // namespace gitstatus
+
+#endif // ROMKATV_GITSTATUS_DIR_H_
diff --git a/gitstatus/src/git.cc b/gitstatus/src/git.cc
new file mode 100644
index 00000000..552100cb
--- /dev/null
+++ b/gitstatus/src/git.cc
@@ -0,0 +1,250 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#include "git.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include "arena.h"
+#include "check.h"
+#include "print.h"
+#include "scope_guard.h"
+
+namespace gitstatus {
+
+const char* GitError() {
+ const git_error* err = git_error_last();
+ return err && err->message ? err->message : "unknown error";
+}
+
+std::string RepoState(git_repository* repo) {
+ Arena arena;
+ StringView gitdir(git_repository_path(repo));
+
+ // These names mostly match gitaction in vcs_info:
+ // https://github.com/zsh-users/zsh/blob/master/Functions/VCS_Info/Backends/VCS_INFO_get_data_git.
+ auto State = [&]() {
+ switch (git_repository_state(repo)) {
+ case GIT_REPOSITORY_STATE_NONE:
+ return "";
+ case GIT_REPOSITORY_STATE_MERGE:
+ return "merge";
+ case GIT_REPOSITORY_STATE_REVERT:
+ return "revert";
+ case GIT_REPOSITORY_STATE_REVERT_SEQUENCE:
+ return "revert-seq";
+ case GIT_REPOSITORY_STATE_CHERRYPICK:
+ return "cherry";
+ case GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE:
+ return "cherry-seq";
+ case GIT_REPOSITORY_STATE_BISECT:
+ return "bisect";
+ case GIT_REPOSITORY_STATE_REBASE:
+ return "rebase";
+ case GIT_REPOSITORY_STATE_REBASE_INTERACTIVE:
+ return "rebase-i";
+ case GIT_REPOSITORY_STATE_REBASE_MERGE:
+ return "rebase-m";
+ case GIT_REPOSITORY_STATE_APPLY_MAILBOX:
+ return "am";
+ case GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE:
+ return "am/rebase";
+ }
+ return "action";
+ };
+
+ auto DirExists = [&](StringView name) {
+ int fd = open(arena.StrCat(gitdir, "/", name), O_DIRECTORY | O_CLOEXEC);
+ if (fd < 0) return false;
+ CHECK(!close(fd)) << Errno();
+ return true;
+ };
+
+ auto ReadFile = [&](StringView name) {
+ std::ifstream strm(arena.StrCat(gitdir, "/", name));
+ std::string res;
+ strm >> res;
+ return res;
+ };
+
+ std::string next;
+ std::string last;
+
+ if (DirExists("rebase-merge")) {
+ next = ReadFile("rebase-merge/msgnum");
+ last = ReadFile("rebase-merge/end");
+ } else if (DirExists("rebase-apply")) {
+ next = ReadFile("rebase-apply/next");
+ last = ReadFile("rebase-apply/last");
+ }
+
+ std::ostringstream res;
+ res << State();
+ if (!next.empty() && !last.empty()) res << ' ' << next << '/' << last;
+ return res.str();
+}
+
+size_t CountRange(git_repository* repo, const std::string& range) {
+ git_revwalk* walk = nullptr;
+ VERIFY(!git_revwalk_new(&walk, repo)) << GitError();
+ ON_SCOPE_EXIT(=) { git_revwalk_free(walk); };
+ VERIFY(!git_revwalk_push_range(walk, range.c_str())) << GitError();
+ size_t res = 0;
+ while (true) {
+ git_oid oid;
+ switch (git_revwalk_next(&oid, walk)) {
+ case 0:
+ ++res;
+ break;
+ case GIT_ITEROVER:
+ return res;
+ default:
+ LOG(ERROR) << "git_revwalk_next: " << range << ": " << GitError();
+ throw Exception();
+ }
+ }
+}
+
+size_t NumStashes(git_repository* repo) {
+ size_t res = 0;
+ auto* cb = +[](size_t index, const char* message, const git_oid* stash_id, void* payload) {
+ ++*static_cast(payload);
+ return 0;
+ };
+ if (!git_stash_foreach(repo, cb, &res)) return res;
+ // Example error: failed to parse signature - malformed e-mail.
+ // See https://github.com/romkatv/powerlevel10k/issues/216.
+ LOG(WARN) << "git_stash_foreach: " << GitError();
+ return 0;
+}
+
+git_reference* Head(git_repository* repo) {
+ git_reference* symbolic = nullptr;
+ switch (git_reference_lookup(&symbolic, repo, "HEAD")) {
+ case 0:
+ break;
+ case GIT_ENOTFOUND:
+ return nullptr;
+ default:
+ LOG(ERROR) << "git_reference_lookup: " << GitError();
+ throw Exception();
+ }
+
+ git_reference* direct = nullptr;
+ if (git_reference_resolve(&direct, symbolic)) {
+ LOG(INFO) << "Empty git repo (no HEAD)";
+ return symbolic;
+ }
+ git_reference_free(symbolic);
+ return direct;
+}
+
+const char* LocalBranchName(const git_reference* ref) {
+ CHECK(ref);
+ git_reference_t type = git_reference_type(ref);
+ switch (type) {
+ case GIT_REFERENCE_DIRECT: {
+ return git_reference_is_branch(ref) ? git_reference_shorthand(ref) : "";
+ }
+ case GIT_REFERENCE_SYMBOLIC: {
+ static constexpr char kHeadPrefix[] = "refs/heads/";
+ const char* target = git_reference_symbolic_target(ref);
+ if (!target) return "";
+ size_t len = std::strlen(target);
+ if (len < sizeof(kHeadPrefix)) return "";
+ if (std::memcmp(target, kHeadPrefix, sizeof(kHeadPrefix) - 1)) return "";
+ return target + (sizeof(kHeadPrefix) - 1);
+ }
+ case GIT_REFERENCE_INVALID:
+ case GIT_REFERENCE_ALL:
+ break;
+ }
+ LOG(ERROR) << "Invalid reference type: " << type;
+ throw Exception();
+}
+
+RemotePtr GetRemote(git_repository* repo, const git_reference* local) {
+ git_remote* remote;
+ git_buf symref = {};
+ if (git_branch_remote(&remote, &symref, repo, git_reference_name(local))) return nullptr;
+ ON_SCOPE_EXIT(&) {
+ git_remote_free(remote);
+ git_buf_free(&symref);
+ };
+
+ git_reference* ref;
+ if (git_reference_lookup(&ref, repo, symref.ptr)) return nullptr;
+ ON_SCOPE_EXIT(&) { if (ref) git_reference_free(ref); };
+
+ const char* branch = nullptr;
+ std::string name = remote ? git_remote_name(remote) : ".";
+ if (git_branch_name(&branch, ref)) {
+ branch = "";
+ } else if (remote) {
+ VERIFY(std::strstr(branch, name.c_str()) == branch);
+ VERIFY(branch[name.size()] == '/');
+ branch += name.size() + 1;
+ }
+
+ auto res = std::make_unique();
+ res->name = std::move(name);
+ res->branch = branch;
+ res->url = remote ? (git_remote_url(remote) ?: "") : "";
+ res->ref = std::exchange(ref, nullptr);
+ return RemotePtr(res.release());
+}
+
+PushRemotePtr GetPushRemote(git_repository* repo, const git_reference* local) {
+ git_remote* remote;
+ git_buf symref = {};
+ if (git_branch_push_remote(&remote, &symref, repo, git_reference_name(local))) return nullptr;
+ ON_SCOPE_EXIT(&) {
+ git_remote_free(remote);
+ git_buf_free(&symref);
+ };
+
+ git_reference* ref;
+ if (git_reference_lookup(&ref, repo, symref.ptr)) return nullptr;
+ ON_SCOPE_EXIT(&) { if (ref) git_reference_free(ref); };
+
+ std::string name = remote ? git_remote_name(remote) : ".";
+
+ auto res = std::make_unique();
+ res->name = std::move(name);
+ res->url = remote ? (git_remote_url(remote) ?: "") : "";
+ res->ref = std::exchange(ref, nullptr);
+ return PushRemotePtr(res.release());
+}
+
+CommitMessage GetCommitMessage(git_repository* repo, const git_oid& id) {
+ git_commit* commit;
+ VERIFY(!git_commit_lookup(&commit, repo, &id)) << GitError();
+ ON_SCOPE_EXIT(=) { git_commit_free(commit); };
+ return {.encoding = git_commit_message_encoding(commit) ?: "",
+ .summary = git_commit_summary(commit) ?: ""};
+}
+
+} // namespace gitstatus
diff --git a/gitstatus/src/git.h b/gitstatus/src/git.h
new file mode 100644
index 00000000..b85f09f7
--- /dev/null
+++ b/gitstatus/src/git.h
@@ -0,0 +1,115 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#ifndef ROMKATV_GITSTATUS_GIT_H_
+#define ROMKATV_GITSTATUS_GIT_H_
+
+#include
+
+#include
+#include
+#include
+
+namespace gitstatus {
+
+// Not null.
+const char* GitError();
+
+// Not null.
+std::string RepoState(git_repository* repo);
+
+// Returns the number of commits in the range.
+size_t CountRange(git_repository* repo, const std::string& range);
+
+// How many stashes are there?
+size_t NumStashes(git_repository* repo);
+
+// Returns the origin URL or an empty string. Not null.
+std::string RemoteUrl(git_repository* repo, const git_reference* ref);
+
+// Returns reference to HEAD or null if not found. The reference is symbolic if the repo is empty
+// and direct otherwise.
+git_reference* Head(git_repository* repo);
+
+// Returns the name of the local branch, or an empty string.
+const char* LocalBranchName(const git_reference* ref);
+
+struct CommitMessage {
+ // Can be empty, meaning "UTF-8".
+ std::string encoding;
+ // The first paragraph of the commit's message as a one-liner.
+ std::string summary;
+};
+
+CommitMessage GetCommitMessage(git_repository* repo, const git_oid& id);
+
+struct Remote {
+ // Tip of the remote branch.
+ git_reference* ref;
+
+ // Name of the tracking remote. For example, "origin".
+ std::string name;
+
+ // Name of the tracking remote branch. For example, "master".
+ std::string branch;
+
+ // URL of the tracking remote. For example, "https://foo.com/repo.git".
+ std::string url;
+
+ // Note: pushurl is not exposed (but could be).
+
+ struct Free {
+ void operator()(const Remote* p) const {
+ if (p) {
+ if (p->ref) git_reference_free(p->ref);
+ delete p;
+ }
+ }
+ };
+};
+
+struct PushRemote {
+ // Tip of the remote branch.
+ git_reference* ref;
+
+ // Name of the tracking remote. For example, "origin".
+ std::string name;
+
+ // URL of the tracking remote. For example, "https://foo.com/repo.git".
+ std::string url;
+
+ // Note: pushurl is not exposed (but could be).
+
+ struct Free {
+ void operator()(const PushRemote* p) const {
+ if (p) {
+ if (p->ref) git_reference_free(p->ref);
+ delete p;
+ }
+ }
+ };
+};
+
+using RemotePtr = std::unique_ptr;
+using PushRemotePtr = std::unique_ptr;
+
+RemotePtr GetRemote(git_repository* repo, const git_reference* local);
+PushRemotePtr GetPushRemote(git_repository* repo, const git_reference* local);
+
+} // namespace gitstatus
+
+#endif // ROMKATV_GITSTATUS_GIT_H_
diff --git a/gitstatus/src/gitstatus.cc b/gitstatus/src/gitstatus.cc
new file mode 100644
index 00000000..81399ea7
--- /dev/null
+++ b/gitstatus/src/gitstatus.cc
@@ -0,0 +1,219 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#include
+
+#include
+#include
+#include
+
+#include
+
+#include "check.h"
+#include "git.h"
+#include "logging.h"
+#include "options.h"
+#include "print.h"
+#include "repo.h"
+#include "repo_cache.h"
+#include "request.h"
+#include "response.h"
+#include "scope_guard.h"
+#include "thread_pool.h"
+#include "timer.h"
+
+namespace gitstatus {
+namespace {
+
+using namespace std::string_literals;
+
+void Truncate(std::string& s, size_t max_len) {
+ if (s.size() > max_len) s.resize(max_len);
+}
+
+void ProcessRequest(const Options& opts, RepoCache& cache, Request req) {
+ Timer timer;
+ ON_SCOPE_EXIT(&) { timer.Report("request"); };
+
+ ResponseWriter resp(req.id);
+ Repo* repo = cache.Open(req.dir, req.from_dotgit);
+ if (!repo) return;
+
+ git_config* cfg;
+ VERIFY(!git_repository_config(&cfg, repo->repo())) << GitError();
+ ON_SCOPE_EXIT(=) { git_config_free(cfg); };
+ VERIFY(!git_config_refresh(cfg)) << GitError();
+
+ // Symbolic reference if and only if the repo is empty.
+ git_reference* head = Head(repo->repo());
+ if (!head) return;
+ ON_SCOPE_EXIT(=) { git_reference_free(head); };
+
+ // Null if and only if the repo is empty.
+ const git_oid* head_target = git_reference_target(head);
+
+ // Looking up tags may take some time. Do it in the background while we check for stuff.
+ // Note that GetTagName() doesn't access index, so it'll overlap with index reading and
+ // parsing.
+ std::future tag = repo->GetTagName(head_target);
+ ON_SCOPE_EXIT(&) {
+ if (tag.valid()) {
+ try {
+ tag.wait();
+ } catch (const Exception&) {
+ }
+ }
+ };
+
+ // Repository working directory. Absolute; no trailing slash. E.g., "/home/romka/gitstatus".
+ StringView workdir(git_repository_workdir(repo->repo()));
+ if (workdir.len == 0) return;
+ if (workdir.len > 1 && workdir.ptr[workdir.len - 1] == '/') --workdir.len;
+ resp.Print(workdir);
+
+ // Revision. Either 40 hex digits or an empty string for empty repo.
+ resp.Print(head_target ? git_oid_tostr_s(head_target) : "");
+
+ // Local branch name (e.g., "master") or empty string if not on a branch.
+ resp.Print(LocalBranchName(head));
+
+ // Remote tracking branch or null.
+ RemotePtr remote = GetRemote(repo->repo(), head);
+
+ // Tracking remote branch name (e.g., "master") or empty string if there is no tracking remote.
+ resp.Print(remote ? remote->branch : "");
+
+ // Tracking remote name (e.g., "origin") or empty string if there is no tracking remote.
+ resp.Print(remote ? remote->name : "");
+
+ // Tracking remote URL or empty string if there is no tracking remote.
+ resp.Print(remote ? remote->url : "");
+
+ // Repository state, A.K.A. action. For example, "merge".
+ resp.Print(RepoState(repo->repo()));
+
+ IndexStats stats;
+ // Look for staged, unstaged and untracked. This is where most of the time is spent.
+ if (req.diff) stats = repo->GetIndexStats(head_target, cfg);
+
+ // The number of files in the index.
+ resp.Print(stats.index_size);
+ // The number of staged changes. At most opts.max_num_staged.
+ resp.Print(stats.num_staged);
+ // The number of unstaged changes. At most opts.max_num_unstaged. 0 if index is too large.
+ resp.Print(stats.num_unstaged);
+ // The number of conflicted changes. At most opts.max_num_conflicted. 0 if index is too large.
+ resp.Print(stats.num_conflicted);
+ // The number of untracked changes. At most opts.max_num_untracked. 0 if index is too large.
+ resp.Print(stats.num_untracked);
+
+ if (remote && remote->ref) {
+ const char* ref = git_reference_name(remote->ref);
+ // Number of commits we are ahead of upstream. Non-negative integer.
+ resp.Print(CountRange(repo->repo(), ref + "..HEAD"s));
+ // Number of commits we are behind upstream. Non-negative integer.
+ resp.Print(CountRange(repo->repo(), "HEAD.."s + ref));
+ } else {
+ resp.Print("0");
+ resp.Print("0");
+ }
+
+ // Number of stashes. Non-negative integer.
+ resp.Print(NumStashes(repo->repo()));
+
+ // Tag that points to HEAD (e.g., "v4.2") or empty string if there aren't any. The same as
+ // `git describe --tags --exact-match`.
+ resp.Print(tag.get());
+
+ // The number of unstaged deleted files. At most stats.num_unstaged.
+ resp.Print(stats.num_unstaged_deleted);
+ // The number of staged new files. At most stats.num_staged.
+ resp.Print(stats.num_staged_new);
+ // The number of staged deleted files. At most stats.num_staged.
+ resp.Print(stats.num_staged_deleted);
+
+ // Push remote or null.
+ PushRemotePtr push_remote = GetPushRemote(repo->repo(), head);
+
+ // Push remote name (e.g., "origin") or empty string if there is no push remote.
+ resp.Print(push_remote ? push_remote->name : "");
+
+ // Push remote URL or empty string if there is no push remote.
+ resp.Print(push_remote ? push_remote->url : "");
+
+ if (push_remote && push_remote->ref) {
+ const char* ref = git_reference_name(push_remote->ref);
+ // Number of commits we are ahead of push remote. Non-negative integer.
+ resp.Print(CountRange(repo->repo(), ref + "..HEAD"s));
+ // Number of commits we are behind upstream. Non-negative integer.
+ resp.Print(CountRange(repo->repo(), "HEAD.."s + ref));
+ } else {
+ resp.Print("0");
+ resp.Print("0");
+ }
+
+ // The number of files in the index with skip-worktree bit set.
+ resp.Print(stats.num_skip_worktree);
+ // The number of files in the index with assume-unchanged bit set.
+ resp.Print(stats.num_assume_unchanged);
+
+ CommitMessage msg = head_target ? GetCommitMessage(repo->repo(), *head_target) : CommitMessage();
+ Truncate(msg.summary, opts.max_commit_summary_length);
+ resp.Print(msg.encoding);
+ resp.Print(msg.summary);
+
+ resp.Dump("with git status");
+}
+
+int GitStatus(int argc, char** argv) {
+ tzset();
+ Options opts = ParseOptions(argc, argv);
+ g_min_log_level = opts.log_level;
+ for (int i = 0; i != argc; ++i) LOG(INFO) << "argv[" << i << "]: " << Print(argv[i]);
+ RequestReader reader(fileno(stdin), opts.lock_fd, opts.parent_pid);
+ RepoCache cache(opts);
+
+ InitGlobalThreadPool(opts.num_threads);
+ git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0);
+ git_libgit2_opts(GIT_OPT_DISABLE_INDEX_CHECKSUM_VERIFICATION, 1);
+ git_libgit2_opts(GIT_OPT_DISABLE_INDEX_FILEPATH_VALIDATION, 1);
+ git_libgit2_opts(GIT_OPT_DISABLE_READNG_PACKED_TAGS, 1);
+ git_libgit2_init();
+
+ while (true) {
+ try {
+ Request req;
+ if (reader.ReadRequest(req)) {
+ LOG(INFO) << "Processing request: " << req;
+ try {
+ ProcessRequest(opts, cache, req);
+ LOG(INFO) << "Successfully processed request: " << req;
+ } catch (const Exception&) {
+ LOG(ERROR) << "Error processing request: " << req;
+ }
+ } else if (opts.repo_ttl >= Duration()) {
+ cache.Free(Clock::now() - opts.repo_ttl);
+ }
+ } catch (const Exception&) {
+ }
+ }
+}
+
+} // namespace
+} // namespace gitstatus
+
+int main(int argc, char** argv) { gitstatus::GitStatus(argc, argv); }
diff --git a/gitstatus/src/index.cc b/gitstatus/src/index.cc
new file mode 100644
index 00000000..4d66876b
--- /dev/null
+++ b/gitstatus/src/index.cc
@@ -0,0 +1,456 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#include "index.h"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "algorithm.h"
+#include "check.h"
+#include "dir.h"
+#include "git.h"
+#include "index.h"
+#include "print.h"
+#include "scope_guard.h"
+#include "stat.h"
+#include "string_cmp.h"
+#include "thread_pool.h"
+
+namespace gitstatus {
+
+namespace {
+
+void CommonDir(Str<> str, const char* a, const char* b, size_t* dir_len, size_t* dir_depth) {
+ *dir_len = 0;
+ *dir_depth = 0;
+ for (size_t i = 1; str.Eq(*a, *b) && *a; ++i, ++a, ++b) {
+ if (*a == '/') {
+ *dir_len = i;
+ ++*dir_depth;
+ }
+ }
+}
+
+size_t Weight(const IndexDir& dir) { return 1 + dir.subdirs.size() + dir.files.size(); }
+
+bool MTimeEq(const git_index_time& index, const struct timespec& workdir) {
+ if (index.seconds != workdir.tv_sec) return false;
+ if (int64_t{index.nanoseconds} == workdir.tv_nsec) return true;
+#ifdef GITSTATUS_ZERO_NSEC
+ return index.nanoseconds == 0;
+#else
+ return false;
+#endif
+}
+
+bool IsModified(const git_index_entry* entry, const struct stat& st, const RepoCaps& caps) {
+ mode_t mode = st.st_mode;
+ if (S_ISREG(mode)) {
+ if (!caps.has_symlinks && S_ISLNK(entry->mode)) {
+ mode = entry->mode;
+ } else if (!caps.trust_filemode) {
+ mode = entry->mode;
+ } else {
+ mode = S_IFREG | (mode & 0100 ? 0755 : 0644);
+ }
+ } else {
+ mode &= S_IFMT;
+ }
+
+ bool res = false;
+
+#define COND(field, cond...) \
+ if (cond) { \
+ } else \
+ res = true, \
+ LOG(DEBUG) << "Dirty candidate (modified): " << Print(entry->path) << ": " #field " "
+
+ COND(ino, !entry->ino || entry->ino == static_cast(st.st_ino))
+ << entry->ino << " => " << static_cast(st.st_ino);
+
+ COND(stage, GIT_INDEX_ENTRY_STAGE(entry) == 0) << "=> " << GIT_INDEX_ENTRY_STAGE(entry);
+ COND(fsize, int64_t{entry->file_size} == st.st_size) << entry->file_size << " => " << st.st_size;
+ COND(mtime, MTimeEq(entry->mtime, MTim(st))) << Print(entry->mtime) << " => " << Print(MTim(st));
+ COND(mode, entry->mode == mode) << std::oct << entry->mode << " => " << std::oct << mode;
+
+#undef COND
+
+ return res;
+}
+
+int OpenDir(int parent_fd, const char* name) {
+ return openat(parent_fd, name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+}
+
+void OpenTail(int* fds, size_t nfds, int root_fd, StringView dirname, Arena& arena) {
+ CHECK(fds && nfds && root_fd >= 0);
+ std::fill(fds, fds + nfds, -1);
+ if (!dirname.len) return;
+ CHECK(dirname.len > 1);
+ CHECK(dirname.ptr[0] != '/');
+ CHECK(dirname.ptr[dirname.len - 1] == '/');
+
+ char* begin = arena.StrDup(dirname.ptr, dirname.len - 1);
+ WithArena> subdirs(&arena);
+ subdirs.reserve(nfds + 1);
+
+ for (char* sep = begin + dirname.len - 1; subdirs.size() < nfds;) {
+ sep = FindLast(begin, sep, '/');
+ if (sep == begin) break;
+ *sep = 0;
+ subdirs.push_back(sep + 1);
+ }
+ subdirs.push_back(begin);
+ if (subdirs.size() < nfds + 1) subdirs.push_back(".");
+ CHECK(subdirs.size() <= nfds + 1);
+
+ for (size_t i = subdirs.size(); i != 1; --i) {
+ const char* path = subdirs[i - 1];
+ if ((root_fd = OpenDir(root_fd, path)) < 0) {
+ for (; i != subdirs.size(); ++i) {
+ CHECK(!close(fds[i - 1])) << Errno();
+ fds[i - 1] = -1;
+ }
+ return;
+ }
+ fds[i - 2] = root_fd;
+ }
+}
+
+std::vector ScanDirs(git_index* index, int root_fd, IndexDir* const* begin,
+ IndexDir* const* end, const RepoCaps& caps,
+ const ScanOpts& opts) {
+ const Str<> str(caps.case_sensitive);
+
+ Arena arena;
+ std::vector dirty_candidates;
+ std::vector entries;
+ entries.reserve(128);
+
+ auto AddCandidate = [&](const char* kind, const char* path) {
+ if (kind) LOG(DEBUG) << "Dirty candidate (" << kind << "): " << Print(path);
+ dirty_candidates.push_back(path);
+ };
+
+ constexpr ssize_t kDirStackSize = 5;
+ int dir_fd[kDirStackSize];
+ std::fill(std::begin(dir_fd), std::end(dir_fd), -1);
+ auto Close = [](int& fd) {
+ if (fd >= 0) {
+ CHECK(!close(fd)) << Errno();
+ fd = -1;
+ }
+ };
+ auto CloseAll = [&] { std::for_each(std::begin(dir_fd), std::end(dir_fd), Close); };
+ ON_SCOPE_EXIT(&) { CloseAll(); };
+ if (begin != end) OpenTail(dir_fd, kDirStackSize, root_fd, (*begin)->path, arena);
+
+ for (IndexDir* const* it = begin; it != end; ++it) {
+ IndexDir& dir = **it;
+
+ auto Basename = [&](const git_index_entry* e) { return e->path + dir.path.len; };
+
+ auto AddUnmached = [&](StringView basename) {
+ if (!basename.len) {
+ dir.st = {};
+ dir.unmatched.clear();
+ dir.arena.Reuse();
+ } else if (str.Eq(basename, StringView(".git/"))) {
+ return;
+ }
+ char* path = dir.arena.StrCat(dir.path, basename);
+ dir.unmatched.push_back(path);
+ AddCandidate(basename.len ? "new" : "unreadable", path);
+ };
+
+ auto StatFiles = [&]() {
+ struct stat st;
+ for (const git_index_entry* file : dir.files) {
+ if (fstatat(*dir_fd, Basename(file), &st, AT_SYMLINK_NOFOLLOW)) {
+ AddCandidate(errno == ENOENT ? "deleted" : "unreadable", file->path);
+ } else if (IsModified(file, st, caps)) {
+ AddCandidate(nullptr, file->path);
+ }
+ }
+ };
+
+ ssize_t d = 0;
+ if ((it == begin || (d = it[-1]->depth + 1 - dir.depth) < kDirStackSize) && dir_fd[d] >= 0) {
+ CHECK(d >= 0);
+ int fd = OpenDir(dir_fd[d], arena.StrDup(dir.basename.ptr, dir.basename.len));
+ for (ssize_t i = 0; i != d; ++i) Close(dir_fd[i]);
+ std::rotate(dir_fd, dir_fd + (d ? d : kDirStackSize) - 1, dir_fd + kDirStackSize);
+ Close(*dir_fd);
+ *dir_fd = fd;
+ } else {
+ CloseAll();
+ if (dir.path.len) {
+ CHECK(dir.path.ptr[0] != '/');
+ CHECK(dir.path.ptr[dir.path.len - 1] == '/');
+ *dir_fd = OpenDir(root_fd, arena.StrDup(dir.path.ptr, dir.path.len - 1));
+ } else {
+ VERIFY((*dir_fd = dup(root_fd)) >= 0) << Errno();
+ }
+ }
+ if (*dir_fd < 0) {
+ CloseAll();
+ AddUnmached("");
+ continue;
+ }
+
+ if (!opts.include_untracked) {
+ StatFiles();
+ continue;
+ }
+
+ if (opts.untracked_cache != Tribool::kFalse) {
+ struct stat st;
+ if (fstat(*dir_fd, &st)) {
+ AddUnmached("");
+ continue;
+ }
+ if (opts.untracked_cache == Tribool::kTrue && StatEq(st, dir.st)) {
+ StatFiles();
+ for (const char* path : dir.unmatched) AddCandidate("new", path);
+ continue;
+ }
+ dir.st = st;
+ }
+
+ entries.clear();
+ arena.Reuse();
+ if (!ListDir(*dir_fd, arena, entries, caps.precompose_unicode, caps.case_sensitive)) {
+ AddUnmached("");
+ continue;
+ }
+ dir.unmatched.clear();
+ dir.arena.Reuse();
+
+ const git_index_entry* const* file = dir.files.data();
+ const git_index_entry* const* file_end = file + dir.files.size();
+ const StringView* subdir = dir.subdirs.data();
+ const StringView* subdir_end = subdir + dir.subdirs.size();
+
+ for (char* entry : entries) {
+ bool matched = false;
+
+ for (; file != file_end; ++file) {
+ int cmp = str.Cmp(Basename(*file), entry);
+ if (cmp < 0) {
+ AddCandidate("deleted", (*file)->path);
+ } else if (cmp == 0) {
+ struct stat st;
+ if (fstatat(*dir_fd, entry, &st, AT_SYMLINK_NOFOLLOW)) {
+ AddCandidate("unreadable", (*file)->path);
+ } else if (IsModified(*file, st, caps)) {
+ AddCandidate(nullptr, (*file)->path);
+ }
+ matched = true;
+ ++file;
+ break;
+ } else {
+ break;
+ }
+ }
+
+ if (matched) continue;
+
+ for (; subdir != subdir_end; ++subdir) {
+ int cmp = str.Cmp(*subdir, entry);
+ if (cmp > 0) break;
+ if (cmp == 0) {
+ matched = true;
+ ++subdir;
+ break;
+ }
+ }
+
+ if (!matched) {
+ StringView basename(entry);
+ if (entry[-1] == DT_DIR) entry[basename.len++] = '/';
+ AddUnmached(basename);
+ }
+ }
+
+ for (; file != file_end; ++file) AddCandidate("deleted", (*file)->path);
+ }
+
+ return dirty_candidates;
+}
+
+} // namespace
+
+RepoCaps::RepoCaps(git_repository* repo, git_index* index) {
+ trust_filemode = git_index_is_filemode_trustworthy(index);
+ has_symlinks = git_index_supports_symlinks(index);
+ case_sensitive = git_index_is_case_sensitive(index);
+ precompose_unicode = git_index_precompose_unicode(index);
+ LOG(DEBUG) << "Repository capabilities for " << Print(git_repository_workdir(repo)) << ": "
+ << "is_filemode_trustworthy = " << std::boolalpha << trust_filemode << ", "
+ << "index_supports_symlinks = " << std::boolalpha << has_symlinks << ", "
+ << "index_is_case_sensitive = " << std::boolalpha << case_sensitive << ", "
+ << "precompose_unicode = " << std::boolalpha << precompose_unicode;
+}
+
+Index::Index(git_repository* repo, git_index* index)
+ : dirs_(&arena_),
+ splits_(&arena_),
+ git_index_(index),
+ root_dir_(git_repository_workdir(repo)),
+ caps_(repo, index) {
+ size_t total_weight = InitDirs(index);
+ InitSplits(total_weight);
+}
+
+size_t Index::InitDirs(git_index* index) {
+ const Str<> str(git_index_is_case_sensitive(index));
+ const size_t index_size = git_index_entrycount(index);
+ dirs_.reserve(index_size / 8);
+ std::stack stack;
+ stack.push(arena_.DirectInit(&arena_));
+
+ size_t total_weight = 0;
+ auto PopDir = [&] {
+ CHECK(!stack.empty());
+ IndexDir* top = stack.top();
+ CHECK(top->depth + 1 == stack.size());
+ if (!std::is_sorted(top->subdirs.begin(), top->subdirs.end(), str.Lt)) {
+ StrSort(top->subdirs.begin(), top->subdirs.end(), str.case_sensitive);
+ }
+ total_weight += Weight(*top);
+ dirs_.push_back(top);
+ stack.pop();
+ };
+
+ for (size_t i = 0; i != index_size; ++i) {
+ const git_index_entry* entry = git_index_get_byindex_no_sort(index, i);
+ IndexDir* prev = stack.top();
+ size_t common_len, common_depth;
+ CommonDir(str, prev->path.ptr, entry->path, &common_len, &common_depth);
+ CHECK(common_depth <= prev->depth);
+
+ for (size_t i = common_depth; i != prev->depth; ++i) PopDir();
+
+ for (const char* p = entry->path + common_len; (p = std::strchr(p, '/')); ++p) {
+ IndexDir* top = stack.top();
+ StringView subdir(entry->path + top->path.len, p);
+ top->subdirs.push_back(subdir);
+ IndexDir* dir = arena_.DirectInit(&arena_);
+ dir->path = StringView(entry->path, p - entry->path + 1);
+ dir->basename = subdir;
+ dir->depth = stack.size();
+ CHECK(dir->path.ptr[dir->path.len - 1] == '/');
+ stack.push(dir);
+ }
+
+ CHECK(!stack.empty());
+ IndexDir* dir = stack.top();
+ dir->files.push_back(entry);
+ }
+
+ CHECK(!stack.empty());
+ do {
+ PopDir();
+ } while (!stack.empty());
+ std::reverse(dirs_.begin(), dirs_.end());
+
+ return total_weight;
+}
+
+void Index::InitSplits(size_t total_weight) {
+ constexpr size_t kMinShardWeight = 512;
+ const size_t kNumShards = 16 * GlobalThreadPool()->num_threads();
+ const size_t shard_weight = std::max(kMinShardWeight, total_weight / kNumShards);
+
+ splits_.reserve(kNumShards + 1);
+ splits_.push_back(0);
+
+ for (size_t i = 0, w = 0; i != dirs_.size(); ++i) {
+ w += Weight(*dirs_[i]);
+ if (w >= shard_weight) {
+ w = 0;
+ splits_.push_back(i + 1);
+ }
+ }
+
+ if (splits_.back() != dirs_.size()) splits_.push_back(dirs_.size());
+ CHECK(splits_.size() <= kNumShards + 1);
+ CHECK(std::is_sorted(splits_.begin(), splits_.end()));
+ CHECK(std::adjacent_find(splits_.begin(), splits_.end()) == splits_.end());
+}
+
+std::vector Index::GetDirtyCandidates(const ScanOpts& opts) {
+ int root_fd = open(root_dir_, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ VERIFY(root_fd >= 0);
+ ON_SCOPE_EXIT(&) { CHECK(!close(root_fd)) << Errno(); };
+
+ CHECK(!splits_.empty());
+
+ std::mutex mutex;
+ std::condition_variable cv;
+ size_t inflight = splits_.size() - 1;
+ bool error = false;
+ std::vector res;
+
+ for (size_t i = 0; i != splits_.size() - 1; ++i) {
+ size_t from = splits_[i];
+ size_t to = splits_[i + 1];
+
+ GlobalThreadPool()->Schedule([&, from, to]() {
+ ON_SCOPE_EXIT(&) {
+ std::unique_lock lock(mutex);
+ CHECK(inflight);
+ if (--inflight == 0) cv.notify_one();
+ };
+ try {
+ std::vector candidates =
+ ScanDirs(git_index_, root_fd, dirs_.data() + from, dirs_.data() + to, caps_, opts);
+ if (!candidates.empty()) {
+ std::unique_lock lock(mutex);
+ res.insert(res.end(), candidates.begin(), candidates.end());
+ }
+ } catch (const Exception&) {
+ std::unique_lock lock(mutex);
+ error = true;
+ }
+ });
+ }
+
+ {
+ std::unique_lock lock(mutex);
+ while (inflight) cv.wait(lock);
+ }
+
+ VERIFY(!error);
+ StrSort(res.begin(), res.end(), git_index_is_case_sensitive(git_index_));
+ auto StrEq = [](const char* a, const char* b) { return !strcmp(a, b); };
+ res.erase(std::unique(res.begin(), res.end(), StrEq), res.end());
+ return res;
+}
+
+} // namespace gitstatus
diff --git a/gitstatus/src/index.h b/gitstatus/src/index.h
new file mode 100644
index 00000000..bbf95673
--- /dev/null
+++ b/gitstatus/src/index.h
@@ -0,0 +1,84 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#ifndef ROMKATV_GITSTATUS_INDEX_H_
+#define ROMKATV_GITSTATUS_INDEX_H_
+
+#include
+
+#include
+
+#include
+#include
+#include
+
+#include "arena.h"
+#include "options.h"
+#include "string_view.h"
+#include "tribool.h"
+
+namespace gitstatus {
+
+struct RepoCaps {
+ RepoCaps(git_repository* repo, git_index* index);
+
+ bool trust_filemode;
+ bool has_symlinks;
+ bool case_sensitive;
+ bool precompose_unicode;
+};
+
+struct ScanOpts {
+ bool include_untracked;
+ Tribool untracked_cache;
+};
+
+struct IndexDir {
+ explicit IndexDir(Arena* arena) : files(arena), subdirs(arena) {}
+
+ StringView path;
+ StringView basename;
+ size_t depth = 0;
+ struct stat st = {};
+ WithArena> files;
+ WithArena> subdirs;
+
+ Arena arena;
+ std::vector unmatched;
+};
+
+class Index {
+ public:
+ Index(git_repository* repo, git_index* index);
+
+ std::vector GetDirtyCandidates(const ScanOpts& opts);
+
+ private:
+ size_t InitDirs(git_index* index);
+ void InitSplits(size_t total_weight);
+
+ Arena arena_;
+ WithArena> dirs_;
+ WithArena> splits_;
+ git_index* git_index_;
+ const char* root_dir_;
+ RepoCaps caps_;
+};
+
+} // namespace gitstatus
+
+#endif // ROMKATV_GITSTATUS_GIT_H_
diff --git a/gitstatus/src/logging.cc b/gitstatus/src/logging.cc
new file mode 100644
index 00000000..fb9ac9ea
--- /dev/null
+++ b/gitstatus/src/logging.cc
@@ -0,0 +1,139 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#include "logging.h"
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace gitstatus {
+
+namespace internal_logging {
+
+namespace {
+
+std::mutex g_log_mutex;
+
+constexpr char kHexLower[] = {'0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+void FormatThreadId(char (&out)[2 * sizeof(std::uintptr_t) + 1]) {
+ std::uintptr_t tid = (std::uintptr_t)pthread_self();
+ char* p = out + sizeof(out) - 1;
+ *p = 0;
+ do {
+ --p;
+ *p = kHexLower[tid & 0xF];
+ tid >>= 4;
+ } while (p != out);
+}
+
+void FormatCurrentTime(char (&out)[64]) {
+ std::time_t time = std::time(nullptr);
+ struct tm tm;
+ if (localtime_r(&time, &tm) != &tm || std::strftime(out, sizeof(out), "%F %T", &tm) == 0) {
+ std::strcpy(out, "undef");
+ }
+}
+
+} // namespace
+
+LogStreamBase::LogStreamBase(const char* file, int line, LogLevel lvl)
+ : errno_(errno), file_(file), line_(line), lvl_(LogLevelStr(lvl)) {
+ strm_ = std::make_unique();
+}
+
+void LogStreamBase::Flush() {
+ {
+ std::string msg = strm_->str();
+ char tid[2 * sizeof(std::uintptr_t) + 1];
+ FormatThreadId(tid);
+ char time[64];
+ FormatCurrentTime(time);
+
+ std::unique_lock lock(g_log_mutex);
+ std::fprintf(stderr, "[%s %s %s %s:%d] %s\n", time, tid, lvl_, file_, line_, msg.c_str());
+ }
+ strm_.reset();
+ errno = errno_;
+}
+
+std::ostream& operator<<(std::ostream& strm, Errno e) {
+ // GNU C Library uses a buffer of 1024 characters for strerror(). Mimic to avoid truncations.
+ char buf[1024];
+ auto x = strerror_r(e.err, buf, sizeof(buf));
+ // There are two versions of strerror_r with different semantics. We can figure out which
+ // one we've got by looking at the result type.
+ if (std::is_same::value) {
+ // XSI-compliant version.
+ strm << (x ? "unknown error" : buf);
+ } else if (std::is_same::value) {
+ // GNU-specific version.
+ strm << x;
+ } else {
+ // Something else entirely.
+ strm << "unknown error";
+ }
+ return strm;
+}
+
+} // namespace internal_logging
+
+LogLevel g_min_log_level = INFO;
+
+const char* LogLevelStr(LogLevel lvl) {
+ switch (lvl) {
+ case DEBUG:
+ return "DEBUG";
+ case INFO:
+ return "INFO";
+ case WARN:
+ return "WARN";
+ case ERROR:
+ return "ERROR";
+ case FATAL:
+ return "FATAL";
+ }
+ return "UNKNOWN";
+}
+
+bool ParseLogLevel(const char* s, LogLevel& lvl) {
+ if (!s)
+ return false;
+ else if (!std::strcmp(s, "DEBUG"))
+ lvl = DEBUG;
+ else if (!std::strcmp(s, "INFO"))
+ lvl = INFO;
+ else if (!std::strcmp(s, "WARN"))
+ lvl = WARN;
+ else if (!std::strcmp(s, "ERROR"))
+ lvl = ERROR;
+ else if (!std::strcmp(s, "FATAL"))
+ lvl = FATAL;
+ else
+ return false;
+ return true;
+}
+
+} // namespace gitstatus
diff --git a/gitstatus/src/logging.h b/gitstatus/src/logging.h
new file mode 100644
index 00000000..6ddb2e16
--- /dev/null
+++ b/gitstatus/src/logging.h
@@ -0,0 +1,124 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#ifndef ROMKATV_GITSTATUS_LOGGING_H_
+#define ROMKATV_GITSTATUS_LOGGING_H_
+
+#include
+#include
+#include
+#include
+
+#define LOG(severity) LOG_I(severity)
+
+#define LOG_I(severity) \
+ (::gitstatus::severity < ::gitstatus::g_min_log_level) \
+ ? static_cast(0) \
+ : ::gitstatus::internal_logging::Assignable() = \
+ ::gitstatus::internal_logging::LogStream<::gitstatus::severity>(__FILE__, __LINE__, \
+ ::gitstatus::severity) \
+ .ref()
+
+namespace gitstatus {
+
+enum LogLevel {
+ DEBUG,
+ INFO,
+ WARN,
+ ERROR,
+ FATAL,
+};
+
+const char* LogLevelStr(LogLevel lvl);
+bool ParseLogLevel(const char* s, LogLevel& lvl);
+
+extern LogLevel g_min_log_level;
+
+namespace internal_logging {
+
+struct Assignable {
+ template
+ void operator=(const T&) const {}
+};
+
+class LogStreamBase {
+ public:
+ LogStreamBase(const char* file, int line, LogLevel lvl);
+
+ LogStreamBase& ref() { return *this; }
+ std::ostream& strm() { return *strm_; }
+ int stashed_errno() const { return errno_; }
+
+ protected:
+ void Flush();
+
+ private:
+ int errno_;
+ const char* file_;
+ int line_;
+ const char* lvl_;
+ std::unique_ptr strm_;
+};
+
+template
+class LogStream : public LogStreamBase {
+ public:
+ using LogStreamBase::LogStreamBase;
+ ~LogStream() { this->Flush(); }
+};
+
+template <>
+class LogStream : public LogStreamBase {
+ public:
+ using LogStreamBase::LogStreamBase;
+ ~LogStream() __attribute__((noreturn)) {
+ this->Flush();
+ std::abort();
+ }
+};
+
+template
+LogStreamBase& operator<<(LogStreamBase& strm, const T& val) {
+ strm.strm() << val;
+ return strm;
+}
+
+inline LogStreamBase& operator<<(LogStreamBase& strm, std::ostream& (*manip)(std::ostream&)) {
+ strm.strm() << manip;
+ return strm;
+}
+
+struct Errno {
+ int err;
+};
+
+std::ostream& operator<<(std::ostream& strm, Errno e);
+
+struct StashedErrno {};
+
+inline LogStreamBase& operator<<(LogStreamBase& strm, StashedErrno) {
+ return strm << Errno{strm.stashed_errno()};
+}
+
+} // namespace internal_logging
+
+inline internal_logging::Errno Errno(int err) { return {err}; }
+inline internal_logging::StashedErrno Errno() { return {}; }
+
+} // namespace gitstatus
+
+#endif // ROMKATV_GITSTATUS_LOGGING_H_
diff --git a/gitstatus/src/options.cc b/gitstatus/src/options.cc
new file mode 100644
index 00000000..b7abe5db
--- /dev/null
+++ b/gitstatus/src/options.cc
@@ -0,0 +1,362 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#include "options.h"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include "print.h"
+
+namespace gitstatus {
+
+namespace {
+
+long ParseLong(const char* s) {
+ errno = 0;
+ char* end = nullptr;
+ long res = std::strtol(s, &end, 10);
+ if (*end || end == s || errno) {
+ std::cerr << "gitstatusd: not an integer: " << s << std::endl;
+ std::exit(10);
+ }
+ return res;
+}
+
+long ParseInt(const char* s) {
+ long res = ParseLong(s);
+ if (res < INT_MIN || res > INT_MAX) {
+ std::cerr << "gitstatusd: integer out of bounds: " << s << std::endl;
+ std::exit(10);
+ }
+ return res;
+}
+
+size_t ParseSizeT(const char* s) {
+ static_assert(sizeof(long) <= sizeof(size_t), "");
+ long res = ParseLong(s);
+ return res >= 0 ? res : -1;
+}
+
+void PrintUsage() {
+ std::cout << "Usage: gitstatusd [OPTION]...\n"
+ << "Print machine-readable status of the git repos for directories in stdin.\n"
+ << "\n"
+ << "OPTIONS\n"
+ << " -l, --lock-fd=NUM [default=-1]\n"
+ << " If non-negative, check whether the specified file descriptor is locked when\n"
+ << " not receiving any requests for one second; exit if it isn't locked.\n"
+ << "\n"
+ << " -p, --parent-pid=NUM [default=-1]\n"
+ << " If non-negative, send signal 0 to the specified PID when not receiving any\n"
+ << " requests for one second; exit if signal sending fails.\n"
+ << "\n"
+ << " -t, --num-threads=NUM [default=1]\n"
+ << " Use this many threads to scan git workdir for unstaged and untracked files.\n"
+ << " Empirically, setting this parameter to twice the number of virtual CPU yields\n"
+ << " maximum performance.\n"
+ << "\n"
+ << " -v, --log-level=STR [default=INFO]\n"
+ << " Don't write entries to log whose log level is below this. Log levels in\n"
+ << " increasing order: DEBUG, INFO, WARN, ERROR, FATAL.\n"
+ << "\n"
+ << " -r, --repo-ttl-seconds=NUM [default=3600]\n"
+ << " Close git repositories that haven't been used for this long. This is meant to\n"
+ << " release resources such as memory and file descriptors. The next request for a\n"
+ << " repo that's been closed is much slower than for a repo that hasn't been.\n"
+ << " Negative value means infinity.\n"
+ << "\n"
+ << " -z, --max-commit-summary-length=NUM [default=256]\n"
+ << " Truncate commit summary if it's longer than this many bytes.\n"
+ << "\n"
+ << " -s, --max-num-staged=NUM [default=1]\n"
+ << " Report at most this many staged changes; negative value means infinity.\n"
+ << "\n"
+ << " -u, --max-num-unstaged=NUM [default=1]\n"
+ << " Report at most this many unstaged changes; negative value means infinity.\n"
+ << "\n"
+ << " -c, --max-num-conflicted=NUM [default=1]\n"
+ << " Report at most this many conflicted changes; negative value means infinity.\n"
+ << "\n"
+ << " -d, --max-num-untracked=NUM [default=1]\n"
+ << " Report at most this many untracked files; negative value means infinity.\n"
+ << "\n"
+ << " -m, --dirty-max-index-size=NUM [default=-1]\n"
+ << " If a repo has more files in its index than this, override --max-num-unstaged\n"
+ << " and --max-num-untracked (but not --max-num-staged) with zeros; negative value\n"
+ << " means infinity.\n"
+ << "\n"
+ << " -e, --recurse-untracked-dirs\n"
+ << " Count files within untracked directories like `git status --untracked-files`.\n"
+ << "\n"
+ << " -U, --ignore-status-show-untracked-files\n"
+ << " Unless this option is specified, report zero untracked files for repositories\n"
+ << " with status.showUntrackedFiles = false.\n"
+ << "\n"
+ << " -W, --ignore-bash-show-untracked-files\n"
+ << " Unless this option is specified, report zero untracked files for repositories\n"
+ << " with bash.showUntrackedFiles = false.\n"
+ << "\n"
+ << " -D, --ignore-bash-show-dirty-state\n"
+ << " Unless this option is specified, report zero staged, unstaged and conflicted\n"
+ << " changes for repositories with bash.showDirtyState = false.\n"
+ << "\n"
+ << " -V, --version\n"
+ << " Print gitstatusd version and exit.\n"
+ << "\n"
+ << " -G, --version-glob=STR [default=*]\n"
+ << " Immediately exit with code 11 if gitstatusd version (see --version) doesn't\n"
+ << " does not match the specified pattern. Matching is done with fnmatch(3)\n"
+ << " without flags.\n"
+ << "\n"
+ << " -h, --help\n"
+ << " Display this help and exit.\n"
+ << "\n"
+ << "INPUT\n"
+ << "\n"
+ << " Requests are read from stdin, separated by ascii 30 (record separator). Each\n"
+ << " request is made of the following fields, in the specified order, separated by\n"
+ << " ascii 31 (unit separator):\n"
+ << "\n"
+ << " 1. Request ID. Any string. Can be empty.\n"
+ << " 2. Path to the directory for which git stats are being requested.\n"
+ << " If the first character is ':', it is removed and the remaining path\n"
+ << " is treated as GIT_DIR.\n"
+ << " 3. (Optional) '1' to disable computation of anything that requires reading\n"
+ << " git index; '0' for the default behavior of computing everything.\n"
+ << "\n"
+ << "OUTPUT\n"
+ << "\n"
+ << " For every request read from stdin there is response written to stdout.\n"
+ << " Responses are separated by ascii 30 (record separator). Each response is made\n"
+ << " of the following fields, in the specified order, separated by ascii 31\n"
+ << " (unit separator):\n"
+ << "\n"
+ << " 1. Request id. The same as the first field in the request.\n"
+ << " 2. 0 if the directory isn't a git repo, 1 otherwise. If 0, all the\n"
+ << " following fields are missing.\n"
+ << " 3. Absolute path to the git repository workdir.\n"
+ << " 4. Commit hash that HEAD is pointing to. 40 hex digits.\n"
+ << " 5. Local branch name or empty if not on a branch.\n"
+ << " 6. Upstream branch name. Can be empty.\n"
+ << " 7. The remote name, e.g. \"upstream\" or \"origin\".\n"
+ << " 8. Remote URL. Can be empty.\n"
+ << " 9. Repository state, A.K.A. action. Can be empty.\n"
+ << " 10. The number of files in the index.\n"
+ << " 11. The number of staged changes.\n"
+ << " 12. The number of unstaged changes.\n"
+ << " 13. The number of conflicted changes.\n"
+ << " 14. The number of untracked files.\n"
+ << " 15. Number of commits the current branch is ahead of upstream.\n"
+ << " 16. Number of commits the current branch is behind upstream.\n"
+ << " 17. The number of stashes.\n"
+ << " 18. The last tag (in lexicographical order) that points to the same\n"
+ << " commit as HEAD.\n"
+ << " 19. The number of unstaged deleted files.\n"
+ << " 20. The number of staged new files.\n"
+ << " 21. The number of staged deleted files.\n"
+ << " 22. The push remote name, e.g. \"upstream\" or \"origin\".\n"
+ << " 23. Push remote URL. Can be empty.\n"
+ << " 24. Number of commits the current branch is ahead of push remote.\n"
+ << " 25. Number of commits the current branch is behind push remote.\n"
+ << " 26. Number of files in the index with skip-worktree bit set.\n"
+ << " 27. Number of files in the index with assume-unchanged bit set.\n"
+ << " 28. Encoding of the HEAD's commit message. Empty value means UTF-8.\n"
+ << " 29. The first paragraph of the HEAD's commit message as one line.\n"
+ << "\n"
+ << "Note: Renamed files are reported as deleted plus new.\n"
+ << "\n"
+ << "EXAMPLE\n"
+ << "\n"
+ << " Send a single request and print response (zsh syntax):\n"
+ << "\n"
+ << " local req_id=id\n"
+ << " local dir=$PWD\n"
+ << " echo -nE $req_id$'\\x1f'$dir$'\\x1e' | ./gitstatusd | {\n"
+ << " local resp\n"
+ << " IFS=$'\\x1f' read -rd $'\\x1e' -A resp && print -lr -- \"${(@qq)resp}\"\n"
+ << " }\n"
+ << "\n"
+ << " Output:"
+ << "\n"
+ << " 'id'\n"
+ << " '1'\n"
+ << " '/home/romka/gitstatus'\n"
+ << " 'bf46bf03dbab7108801b53f8a720caee8464c9c3'\n"
+ << " 'master'\n"
+ << " 'master'\n"
+ << " 'origin'\n"
+ << " 'git@github.com:romkatv/gitstatus.git'\n"
+ << " ''\n"
+ << " '70'\n"
+ << " '1'\n"
+ << " '0'\n"
+ << " '0'\n"
+ << " '2'\n"
+ << " '0'\n"
+ << " '0'\n"
+ << " ''\n"
+ << " '0'\n"
+ << " '0'\n"
+ << " '0'\n"
+ << " ''\n"
+ << " ''\n"
+ << " '0'\n"
+ << " '0'\n"
+ << " '0'\n"
+ << " '0'\n"
+ << " ''\n"
+ << " 'add a build server for darwin-arm64'\n"
+ << "\n"
+ << "EXIT STATUS\n"
+ << "\n"
+ << " The command returns zero on success (when printing help or on EOF),\n"
+ << " non-zero on failure. In the latter case the output is unspecified.\n"
+ << "\n"
+ << "COPYRIGHT\n"
+ << "\n"
+ << " Copyright 2019 Roman Perepelitsa\n"
+ << " This is free software; see https://github.com/romkatv/gitstatus for copying\n"
+ << " conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR\n"
+ << " A PARTICULAR PURPOSE." << std::endl;
+}
+
+const char* Version() {
+#define _INTERNAL_GITSTATUS_STRINGIZE(x) _INTERNAL_GITSTATUS_STRINGIZE_I(x)
+#define _INTERNAL_GITSTATUS_STRINGIZE_I(x) #x
+ return _INTERNAL_GITSTATUS_STRINGIZE(GITSTATUS_VERSION);
+#undef _INTERNAL_GITSTATUS_STRINGIZE_I
+#undef _INTERNAL_GITSTATUS_STRINGIZE
+}
+
+} // namespace
+
+Options ParseOptions(int argc, char** argv) {
+ const struct option opts[] = {{"help", no_argument, nullptr, 'h'},
+ {"version", no_argument, nullptr, 'V'},
+ {"version-glob", required_argument, nullptr, 'G'},
+ {"lock-fd", required_argument, nullptr, 'l'},
+ {"parent-pid", required_argument, nullptr, 'p'},
+ {"num-threads", required_argument, nullptr, 't'},
+ {"log-level", required_argument, nullptr, 'v'},
+ {"repo-ttl-seconds", required_argument, nullptr, 'r'},
+ {"max-commit-summary-length", required_argument, nullptr, 'z'},
+ {"max-num-staged", required_argument, nullptr, 's'},
+ {"max-num-unstaged", required_argument, nullptr, 'u'},
+ {"max-num-conflicted", required_argument, nullptr, 'c'},
+ {"max-num-untracked", required_argument, nullptr, 'd'},
+ {"dirty-max-index-size", required_argument, nullptr, 'm'},
+ {"recurse-untracked-dirs", no_argument, nullptr, 'e'},
+ {"ignore-status-show-untracked-files", no_argument, nullptr, 'U'},
+ {"ignore-bash-show-untracked-files", no_argument, nullptr, 'W'},
+ {"ignore-bash-show-dirty-state", no_argument, nullptr, 'D'},
+ {}};
+ Options res;
+ while (true) {
+ switch (getopt_long(argc, argv, "hVG:l:p:t:v:r:z:s:u:c:d:m:eUWD", opts, nullptr)) {
+ case -1:
+ if (optind != argc) {
+ std::cerr << "unexpected positional argument: " << argv[optind] << std::endl;
+ std::exit(10);
+ }
+ return res;
+ case 'h':
+ PrintUsage();
+ std::exit(0);
+ case 'V':
+ std::cout << Version() << std::endl;
+ std::exit(0);
+ case 'G':
+ if (int err = fnmatch(optarg, Version(), 0)) {
+ if (err != FNM_NOMATCH) {
+ std::cerr << "Cannot match " << Print(Version()) << " against pattern "
+ << Print(optarg) << ": error " << err;
+ std::exit(10);
+ }
+ std::cerr << "Version mismatch. Wanted (pattern): " << Print(optarg)
+ << ". Actual: " << Print(Version()) << "." << std::endl;
+ std::exit(11);
+ }
+ break;
+ case 'l':
+ res.lock_fd = ParseInt(optarg);
+ break;
+ case 'p':
+ res.parent_pid = ParseInt(optarg);
+ break;
+ case 'v':
+ if (!ParseLogLevel(optarg, res.log_level)) {
+ std::cerr << "invalid log level: " << optarg << std::endl;
+ std::exit(10);
+ }
+ break;
+ case 'r':
+ res.repo_ttl = std::chrono::seconds(ParseLong(optarg));
+ break;
+ case 't': {
+ long n = ParseLong(optarg);
+ if (n <= 0) {
+ std::cerr << "invalid number of threads: " << n << std::endl;
+ std::exit(10);
+ }
+ res.num_threads = n;
+ break;
+ }
+ case 'z':
+ res.max_commit_summary_length = ParseSizeT(optarg);
+ break;
+ case 's':
+ res.max_num_staged = ParseSizeT(optarg);
+ break;
+ case 'u':
+ res.max_num_unstaged = ParseSizeT(optarg);
+ break;
+ case 'c':
+ res.max_num_conflicted = ParseSizeT(optarg);
+ break;
+ case 'd':
+ res.max_num_untracked = ParseSizeT(optarg);
+ break;
+ case 'm':
+ res.dirty_max_index_size = ParseSizeT(optarg);
+ break;
+ case 'e':
+ res.recurse_untracked_dirs = true;
+ break;
+ case 'U':
+ res.ignore_status_show_untracked_files = true;
+ break;
+ case 'W':
+ res.ignore_bash_show_untracked_files = true;
+ break;
+ case 'D':
+ res.ignore_bash_show_dirty_state = true;
+ break;
+ default:
+ std::exit(10);
+ }
+ }
+}
+
+} // namespace gitstatus
diff --git a/gitstatus/src/options.h b/gitstatus/src/options.h
new file mode 100644
index 00000000..bb373155
--- /dev/null
+++ b/gitstatus/src/options.h
@@ -0,0 +1,78 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#ifndef ROMKATV_GITSTATUS_OPTIONS_H_
+#define ROMKATV_GITSTATUS_OPTIONS_H_
+
+#include
+#include
+
+#include "logging.h"
+#include "time.h"
+
+namespace gitstatus {
+
+struct Limits {
+ // Truncate commit summary if it's longer than this many bytes.
+ size_t max_commit_summary_length = 256;
+ // Report at most this many staged changes.
+ size_t max_num_staged = 1;
+ // Report at most this many unstaged changes.
+ size_t max_num_unstaged = 1;
+ // Report at most this many conflicted changes.
+ size_t max_num_conflicted = 1;
+ // Report at most this many untracked files.
+ size_t max_num_untracked = 1;
+ // If a repo has more files in its index than this, override max_num_unstaged and
+ // max_num_untracked (but not max_num_staged) with zeros.
+ size_t dirty_max_index_size = -1;
+ // If true, report untracked files like `git status --untracked-files`.
+ bool recurse_untracked_dirs = false;
+ // Unless true, report zero untracked files for repositories with
+ // status.showUntrackedFiles = false.
+ bool ignore_status_show_untracked_files = false;
+ // Unless true, report zero untracked files for repositories with
+ // bash.showUntrackedFiles = false.
+ bool ignore_bash_show_untracked_files = false;
+ // Unless true, report zero staged, unstaged and conflicted changes for repositories with
+ // bash.showDirtyState = false.
+ bool ignore_bash_show_dirty_state = false;
+};
+
+struct Options : Limits {
+ // Use this many threads to scan git workdir for unstaged and untracked files. Must be positive.
+ size_t num_threads = 1;
+ // If non-negative, check whether the specified file descriptor is locked when not receiving any
+ // requests for one second; exit if it isn't locked.
+ int lock_fd = -1;
+ // If non-negative, send signal 0 to the specified PID when not receiving any requests for one
+ // second; exit if signal sending fails.
+ int parent_pid = -1;
+ // Don't write entries to log whose log level is below this. Log levels in increasing order:
+ // DEBUG, INFO, WARN, ERROR, FATAL.
+ LogLevel log_level = INFO;
+ // Close git repositories that haven't been used for this long. This is meant to release resources
+ // such as memory and file descriptors. The next request for a repo that's been closed is much
+ // slower than for a repo that hasn't been. Negative value means infinity.
+ Duration repo_ttl = std::chrono::seconds(3600);
+};
+
+Options ParseOptions(int argc, char** argv);
+
+} // namespace gitstatus
+
+#endif // ROMKATV_GITSTATUS_OPTIONS_H_
diff --git a/gitstatus/src/print.h b/gitstatus/src/print.h
new file mode 100644
index 00000000..949f946b
--- /dev/null
+++ b/gitstatus/src/print.h
@@ -0,0 +1,101 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#ifndef ROMKATV_GITSTATUS_PRINT_H_
+#define ROMKATV_GITSTATUS_PRINT_H_
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "string_view.h"
+#include "strings.h"
+
+namespace gitstatus {
+
+template
+struct Printable {
+ const T& value;
+};
+
+template
+Printable Print(const T& val) {
+ return {val};
+}
+
+template
+std::ostream& operator<<(std::ostream& strm, const Printable& p) {
+ static_assert(!std::is_pointer>(), "");
+ return strm << p.value;
+}
+
+inline std::ostream& operator<<(std::ostream& strm, const Printable& p) {
+ Quote(strm, p.value.ptr, p.value.ptr + p.value.len);
+ return strm;
+}
+
+inline std::ostream& operator<<(std::ostream& strm, const Printable& p) {
+ Quote(strm, p.value.data(), p.value.data() + p.value.size());
+ return strm;
+}
+
+inline std::ostream& operator<<(std::ostream& strm, const Printable& p) {
+ Quote(strm, p.value, p.value ? p.value + std::strlen(p.value) : nullptr);
+ return strm;
+}
+
+inline std::ostream& operator<<(std::ostream& strm, const Printable& p) {
+ Quote(strm, p.value, p.value ? p.value + std::strlen(p.value) : nullptr);
+ return strm;
+}
+
+template
+std::ostream& operator<<(std::ostream& strm, const Printable>& p) {
+ return strm << '{' << Print(p.value.first) << ", " << Print(p.value.second) << '}';
+}
+
+template
+std::ostream& operator<<(std::ostream& strm, const Printable>& p) {
+ strm << '[';
+ for (size_t i = 0; i != p.value.size(); ++i) {
+ if (i) strm << ", ";
+ strm << Print(p.value[i]);
+ }
+ strm << ']';
+ return strm;
+}
+
+inline std::ostream& operator<<(std::ostream& strm, const Printable& p) {
+ strm << p.value.tv_sec << '.' << std::setw(9) << std::setfill('0') << p.value.tv_nsec;
+ return strm;
+}
+
+inline std::ostream& operator<<(std::ostream& strm, const Printable& p) {
+ strm << p.value.seconds << '.' << std::setw(9) << std::setfill('0') << p.value.nanoseconds;
+ return strm;
+}
+
+} // namespace gitstatus
+
+#endif // ROMKATV_GITSTATUS_PRINT_H_
diff --git a/gitstatus/src/repo.cc b/gitstatus/src/repo.cc
new file mode 100644
index 00000000..a81594a4
--- /dev/null
+++ b/gitstatus/src/repo.cc
@@ -0,0 +1,503 @@
+// Copyright 2019 Roman Perepelitsa.
+//
+// This file is part of GitStatus.
+//
+// GitStatus is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GitStatus is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GitStatus. If not, see .
+
+#include "repo.h"
+
+#include
+#include
+#include