36

Is it possible to...

  • Step up local ZSH shell history (actions only happened in that prompt)

  • Step up global ZSH history (shared history is on) - default what happens when you press UP arrow when shared history is on

... separately.

For example one might bind CTRL+up for global history, normal up for local history. At the same time it makes sense to use the global history for a backwards history search (i.e. CTRL+R).

This might speed up some shell operations, as some operations are specific to that shell window and you want to go back in them.

7 Answers 7

35

Copy & Paste this to your .zshrc:

Cursors are using local history:

bindkey "${key[Up]}" up-line-or-local-history
bindkey "${key[Down]}" down-line-or-local-history

up-line-or-local-history() {
    zle set-local-history 1
    zle up-line-or-history
    zle set-local-history 0
}
zle -N up-line-or-local-history
down-line-or-local-history() {
    zle set-local-history 1
    zle down-line-or-history
    zle set-local-history 0
}
zle -N down-line-or-local-history

If you need also key bindings (CTRL + cursors) to step through the global history add also this to your .zshrc:

bindkey "^[[1;5A" up-line-or-history    # [CTRL] + Cursor up
bindkey "^[[1;5B" down-line-or-history  # [CTRL] + Cursor down

To make this work the option SHARE_HISTORY (see 16.2.4 History) needs to be enabled. Run setopt and check if "sharehistory" is listed. If not add setopt sharehistory to your .zshrc. Then one can use set-local-history as we did above. The documenation says:

By default, history movement commands visit the imported lines as well as the local lines, but you can toggle this on and off with the set-local-history zle binding. It is also possible to create a zle widget that will make some commands ignore imported commands, and some include them.

Note that by default global history is used (and all functions end with "zle set-local-history 0", i.e. local history is disabled). So pressing CTRL + R will search the global history by default (which makes sense in most cases).

This is quite similar to the solution by @mpy, but ready for copy & paste. It overwrites the cursor keys up and down. I used this mail list entry.

See also:

3
  • 2
    On zsh 5.0.2 I get an error that it cannot bind to an empty key sequence. Even if I replace it with the keys from another answer below, both up/down and Ctrl+up/down just go through global history. Anything I'm doing wrong here?
    – rjh
    Feb 22, 2016 at 20:10
  • 2
    @rjh: Try this: In an editor like vim, delete the ${key[Up]}. Then (in edit mode), type Ctrl+V followed by up arrow. This should insert the correct key code. Do the same for down arrow.
    – robinst
    Mar 1, 2016 at 0:19
  • 1
    I used this method and it works but I lost the ability to search history by writing the initial part of a command and then Arrow up. It goes through all commands like Bash, so I have to work around using Ctrl+R.
    – Melebius
    Sep 13, 2017 at 10:26
5

You can setup a special zle widget to show only local history items:

function only-local-history () {
        zle set-local-history 1
        zle up-history
        zle set-local-history 0
}
zle -N only-local-history

Assuming, that is bound to up-line-or-history (I think that is default), you can bind this widget to another key stroke, like CTRL+:

 bindkey "^[Oa" only-local-history

If this works is probably dependent of your terminal. Above line works in URxvt/Screen. With xterm you'll need

 bindkey "^[[1;5A" only-local-history

for CTRL+.

Another variant could be

function peek-history () {
        zle set-local-history
        zle up-history
        zle set-local-history
}
zle -N peek-history

so, if you have local history enabled, you can peek into the global one or vice versa.

5

@lumbic's answer worked for me only with a few changes:

setopt share_history

up-line-or-local-history() {
    zle set-local-history 1
    zle up-line-or-history
    zle set-local-history 0
}
zle -N up-line-or-local-history
down-line-or-local-history() {
    zle set-local-history 1
    zle down-line-or-history
    zle set-local-history 0
}
zle -N down-line-or-local-history

bindkey '^[OA' up-line-or-history     # Cursor up
bindkey '^[OB' down-line-or-history   # Cursor down
bindkey '^[[1;5A' up-line-or-local-history    # [CTRL] + Cursor up
bindkey '^[[1;5B' down-line-or-local-history  # [CTRL] + Cursor down

This code makes the global history the default, and uses CTRL-arrow for local history.

Note: I use zsh 5.0.2 together with oh-my-zsh.

2
  • This looks like the opposite than what OP requested – Ctrl+arrows for local history.
    – Melebius
    Sep 13, 2017 at 10:30
  • @Melebius: the OP simply asked for them to be separated, and then gave an example of possible bindings: "For example one might bind CTRL+up for global history, normal up for local history."
    – iconoclast
    Oct 24, 2023 at 19:29
3

Expanding on @lumbric's answer to preserve beginning-search functionality, which many zsh user may have by default. I tried wrapping up-line-or-beginning-search and down-line-or-beginning-search but this caused issues (see @Melebius's answer on this), so instead I'm redefining them and toggling the local history for only the underlying search functions.

Here is the behavior we see with this enabled:

Step backwards through local history or beginning search

Step forwards through local history or beginning search

Ctrl + Step backwards through global history or beginning search

Ctrl + Step forwards through global history or beginning search

