ZSH Configuration with Auto-suggestions, Sub-string Search/auto-completion, Syntax-highlight, and Git with Powerlevel10k (P10k)

Published on: Feb 21, 2026

ZSH Configuration

Makover Linux terminal with ZSH. Configure ZSH with Auto-suggestions, substring history search or auto-completion, command syntax-highlight, and Git helpers with Powerlevel10k (p10k). Without using oh-my-zsh, manually install everything and customise.

Install ZSH

First, install ZSH and make it the default shell.

1$ sudo apt install zsh -y
2

After it is installed, you can try switching from bash to zsh by simply using the command zsh

1$ zsh
2

To make ZSH the default shell,

1$ sudo chsh -s $(which zsh)
2

Now, ZSH is the default shell. Verify this by exiting the terminal and opening it again.

Check the ~/.zshrc file if it exists, and open the configuration. This is the ZSH configuration file that we will customise later. This is the same as ~/.bashrc and should include all the ENV, Path, and other exports.


ZSH Auto-suggestions

ZSH Auto-suggestions suggests the full commands while typing based on the previous history. It's useful for quickly typing full complex commands that you used previously, like git, system, and other commonly used commands.

1$ git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions ~/.zsh/zsh-autosuggestions
2

This will clone the repo to the destination folder under the ~/.zsh folder. All ZSH-related plugins and installations can be placed here for future reference and easy management.

Open the ~/.zshrc file and add ZSH Auto-suggestions source for the ZSH to load this plugin while the terminal boots.

~/.zshrc

1# Add ZSH Auto-suggestion plugin
2source ~/.zsh/zsh-autosuggestions/zsh-autosuggestions.zsh
3

Now save the file. To see the changes, either close and open the ZSH terminal or hit source ~/.zshrc.

If you type any command, the terminal will suggest the full command based on the history.

ZSH-Auto Suggestions

For some terminals, the suggested text may not be visible due to different color settings. Set the text color for auto-suggestions text in ~/.zshrc file after the above source as

~/.zshrc

1# Autosuggestion highlight color
2ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE="fg=100"
3

In the above setting, value fg=100 is for setting the foreground color to value 100. ZSH color codes range from 0 to 255, and each value denotes a different color. To compare and set which color looks good for your terminal color settings, use this command that lists all colors in your terminal and how they look.

