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 %!xxd else " restore old options let &ft=b:oldft if !b:oldbin setlocal nobinary endif " set status let b:editHex=0 " return to normal editing %!xxd -r endif " restore values for modified and read only state let &mod=l:modified let &readonly=l:oldreadonly let &modifiable=l:oldmodifiable endfunction
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! 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:
- Only files with a .bin extension are opened this way, even if editing a file in binary mode (e.g. with ++bin)
- 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.
- If you decide to edit a binary file without the xxd, the BufWrite autocommands will try to convert it with xxd -r anyway.
- 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.
- If you go back into non-xxd editing, the filetype will still be xxd.
These problems can be fixed as follows:
- Trigger on all files if they are being edited with the binary option. Modify the extension-specific autocmd to set the binary option.
- Use setlocal rather than using the global binary option.
- 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.
- Temporarily clear the read-only flag whenever you transition between xxd and non-xxd mode, resetting it afterward.
- 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 au! " 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 endif
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).
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>
- :help hex-editing
- :help :autocmd
- :help internal-variables
- :help edit-binary
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 endfor " No match return 0 endfunction
" convert to hex on startup for binary files automatically au BufReadPost * \ if &binary | Hexmode | endif
" 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