Emacs: Vimlike Tab/Window Navigation

If you use Vim, you know about tabs and split windows. I’ve managed to set up Emacs to behave in almost the same way (the only difference is that there can be only 9 “tabs” in Emacs (a limitation of elscreen), unlike in Vim where the number of tabs is arbitrary).

Emacs already has a very capable window-splitting mechanism, so you can emulate Vim’s split window functionality with vanilla Emacs. For Vim’s tabs, however, You need the elscreen package. This page has a version of elscreen that works with the latest Emacs 24.1 release.

The biggest problem with navigating around Emacs’s elscreen + split windows was the problem of deleting windows. In Vim, if you use “:q”, the current window gets killed. If it’s the only window in the tab, the tab gets killed. Lastly, if there are no tabs and only 1 window, “:q” will result in exiting Vim.

The function below was hacked together (I do not know any elisp) by reading parts of the GNU Emacs manual and googling around. It emulates the same functionality as “:q” in Vim as far as split windows/elscreen is concerned:

; Either close the current elscreen, or if only one screen, use the ":q" Evil
; command; this simulates the ":q" behavior of Vim when used with tabs.
(defun vimlike-quit ()
  "Vimlike ':q' behavior: close current window if there are split windows;
otherwise, close current tab (elscreen)."
  (let ((one-elscreen (elscreen-one-screen-p))
        (one-window (one-window-p))
     ; if current tab has split windows in it, close the current live window
     ((not one-window)
      (delete-window) ; delete the current window
      (balance-windows) ; balance remaining windows
     ; if there are multiple elscreens (tabs), close the current elscreen
     ((not one-elscreen)
     ; if there is only one elscreen, just try to quit (calling elscreen-kill
     ; will not work, because elscreen-kill fails if there is only one
     ; elscreen)

The function below is used to emulate Vim’s “:tabe” behavior, which is what I use all the time for creating a new “scratch” or temporary buffer for pasting random junk into.

; A function that behaves like Vim's ':tabe' commnad for creating a new tab and
; buffer (the name "[No Name]" is also taken from Vim).
(defun vimlike-:tabe ()
  "Vimlike ':tabe' behavior for creating a new tab and buffer."
  (let ((buffer (generate-new-buffer "[No Name]")))
      ; create new tab
      ; set window's buffer to the newly-created buffer
      (set-window-buffer (selected-window) buffer)
      ; set state to normal state
      (with-current-buffer buffer

Below are some code snippets to show you how I set up my .emacs to mimic my .vimrc, as far as tabs/split windows go:

Tab movement:

nnoremap <C-l> :tabnext<cr>
nnoremap <C-h> :tabprevious<cr>
inoremap <C-l> <esc>:tabnext<cr>
inoremap <C-h> <esc>:tabprevious<cr>
(define-key evil-normal-state-map (kbd "C-l") 'elscreen-next)
(define-key evil-normal-state-map (kbd "C-h") 'elscreen-previous)
(define-key evil-insert-state-map (kbd "C-l") 'elscreen-next)
(define-key evil-insert-state-map (kbd "C-h") 'elscreen-previous)

Window movement:

nmap <Tab> <C-w><C-w>
(define-key evil-normal-state-map [tab] 'other-window)

Buffer movement (change a window’s contents):

nmap <S-h> :bn<cr>
nmap <S-l> :bp<cr>
(define-key evil-normal-state-map "H" 'evil-next-buffer)
(define-key evil-normal-state-map "L" 'evil-prev-buffer)

Create a new blank tab (for writing a new file):

nmap <localleader>n :tabe<cr>
(define-key evil-normal-state-map ",n" 'vimlike-:tabe)

Horizontally/Vertically split current window (I sort of have the names backwards, but I prefer it this way because I tend to split windows so that they are top and bottom, and “,h” (the mapping I use to achieve this effect) is much faster than “,v”)

nmap <localleader>h :sp<cr>
nmap <localleader>v :vsp<cr>
(define-key evil-normal-state-map ",h" (lambda () (interactive) (split-window-vertically) (balance-windows)))
(define-key evil-normal-state-map ",v" (lambda () (interactive) (split-window-horizontally) (balance-windows)))

I use <localleader> in my .vimrc to avoid conflicts with any external plugins:

let maplocalleader = ","

Since the comma key is already mapped in vanilla Vim to act as “go to next character that was searched with the ‘f’, ‘F’, ‘t’, or ‘T’ key”, I salvage this lost functionality using the ‘K’ key for it. ‘K’ in vanilla Vim is also already mapped to an existing function, but it’s a useless one to people who live in terminals like myself (vanilla ‘K’ in normal mode simply looks up the word under the cursor in the manpages… pretty useless IMHO). The setup below achieves the aforementioned goals:

" Change K from being mapped to interactive man pages to being used as the
" vanilla comma ',' key's functionality (intra-line backwards search repeat for
" any t, T, f, F searches).
nnoremap K ,
vnoremap K ,

In case you are curious, the ‘;’ key is the forwards-search counterpart to the vanilla ‘,’ key.

Here is the .emacs equivalent to the above code snippet:

; Change K from being mapped to interactive man pages to being used as the
; vanilla comma ',' key's functionality (intra-line backwards search repeat for
; any t, T, f, F searches).
(define-key evil-normal-state-map "K" 'evil-repeat-find-char-reverse)

And finally, I use Emacs with the excellent Evil plugin, which makes Emacs much more usable/sane for Vimmers like me. This is why all of the Emacs configuration code snippets in this post have “evil” in there (no pun intended ;)).

I encourage you to try out my keymappings — I find them very usable… I am especially proud of mapping the TAB key to moving between windows inside a tab; it makes jumping around windows so much faster.