1$ for i in {0..255}; do print -Pn "%K{$i}  %k%F{$i}${(l:3::0:)i}%f " ${${(M)$((i%6)):#3}:+$'\n'}; done
2

ZSH Auto-completion or Sub-string search

Let's say you have a ton of commands and want to search through history for all matched commands given the current command. Like when you type a command like apt install and hit all the matches starting with apt install.

Type any command, navigate through all matched commands with UP/DOWN arrow with ZSH Sub-string Search.

ZSH Sub-string search or Auto-completion

In the above example, I can search all the previous commands that I used that start with sudo apt.

Install the source and add it to ~/.zshrc

1$ git clone --depth=1 https://github.com/zsh-users/zsh-history-substring-search ~/.zsh/zsh-substring-search
2

~/.zshrc

1# ZSH Sub-string search
2source ~/.zsh/zsh-history-substring-search/zsh-history-substring-search.zsh
3

You may not observe the substring search yet with UP/DOWN arrows, because we need to bind UP/DOWN arrows as keyboard shortcuts for up/down search and forward/backward movement. Refer this auto-completion section to know which character combination to use as shortcuts for your system.

These are the bind keys for my system that I should use for searching and left/right cursor movements.

~/.zshrc

1# autocompletion using arrow keys (based on history)
2bindkey "$terminfo[kcuu1]" history-substring-search-up
3bindkey "$terminfo[kcud1]" history-substring-search-down
4bindkey "$terminfo[kLFT5]" backward-word
5bindkey "$terminfo[kRIT5]" forward-word
6

These settings should be placed after the source listing of the substring search plugin in ~/.zshrc.


ZSH Syntax-highlight

Highlight the command syntax with different colors. This helps in identifying unknown commands, options, sub-commands...

Install ZSH Syntax highlight and add it to ~/.zshrc

1$ git clone --depth=1 https://github.com/zsh-users/zsh-syntax-highlighting ~/.zsh/zsh-syntax-highlighting
2

~/.zshrc

1# ZSH Syntax-highlighting
2source ~/.zsh/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
3

Powerlevel10k (p10k)

Powerlevel10k(p10k) is a ZSH prompt plugin that changes how the terminal looks. It gives flexibility to change color theme, prompt style, and also adds git support to the terminal like showing current branch, status info and others.

Install the plugin and add it to ~/.zshrc

1$ git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ~/.zsh/powerlevel10k
2

~/.zshrc

1source ~/.zsh/powerlevel10k/powerlevel10k.zsh-theme
2

Once you load the ZSH terminal for the first time, p10k prompts you for configuration for terminal look, prompt style, icon support, and others. Set the style of your choice. After you set the config, p10k edits the ~/.zshrc for new settings.

You can edit the p10k manually by modifying the ~/.p10k.zsh file.

I have selected pure prompt style, and this is how it will look like at first.

If you set any other style, ~/.p10k.zsh file will look different.

Add Git Plugin

One of the best features of p10k is its git support. In the prompt, we can see the git info like current branch, files modified, commits ahead/behind, stash, etc. And, we can change the look and feel of these icons also, like below

p10k git plugin

Pure style doesn't come with a proper git status prompt. We can take the git function from classic config and add this to the pure p10k config.

1  function my_git_formatter() {
2    emulate -L zsh
3
4    if [[ -n $P9K_CONTENT ]]; then
5      # If P9K_CONTENT is not empty, use it. It's either "loading" or from vcs_info (not from
6      # gitstatus plugin). VCS_STATUS_* parameters are not available in this case.
7      typeset -g my_git_format=$P9K_CONTENT
8      return
9    fi
10
11    if (( $1 )); then
12      # Styling for up-to-date Git status.
13      local       meta='%248F'
14      local      clean='%253F' 
15      local   modified='%50F'
16      local  untracked='%226F'
17      local conflicted='%196F'
18      local commits_ahead='%4F'
19      local commits_behind='%4F'
20      local stash='%125F'
21    else
22      # Styling for incomplete and stale Git status.
23      local       meta='%244F'
24      local      clean='%244F'
25      local   modified='%244F'
26      local  untracked='%244F'
27      local conflicted='%244F'  
28      local commits_ahead='%244F'
29      local commits_behind='%244F'
30      local stash='%244F'
31    fi
32
33    local res
34
35    if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then
36      local branch=${(V)VCS_STATUS_LOCAL_BRANCH}
37      # If local branch name is at most 32 characters long, show it in full.
38      # Otherwise show the first 12 … the last 12.
39      # Tip: To always show local branch name in full without truncation, delete the next line.
40      (( $#branch > 32 )) && branch[13,-13]="…"  # <-- this line
41      res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}${branch//\%/%%}"
42    fi
43
44    if [[ -n $VCS_STATUS_TAG
45          # Show tag only if not on a branch.
46          # Tip: To always show tag, delete the next line.
47          && -z $VCS_STATUS_LOCAL_BRANCH  # <-- this line
48        ]]; then
49      local tag=${(V)VCS_STATUS_TAG}
50      # If tag name is at most 32 characters long, show it in full.
51      # Otherwise show the first 12 … the last 12.
52      # Tip: To always show tag name in full without truncation, delete the next line.
53      (( $#tag > 32 )) && tag[13,-13]="…"  # <-- this line
54      res+="${meta}#${clean}${tag//\%/%%}"
55    fi
56
57    # Display the current Git commit if there is no branch and no tag.
58    # Tip: To always display the current Git commit, delete the next line.
59    [[ -z $VCS_STATUS_LOCAL_BRANCH && -z $VCS_STATUS_TAG ]] &&  # <-- this line
60      res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}"
61
62    # Show tracking branch name if it differs from local branch.
63    if [[ -n ${VCS_STATUS_REMOTE_BRANCH:#$VCS_STATUS_LOCAL_BRANCH} ]]; then
64      res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}"
65    fi
66
67    # Display "wip" if the latest commit's summary contains "wip" or "WIP".
68    if [[ $VCS_STATUS_COMMIT_SUMMARY == (|*[^[:alnum:]])(wip|WIP)(|[^[:alnum:]]*) ]]; then
69      res+=" ${modified}wip"
70    fi
71
72    if (( VCS_STATUS_COMMITS_AHEAD || VCS_STATUS_COMMITS_BEHIND )); then
73      # ⇣42 if behind the remote.
74      (( VCS_STATUS_COMMITS_BEHIND )) && res+=" ${commits_behind}⇣${VCS_STATUS_COMMITS_BEHIND}"
75      # ⇡42 if ahead of the remote; no leading space if also behind the remote: ⇣42⇡42.
76      (( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && res+=" "
77      (( VCS_STATUS_COMMITS_AHEAD  )) && res+="${commits_ahead}⇡${VCS_STATUS_COMMITS_AHEAD}"
78    elif [[ -n $VCS_STATUS_REMOTE_BRANCH ]]; then
79      # Tip: Uncomment the next line to display '=' if up to date with the remote.
80      # res+=" ${clean}="
81    fi
82
83    # ⇠42 if behind the push remote.
84    (( VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" ${commits_behind}⇠${VCS_STATUS_PUSH_COMMITS_BEHIND}"
85    (( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" "
86    # ⇢42 if ahead of the push remote; no leading space if also behind: ⇠42⇢42.
87    (( VCS_STATUS_PUSH_COMMITS_AHEAD  )) && res+="${commits_ahead}⇢${VCS_STATUS_PUSH_COMMITS_AHEAD}"
88    # *42 if have stashes.
89    (( VCS_STATUS_STASHES        )) && res+=" ${stash}*${VCS_STATUS_STASHES}"
90    # 'merge' if the repo is in an unusual state.
91    [[ -n $VCS_STATUS_ACTION     ]] && res+=" ${conflicted}${VCS_STATUS_ACTION}"
92    # ~42 if have merge conflicts.
93    (( VCS_STATUS_NUM_CONFLICTED )) && res+=" ${conflicted}~${VCS_STATUS_NUM_CONFLICTED}"
94    # +42 if have staged changes.
95    (( VCS_STATUS_NUM_STAGED     )) && res+=" ${modified}+${VCS_STATUS_NUM_STAGED}"
96    # !42 if have unstaged changes.
97    (( VCS_STATUS_NUM_UNSTAGED   )) && res+=" ${modified}!${VCS_STATUS_NUM_UNSTAGED}"
98    # ?42 if have untracked files. It's really a question mark, your font isn't broken.
99    # See POWERLEVEL9K_VCS_UNTRACKED_ICON above if you want to use a different icon.
100    # Remove the next line if you don't want to see untracked files at all.
101    (( VCS_STATUS_NUM_UNTRACKED  )) && res+=" ${untracked}${(g::)POWERLEVEL9K_VCS_UNTRACKED_ICON}${VCS_STATUS_NUM_UNTRACKED}"
102    # "─" if the number of unstaged files is unknown. This can happen due to
103    # POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY (see below) being set to a non-negative number lower
104    # than the number of files in the Git index, or due to bash.showDirtyState being set to false
105    # in the repository config. The number of staged and untracked files may also be unknown
106    # in this case.
107    (( VCS_STATUS_HAS_UNSTAGED == -1 )) && res+=" ${modified}─"
108
109    typeset -g my_git_format=$res
110  }
111  functions -M my_git_formatter 2>/dev/null
112
113  # Don't count the number of unstaged, untracked and conflicted files in Git repositories with
114  # more than this many files in the index. Negative value means infinity.
115  #
116  # If you are working in Git repositories with tens of millions of files and seeing performance
117  # sagging, try setting POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY to a number lower than the output
118  # of `git ls-files | wc -l`. Alternatively, add `bash.showDirtyState = false` to the repository's
119  # config: `git config bash.showDirtyState false`.
120  typeset -g POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY=-1
121
122  # Don't show Git status in prompt for repositories whose workdir matches this pattern.
123  # For example, if set to '~', the Git repository at $HOME/.git will be ignored.
124  # Multiple patterns can be combined with '|': '~(|/foo)|/bar/baz/*'.
125  typeset -g POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN='~'
126
127  # Disable the default Git status formatting.
128  typeset -g POWERLEVEL9K_VCS_DISABLE_GITSTATUS_FORMATTING=true
129  # Install our own Git status formatter.
130  typeset -g POWERLEVEL9K_VCS_CONTENT_EXPANSION='${$((my_git_formatter(1)))+${my_git_format}}'
131  typeset -g POWERLEVEL9K_VCS_LOADING_CONTENT_EXPANSION='${$((my_git_formatter(0)))+${my_git_format}}'
132  # Enable counters for staged, unstaged, etc.
133  typeset -g POWERLEVEL9K_VCS_{STAGED,UNSTAGED,UNTRACKED,CONFLICTED,COMMITS_AHEAD,COMMITS_BEHIND}_MAX_NUM=-1
134
135  # Untracked Icon
136  typeset -g POWERLEVEL9K_VCS_UNTRACKED_ICON='?'
137
138  # Icon color.
139  typeset -g POWERLEVEL9K_VCS_VISUAL_IDENTIFIER_COLOR=76
140  typeset -g POWERLEVEL9K_VCS_LOADING_VISUAL_IDENTIFIER_COLOR=244
141

I have edited colors and changed config little. Add this block to you local ~/.p10k.zsh file and check the git prompt style.


After configuring the plugins and zshrc, these are the final config files


ZSH supports tons of plugins that help users with day-to-day tasks. Instead of manually adding those plugins, use oh-my-zsh for ZSH that natively comes with more themes, styles, and other configs.