Vim Tips Wiki
Tip 1518 Printable Monobook Previous Next

created 2007 · complexity basic · author Fritzophrenic · version 7.0

Many advanced text editors allow the editing of files in a "hex mode," especially useful for editing binary files. Vim provides this capability through the external program xxd, which is included by default in standard distributions of Vim. Unfortunately, using an external program for this task is non-intuitive and error-prone. A user must remember how to run the filter (:%!xxd to convert to hex, :%!xxd -r to convert back), must remember to convert back from hex before saving, cannot convert read-only files without warnings, and more. This tip improves the use of xxd to edit hex files by adding a mapping and command to easily toggle back and forth between hex and non-hex mode, and automating tasks like converting back from the hex-filtered form before saving a file.

Easily enter and leave hex mode[]

Placing the following code in your vimrc will provide a :Hexmode ex command to toggle xxd hex mode on or off. It will keep track of what mode you are in, so you don't have to. This ex command is easy to map like this:

nnoremap <C-H> :Hexmode<CR>
inoremap <C-H> <Esc>:Hexmode<CR>
vnoremap <C-H> :<C-U>Hexmode<CR>

Note that <C-H> is already a defined command, so override it with care (or use a different left hand side to the mapping). See :help CTRL-H, :help i_CTRL-H, :help v_CTRL-H. Also, you may need to use gvim to distinguish between <C-H> and <BS>.

" ex command for toggling hex mode - define mapping if desired
command -bar Hexmode call ToggleHex()

" helper function to toggle hex mode
function ToggleHex()
  " hex mode should be considered a read-only operation
  " save values for modified and read-only for restoration later,
  " and clear the read-only flag for now
  let l:modified=&mod
  let l:oldreadonly=&readonly
  let &readonly=0
  let l:oldmodifiable=&modifiable
  let &modifiable=1
  if !exists("b:editHex") || !b:editHex
    " save old options
    let b:oldft=&ft
    let b:oldbin=&bin
    " set new options
    setlocal binary " make sure it overrides any textwidth, etc.
    silent :e " this will reload the file without trickeries 
              "(DOS line endings will be shown entirely )
    let &ft="xxd"
    " set status
    let b:editHex=1
    " switch to hex editor
    " restore old options
    let &ft=b:oldft
    if !b:oldbin
      setlocal nobinary
    " set status
    let b:editHex=0
    " return to normal editing
    %!xxd -r
  " restore values for modified and read only state
  let &mod=l:modified
  let &readonly=l:oldreadonly
  let &modifiable=l:oldmodifiable

Automatically handle hex mode[]

It would be nice if the user did not need to remember to convert back from hex before saving changes to a binary file. Also, certain files are almost always binary; it would be nice if Vim would pick up on this and automatically enter an appropriate editing mode. The help files for Vim include the following advice for automating the xxd-style hex editing capabilities for Vim, in order to automate the conversion and automatically enter hex mode for binary files:

" vim -b : edit binary using xxd-format!
augroup Binary
  au BufReadPre  *.bin let &bin=1
  au BufReadPost *.bin if &bin | %!xxd
  au BufReadPost *.bin set ft=xxd | endif
  au BufWritePre *.bin if &bin | %!xxd -r
  au BufWritePre *.bin endif
  au BufWritePost *.bin if &bin | %!xxd
  au BufWritePost *.bin set nomod | endif
augroup END

There are a few problems with this approach:

  1. Only files with a .bin extension are opened this way, even if editing a file in binary mode (e.g. with ++bin)
  2. This sets the binary option for any future documents opened as well. If you use tabe, for example, or if you have Vim set up to open new files in tabs, then any new files opened will open in binary mode.
  3. If you decide to edit a binary file without the xxd, the BufWrite autocommands will try to convert it with xxd -r anyway.
  4. Viewing a file in "xxd mode" requires you to modify the file, so read-only files will warn you if you try to do so, even if you make no changes. Buffers with 'nomodifiable' will cause errors.
  5. If you go back into non-xxd editing, the filetype will still be xxd.