Ctrl + r Search global history

Put the following in $ZSH_CUSTOM/local_history_global_beginning_search.zsh or directly in your .zshrc

up-line-or-beginning-local-search() {
  if [[ $LBUFFER == *$'\n'* ]]; then
    zle up-line-or-history
    __searching=''
  elif [[ -n $PREBUFFER ]] && 
      zstyle -t ':zle:up-line-or-beginning-search' edit-buffer
  then
    zle push-line-or-edit
  else
    [[ $LASTWIDGET = $__searching ]] && CURSOR=$__savecursor
    __savecursor=$CURSOR
    __searching=$WIDGET
    zle set-local-history 1
    zle history-beginning-search-backward
    zle set-local-history 0
    zstyle -T ':zle:up-line-or-beginning-search' leave-cursor &&
        zle end-of-line
  fi
}
zle -N up-line-or-beginning-local-search

down-line-or-beginning-local-search() {
  if [[ ${+NUMERIC} -eq 0 &&
      ( $LASTWIDGET = $__searching || $RBUFFER != *$'\n'* ) ]]
  then
    [[ $LASTWIDGET = $__searching ]] && CURSOR=$__savecursor
    __searching=$WIDGET
    __savecursor=$CURSOR

    zle set-local-history 1
    if zle history-beginning-search-forward; then
      zle set-local-history 0
      [[ $RBUFFER = *$'\n'* ]] || 
        zstyle -T ':zle:down-line-or-beginning-search' leave-cursor &&
        zle end-of-line
      return
    fi
    zle set-local-history 0
    
    [[ $RBUFFER = *$'\n'* ]] || return
  fi
  __searching=''
  zle down-line-or-history
}
zle -N down-line-or-beginning-local-search

# Stepping through local history.
bindkey "${key[Up]}" up-line-or-beginning-local-search       # Cursor Up
bindkey "${key[Down]}" down-line-or-beginning-local-search   # Cursor Down

# Stepping through global history.  
bindkey "^[[1;5A" up-line-or-beginning-search                # Ctrl + Cursor Up
bindkey "^[[1;5B" down-line-or-beginning-search              # Ctrl + Cursor Down

# This option both imports new commands from the history file, and also causes your
# typed commands to be appended to the history file (the latter is like specifying
# INC_APPEND_HISTORY, which should be turned off if this option is in effect).
setopt sharehistory
2

I’ve been trying the options listed in other answers here but I was not happy with them. The widgets1 up-history or up-line-or-history do not include beginning search and the widget1 up-line-or-beginning-search seems to not work well when altering local history preference while using it.2

So I came to the following settings. It enables Ctrl+arrows to browse the global history, Alt+arrows to browse the local history only (similar to Bash) and standalone arrows to use the smart beginning search (default in Oh My Zsh).

up-line-or-local-history() {
    zle set-local-history 1
    zle up-line-or-search
    zle set-local-history 0
}
zle -N up-line-or-local-history

down-line-or-local-history() {
    zle set-local-history 1
    zle down-line-or-search
    zle set-local-history 0
}
zle -N down-line-or-local-history

bindkey "${key[Up]}" up-line-or-beginning-search
bindkey "${key[Down]}" down-line-or-beginning-search
bindkey "^[[1;5A" up-line-or-search    # [CTRL] + Cursor up
bindkey "^[[1;5B" down-line-or-search  # [CTRL] + Cursor down
bindkey "^[[1;3A" up-line-or-local-history    # [ALT] + Cursor up
bindkey "^[[1;3B" down-line-or-local-history  # [ALT] + Cursor up

1 Their down counterparts are meant implicitly, too.

2 The beginning used to search is redefined after altering local history preference, so the whole first found command is used as the beginning to be searched for in the second search.

2

Based on the various answers here, I was able to make a condenced version:

function up-line-or-history() {
    zle set-local-history 1
    zle .up-line-or-history
    zle set-local-history 0
}

function down-line-or-history() {
    zle set-local-history 1
    zle .down-line-or-history
    zle set-local-history 0
}

# Overwrite existing {up,down}-line-or-history widgets with the functions above.
zle -N up-line-or-history
zle -N down-line-or-history

This version will overwrite the existing up-line-or-history and down-line-or-history widgets with new user-defined widgets. This widget enables the local history and will then call the original widgets, which are available under the .-prefixed names. See the zle widget documentation for details.

This means that you get to keep any existing keybindings. You can therefore avoid having to figure out what the escape sequence is for typing an up-arrow in your current terminal.

-1
autoload -U up-line-or-beginning-search
autoload -U down-line-or-beginning-search
zle -N up-line-or-beginning-search
zle -N down-line-or-beginning-search
bindkey "^[[A" up-line-or-beginning-search # Up
bindkey "^[[B" down-line-or-beginning-search # Down

Above answer worked for me.

Reference: https://coderwall.com/p/jpj_6q/zsh-better-history-searching-with-arrow-keys

1
  • Could you explain how this does distinguish local and global history? IMHO your code does not do that at all. Moreover, this seems to be set already in Oh My Zsh.
    – Melebius
    Mar 13, 2018 at 9:33

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .