Pentadacytl (or Vimperator) Form Field Editing

2010-03-16 20:05:32 -0700

I have been using Firefox as my primary browser for quite a while now. What held me back from switching initially were two things: 1) unacceptably poor non-native widgets, which have since improved dramatically and 2) no support for the Mac OS X Keychain, which is now available via the Keychain Services Integration extension. But eventually my desire for vim keybindings everywhere pushed me to switch despite these shortcomings. The vimperator pentadactyl Firefox extension allows for entirely keyboard-based browsing, using bindings similar to vim. And even better, it facilitates form field editing using vim via external editor support. So when I want to edit a form field, I type either "gi" (to enter the field) or "f#" (where # is the QuickHint mode number associated with the field; useful if there are multiple fields on the page) then hit Control-i. This creates a new tab with a buffer for that field in an existing instance of MacVim which is always running in the same Space as my browser, so it comes up almost instantaneously. I edit the contents of this buffer using all the goodness of native vim and then type ZZ. Thanks to a couple of autocommands in my .vimrc, this saves the buffer, saves a copy of its contents in a timestamped archive file, closes the buffer, and then hides MacVim, leaving Firefox active with the text I just wrote inserted in the form field, ready to submit.

Here are the requisite pieces of the puzzle:


This is the pentadactyl configuration file. You need to add a line to set your external editor to the script that opens MacVim.

set editor="~/bin/firefox"


Make a symbolic link to your mvim script so that the script can detect the context it should run in.

ln -s ~/bin/mvim ~/bin/firefox


This is the mvim script that comes bundled with MacVim, moved to ~/bin and modified. Add this bit to the 'case "$name" in' statement:

        opts="$opts --remote-tab-wait-silent"


Next, a couple of additions to the vim configuration file to make ZZ save the contents to an archive, close the buffer, and return Firefox to the foreground.

augroup VimperatorYPentadactyl
    au! BufRead vimperator-*,pentadactyl-* nnoremap <buffer> ZZ :call FormFieldArchive() \| :silent write \| :bd \| :macaction hide:<CR>
    au BufRead vimperator-*,pentadactyl-* imap <buffer> ZZ <Esc>ZZ
augroup END

Finally, here is the function that saves a copy of the form field contents to an archive file, complete with timestamp and url:

function! FormFieldArchive()
    let l:contents = getbufline("%", 1, "$")
    let l:filepath = expand("%")
    let l:filename = expand("%:t:r")
    let l:formfielddir = $HOME . "/webforms/"
    let l:currentdate = TimestampText('date')
    let l:entry = l:formfielddir . l:currentdate . ".txt"
    let l:entryexists = filereadable(l:entry)
    exec "split " . l:entry
    if l:entryexists
        normal Go
        normal o
        exec "call setline(\".\", \"" . TimestampText('time') . "\")"
        exec "call setline(\".\", \"" . TimestampText('journal') . "\")"
        exec "silent !svn add " . l:entry
    normal o
    exec "call setline(\".\", \"" . l:filename . "\")"
    normal o
    exec "call setline(\".\", " . string(l:contents) . ")"

function! TimestampText(style)
    let l:iswindows = has("win16") || has("win32") || has("win64")
    if l:iswindows
        if a:style == "long"
            let l:dateformat = strftime("%#x %H:%M:%S ")
        elseif a:style == "short"
            let l:dateformat = strftime("%Y-%m-%d %H:%M:%S ")
        let l:dateformat .= substitute(strftime("%#z"), '[a-z]\+\($\| \)', '', 'g')
        if a:style == "long"
            let l:dateformat = strftime("%Y %b %d %a %X %Z")
        elseif a:style == "journal"
            let l:dateformat = strftime("%A, %B %d, %Y %H:%M:%S %Z")
        elseif a:style == "short"
            let l:dateformat = strftime("%Y-%m-%d %H:%M:%S %Z")
        elseif a:style == "time"
            let l:dateformat = strftime("%H:%M:%S %Z")
    if a:style == "date"
        let l:dateformat = strftime("%Y-%m-%d")
    return l:dateformat

This will create a new file with the current date, e.g. "2010-03-15.txt", if it doesn't already exist. If it does exist, it will add the entry to the end of the file separated by a timestamp. TimestampText() is just a convenience function I wrote that returns a timestamp in various strftime() formats.

[2011-02-28 22:10:12 PST Update: Now using pentadactyl, so updated references appropriately. Added TimestampText() function for completeness. A couple of other minor edits and links added.]