Showing posts with label shell. Show all posts
Showing posts with label shell. Show all posts

Tuesday, April 01, 2025

Customizing the Bash command prompt for vi editing mode

Customizing readline with inputrc

A brief introduction to the GNU Readline library, customizing user input using the .inputrc file, and the vi editing mode.

(Skip ahead to setting the mode indicator configuration.)

Introduction

I've been using vi/vim as my preferred editor long enough for the basic movement and editing keyboard shortcuts(also know as a key binding) to become second nature to me. Learning that the Bash shell supports multiple editing modes, including vi, gave me the opportunity to use this niche skill with other tools. I appreciate the idea that the key bindings that I had learned through using vi had some usefulness outside of a single application.

Bash supports this functionality through a library called Readline. Switching from Bash's default Emacs editing mode over to vi editing mode made sense, to me. Being able to switch tools and not have to remember a different set of keyboard shortcuts is convenient.

One feature that was missing, however, was a visual indicator of which mode I was currently in. For this, I needed to look at setting a couple of readline-functions that would modify the command line prompt to visually indicate whether one was in either insert or edit mode.

Setting the editing mode

By default, the Bash shell is set to use the readline Emacs editing mode.

To switch your command-line editing mode to vi, enter the following at the console:

set -o vi

If you are unsure which editing mode is currently being used in the shell, enter the command set -o from the command line. This will list the current state of the option-names and which editing mode is currently enabled:

The set command is a bash built-in that "sets or unsets values of shell options and positional parameters". See set --help for more details.

To make this change permanent, settings can be added in one of two files, depending on one's requirements.
For changing only the Bash shell to use the vi-editing mode, add the line set -o vi to the .bashrc.
Enabling the vi-editing mode for the Bash shell and any other tool that uses the Readline library, add the following line to the .inputrc file:

set editing-mode vi

From the readline(3) manpage:

Readline is customized by putting commands in an initialization file (the inputrc file). The name of this file is taken from the value of the INPUTRC environment variable. If that variable is unset, the default is ~/.inputrc. If that file does not exist or cannot be read, the ultimate default is /etc/inputrc.

Adding a Mode Indicator in the Prompt

Do you use set -o vi in your shell and can't remember whether you are in insert or edit/cmd mode? I do.

From the command line, activating a visual indicator at the prompt can be toggled with the following two commands:


bind 'set show-mode-in-prompt on'  
bind 'set show-mode-in-prompt off'  

From the man page:

If set to On, add a string to the beginning of the prompt indicating the editing mode: emacs, vi command, or vi insertion. The mode strings are user-settable (e.g., emacs-mode-string)

By default, this will place a string ((cmd)/(ins)) at the beginning of the prompt.

Moving between the modes can be done by pressing the Esc to enter the command mode and pressing the I key to resturn to insert mode.
This prompt can be customized with the vi-ins-mode-string and vi-cmd-mode-string variables.

If set from the command line, these indicators will persist only for the duration of that shell's existence.
To make the change permanent, add the following line to your .inputrc to place a visual indicator at the beginning of your prompt: set show-mode-in-prompt on

Personally, I prefer my command prompt slightly less cluttered. This is what my .inputrc file looks like for the mode indicators:


# Add mode indicators to command line
set show-mode-in-prompt on  
set vi-ins-mode-string "+"  
set vi-cmd-mode-string ":"  

This is my prompt with the customized mode indicators:

Different cursor shapes can also be configured for each mode. See the Arch Linux Readline wiki page for examples and a few more tips on modifying readline using the inputrc file.

vi editing mode