These problems can be fixed as follows:

  1. Trigger on all files if they are being edited with the binary option. Modify the extension-specific autocmd to set the binary option.
  2. Use setlocal rather than using the global binary option.
  3. Maintain a variable for each buffer that tracks whether the file is in "xxd mode". Only restore to non-xxd before writing if this variable says we are in the appropriate mode.
  4. Temporarily clear the read-only flag whenever you transition between xxd and non-xxd mode, resetting it afterward.
  5. Store the filetype when entering xxd mode and restore it when leaving.

Below is some code for your vimrc that does all of this, making use of the command we define above:

" autocmds to automatically enter hex mode and handle file writes properly
if has("autocmd")
  " vim -b : edit binary using xxd-format!
  augroup Binary

    " set binary option for all binary files before reading them
    au BufReadPre *.bin,*.hex setlocal binary

    " if on a fresh read the buffer variable is already set, it's wrong
    au BufReadPost *
          \ if exists('b:editHex') && b:editHex |
          \   let b:editHex = 0 |
          \ endif

    " convert to hex on startup for binary files automatically
    au BufReadPost *
          \ if &binary | Hexmode | endif

    " When the text is freed, the next time the buffer is made active it will
    " re-read the text and thus not match the correct mode, we will need to
    " convert it again if the buffer is again loaded.
    au BufUnload *
          \ if getbufvar(expand("<afile>"), 'editHex') == 1 |
          \   call setbufvar(expand("<afile>"), 'editHex', 0) |
          \ endif

    " before writing a file when editing in hex mode, convert back to non-hex
    au BufWritePre *
          \ if exists("b:editHex") && b:editHex && &binary |
          \  let oldro=&ro | let &ro=0 |
          \  let oldma=&ma | let &ma=1 |
          \  silent exe "%!xxd -r" |
          \  let &ma=oldma | let &ro=oldro |
          \  unlet oldma | unlet oldro |
          \ endif

    " after writing a binary file, if we're in hex mode, restore hex mode
    au BufWritePost *
          \ if exists("b:editHex") && b:editHex && &binary |
          \  let oldro=&ro | let &ro=0 |
          \  let oldma=&ma | let &ma=1 |
          \  silent exe "%!xxd" |
          \  exe "set nomod" |
          \  let &ma=oldma | let &ro=oldro |
          \  unlet oldma | unlet oldro |
          \ endif
  augroup END

This will make Vim automatically invoke hex mode using the command defined earlier whenever a file is opened in binary mode, and will automatically (locally) set binary mode for .bin and .hex files (so .bin and .hex files will automatically open in hex mode always).

Other enhancements[]

You may need to take steps to avoid the "Hit ENTER to continue prompt if you use this tip.

Here are commands to use to launch any file(s) in hex mode in a separate "hex editor" Vim server. Under Windows, you can easily add these commands to your "Send To" menu if you use the full path for gvim.exe in place of "vim". Remove the "-p" and "--remote-tab-silent" option if you do not want the tabbed interface. The -c "set binary" ensures that any tabwidth, wrap, etc. settings are overwritten after the file finishes loading.

  • Launch in tabs in a new dedicated Vim hex editor: vim -p -b -c "set binary" --servername HEXVIM <files>
  • Launch in tabs in an existing dedicated Vim hex editor: vim -b -c "set binary" --servername HEXVIM --remote-tab-silent <files>



script#666 provides similar (and extended) capability, though I have never used it and can't vouch for its usefulness/completeness/correctness.

For me, the code to automatically enter hex mode causes :help commands to fail for any gzipped help files, so I fixed it by adding this chunk of code

" Exclude vim files from auto hexmode
function IsVimFile()
  let b:path = expand("%:p:h")

  " Loop through each directory in the runtime path
  for i in split(&rtp, ",")
    " See if this file resides somewhere in the runtime path
    if match(b:path, i) != -1 | return 1 | endif

  " No match
  return 0

and modifying

    " convert to hex on startup for binary files automatically
    au BufReadPost *
          \ if &binary | Hexmode | endif

to read

    " convert to hex on startup for binary files automatically
    au BufReadPost *
          \ if &binary && !IsVimFile() | Hexmode | endif

I have made a git repository at GitHub of this code and would welcome other updates. Thanks Fritzophrenic for making this plugin! -- Tyler Akins