When opening a command line, the user will be in insert mode where text can be entered normally. When Esc or Ctrl+[ is pressed, an emulation of vi's cmd or normal mode will be entered and vi's keybindings can be used to move around and edit the command line. To return to insert mode, press the I key and text can be entered normally. Below is a list of some of the common key combinations:

  • 0 — Move to start of line
  • $ — Move to end of line
  • b — Move back a word
  • w — Move forward a word
  • e — Move to the end of the next word
  • dw — Delete a word
  • d$ — Delete to end of line
  • dd — Delete entire line
For a more complete list of commands, take a look at the Bash vi editing mode cheat sheet.

Thursday, March 20, 2025

Integrating fzf with the bash shell

command-line fuzzy finder(fzf) Shell Integration

Introduction

fzf is a highly customizable command-line fuzzy finder. Per the project's README:

It's an interactive filter program for any kind of list; files, command history, processes, hostnames, bookmarks, git commits, etc. It implements a "fuzzy" matching algorithm, so you can quickly type in patterns with omitted characters and still get the results you want.

Reviewing my notes, I realize that I'm barely scratching the surface of how fzf can be used and customized. For now, I'm going to outline how I've set up fzf for daily usage in the bash shell.

  • Installation and upgrading fzf
  • What key combinations can be used to fuzzy search through files and directories.
  • Customizing the finder display

Installation

This article assumes an fzf installation directly from GitHub and using the bash shell. For other installation methods and shell-dependent commands, refer to the fzf installation documentation.

  1. git clone --depth 1 https://github.com/junegunn/fzf.git ~/.config/fzf ~/.config/fzf/install

    Note that this is slightly different than the recommended install command. When I am able, I try to move installations off of the root of my home directory and, preferably, place them in the ~/.config/ directory.

  2. The installation script will prompt for three configuration options:

    • Do you want to enable fuzzy auto-completion? ([y]/n)
    • Do you want to enable key bindings? ([y]/n)
    • Do you want to update your shell configuration files? ([y]/n)

    Unless there is a compelling reason to do otherwise, accept the default choices.

  3. Here, the installation places the file .fzf.bash, which sets up the key bindings and completions, in the root of the home directory and modifies the .bashrc to source this file on startup. Again, I make some modifications to place this file in the ~/.config/fzf/ directory:
    • mv ~/.fzf.bash ~/.config/fzf/fzf.bash
    • Changes to fzf.bash:
      # Setup fzf
      if [[ ! "$PATH" == */home/kbowen/.config/fzf/bin* ]]; then
      PATH="${PATH:+${PATH}:}/home/kbowen/.config/fzf/bin"
      fi
      # Setup key bindings and shell completion
      eval "$(fzf --bash)"
    • Add the following line to .bashrc:
      # Set up fzf key bindings and fuzzy completion
      [ -f ~/.config/fzf/fzf.bash ] && source "$HOME/.config/fzf/fzf.bash"
  4. For reference, the keybindings and completion in fzf are provided by two files. In the installation directly from git, these files are located here:

      ~/.config/fzf/shell/key-bindings.bash
      ~/.config/fzf/shell/completion.bash
    

    Modifying the default installation is not required, just a personal preference of mine.

Upgrading fzf

fzf is actively developed and updated relatively frequently. Check for updates regularly with the command:

  • cd ~/.config/fzf && git pull && ./install
    For convenience, I've created an alias for this in my .bash_aliases file:
  • alias fzf_update='cd ~/.fzf && git pull && ./install && cd -'

Keybindings with fzf

Key Combination Description
CTRL-t Search all files and subdirectories of the working directory, and output the selection to STDOUT.
ALT-c Search all subdirectories of the working directory, and run the command cd with the output as argument.
CTRL-r Search your shell history, and output the selection to STDOUT.

CTRL-r is my most frequently used fzf command. Generally, when I want to run the previous command or the one before that, I'll use !! or !-2. After that, I don't keep the commands in my memory and using CTRL-r has become a great time-saver, for me.

Using CTRL-t is useful if you want to locate and run a command on a file. For example, to edit a file, type vim at the command line, then press the key combination <Ctrl-t> to perform a search. Finally, press <Enter> twice to open the file inside vim.

ALT-c is used for searching for and moving into a subdirectory.

Using the finder

Using any of these three key combinations will bring up a finder that will display a list(either of files, commands, or directories) and filter the results depending on the text entered.

Movement within the finder display

  • CTRL-K / CTRL-J (or CTRL-P / CTRL-N) to move cursor up and down
  • Enter key to select the item, CTRL-C / CTRL-G / ESC to exit
  • On multi-select mode (-m), TAB and Shift-TAB to mark multiple items
  • Emacs style key bindings
  • Mouse: scroll, click, double-click; shift-click and shift-scroll on multi-select mode

Customizing the finder display

By default, the finder will display a window without a file preview with the filter command line at the bottom of the display. With a little bit of tinkering, I've build a display with a preview window to show file contents as well as look aesthetically pleasing to me. For this, I needed to modify the FZF_DEFAULT_OPTS environment variable and add it to the .bashrc file:

# Set the fzf display layout
export FZF_DEFAULT_OPTS=" \
--multi \
--height=50% \
--margin=5%,2%,2%,5% \
--reverse \
--preview='(highlight -O ansi {} || bat {}) 2> /dev/null | head -n 50' \
--preview-window=45%,border-double --border=double \
--prompt='▶' \
--pointer='→' \
--marker='✗' \
--header='CTRL-c or ESC to quit' \
--color='dark,fg:magenta,border:cyan'"

See the fzf README for details on customizing the look of the display filter.

Thursday, February 27, 2025

Adding color to man pages

How to colorize man pages in your terminal

Adding color highlighting to the man pages is similar to code syntax highlighting. I find adding color to the pages helps differentiate variables, strings, and reserved words and makes it easier to visually parse the information presented. I accomplish this by passing ANSI color code parameters to the less pager used in display the man pages.

Configuration Steps

  1. Open your .bashrc file in an editor and add the following lines: 
  2.  
    LESS_TERMCAP_mb=$(printf "\e[01;31m") # enter blinking mode(bold red)  
    export LESS_TERMCAP_mb  
    LESS_TERMCAP_md=$(printf '\e[01;38;5;75m') # enter double-bright mode  
    export LESS_TERMCAP_md  
    LESS_TERMCAP_me=$(printf "\e[0m") # turn off all appearance modes  
    export LESS_TERMCAP_me  
    LESS_TERMCAP_se=$(printf "\e[0m") # leave standout mode  
    export LESS_TERMCAP_se  
    LESS_TERMCAP_so=$(printf "\e[01;33m") # enter standout mode(bold yellow)  
    export LESS_TERMCAP_so  
    LESS_TERMCAP_ue=$(printf "\e[0m") # leave underline mode  
    export LESS_TERMCAP_ue  
    LESS_TERMCAP_us=$(printf '\e[04;38;5;200m') # enter underline mode (magenta)  
    export LESS_TERMCAP_us  
      
    # Turn off Select Graphic Rendition(SGR)  
    # export GROFF_NO_SGR=1  
    # Start with color output disabled (avoid unexpected color code output)  
    export MANROFFOPT="-c"  
      
    # Set options for how less will be used as a pager. 
    # The MANPAGER environment variable will override 
    # settings used by the more general PAGER settings.  
    # In this case, I want to show percentage of page 
    # completion for man pages  
    export MANPAGER='/usr/bin/less -s -M +Gg'  
    export PAGER='/usr/bin/less -s -M'
    
      

  3. Source the .bashrc file (source .bashrc) to read and execute the newly added commands into the current shell.

Notes on groff rendering

Two lines of note in the above configuration: export GROFF_NO_SGR=1 and export MANROFFOPT="-c". One of these is needed in the configuration in order for the man page colorization to work. As I learned this week, there is an issue with newer versions of groff(i.e. newer than 2023)

See the following for details:
Groff + most change in behaviour
Displaying a man page on a terminal with/without my favorite pager shows garbage

Alternate Color Theme

Here is a slightly different color theme:

 
export LESS_TERMCAP_mb=$(printf '\e[01;31m') # (red)
export LESS_TERMCAP_md=$(printf '\e[01;35m') # (bold, magenta)  
export LESS_TERMCAP_me=$(printf '\e[0m')  
export LESS_TERMCAP_se=$(printf '\e[0m') 
export LESS_TERMCAP_so=$(printf '\e[01;33m') # (yellow)  
export LESS_TERMCAP_ue=$(printf '\e[0m') # 
export LESS_TERMCAP_us=$(printf '\e[04;36m') # (cyan)  

Generally, I have come to prefer separating out the color variable definitions from the statements exporting the variable as shown in the first configuration example. This alternate theme example will also work; but, shellcheck gives me an error: SC2155: Declare and assign separately to avoid masking return values. I try to pay attention to tools that I use when they make recommendations that could improve my code and configurations.

See Shellcheck error SC2155 for additional details.

How it all works

To quote liberally from Russell Parker’s post Adding Colors to man

To understand how the above environment variables work it helps to review what steps normally happen when viewing a manpage:

  1. man renders a page from a (likely compressed) nroff or troff/groff document and pipes the result it into the pager program, usually less
  2. If the piped text indicates formatting that needs to be performed then less has to figure out how to accomplish this for terminal output
  3. less uses the (deprecated) termcap database to look up how to achieve effects like underline and bold. In reality it ends up using termcap’s successor, the terminfo database, which maintains support for the termcap interface. This gives back an escape string which corresponds to the specified effect for your particular terminal
  4. Using these nifty escape strings, less finally displays the manpage to the user

Manpages use formatting like bold (for sections) and underlines (for things like file names and arguments). These should already work out of the box when using the man command but will not change the text color.

ANSI Color Codes

To see the ANSI color codes and how they would look on your specific terminal, here is a bash script to print them out.

 
#!/bin/bash  
# name: ansi_codes.sh (see References for link to source)  
for x in {0..5};  
  do echo === && for z in 0 10 60 70;  
  do for y in {30..37};  
  do y=$((y + z)) && printf '\e[%d;%dm%-12s\e[0m' "$x" \
  "$y" "$(printf ' \\e[%d;%dm] ' "$x" "$y")" && printf ' '; 
  done && printf '\n';  
done;  
done

The References section below contains links to several color charts.

References