Releases: ahw/vim-hooks
2.2.0 Auto-refresh, prettier paths, feedkeys support, debug mode
Auto-refresh Hook Files After Editing
Hook files are just executed as scripts on the command line which means their contents are not stored in memory — with one exception: hook options, which are embedded as lines of text within a given hook script, are. They are parsed out during VimHook initialization and stored within an instance of the VimHook data structure. Thus in previous releases of this plugin, none of the changes a user made to the option settings of a hook script would be reflected until the user either quit and restarted Vim or when they manually ran :FindVimHooks, which wipes out everything and reinitializes all hooks from scratch.
In this release I've fixed that; any edits made to a VimHook script will trigger an automatic re-parsing of the script so that any option settings which may have changed will be immediately reflected. In the background, this is achieved by just setting up an autocmd listener to BufWritePost events on *vimhook* files. Nothing new is required from a user perspective — things should Just Work™.
Feed Keys
This one is actually pretty cool. Suppose you have the vimhook.bufferoutput option setting enabled for some hook, and suppose the output is a few hundred lines long. Perhaps you're writing a bunch of unit tests and each new one you write is outputting stuff to an ever-growing chunk of stdout. Now, every time the hook is triggered, that output will get refreshed just as you wanted, but because of the implementation, the window it's in will get scrolled back up to line 1. This becomes annoying when all you really care about is the most recent stuff at the bottom of the output (in this example, the latest unit test you've just written). Luckily, the vimhook.bufferoutput.feedkeys option has come to the rescue. Now you can pass in an arbitrary string of Normal mode commands to run in the output buffer after every hook execution. So something like:
# vimhook.bufferoutput.feedkeys = G
will make the buffer output window scroll to the very bottom every time it's updated. Cool, huh?
Run Hooks in "Debug" Mode
Can't quite remember why I did this, but I'm sure it was quite useful at the time. There is a new buffer-local mapping to r in the :ListVimHooks window which will run a VimHook manually in "debug" mode, meaning all output is echoed to screen. Useful for troubleshooting a hook that is asynchronous and for some reason not doing what you thought it would.
Ignore Enabled State in VimHook Listing
By default, :ListVimHooks will list enabled scripts first, in lexicographical order, followed by disabled scripts, in lexicographical order. If for whatever reason it makes more sense to you to list all hooks in lexicographical order without grouping them together into "enabled" and "disabled" groups, you can set the global option g:vimhooks_list_enabled_first to something falsey (like 0). The default is false.
Prettier Paths
Took a stab at replacing $HOME with ~ in the :ListVimHooks output just 'cause. It works fine in most cases but breaks down when $HOME is a symlink to someplace else. If you see that happening in your case, you'll have to just ignore it and move on until I fix the logic to handle that case.
Set Buffer Output Filetype
There is a new hook option, vimhook.bufferoutput.filetype, which sets the filetype of the output buffer to whatever value is provided. Useful if you want to get syntax highlighting or some other filetype-specific goodness from the output buffer. The default is unset.
2.1.0: Async, Debounce, Error Messaging
This update includes some awesome new functionality:
Asynchronous Hooks
You can set the vimhook.async option in a hook script to execute the script in a forked process. It's just appending a & to the end of the shell command. There isn't any callback functionality so any exit codes, stdout, and stderr data will just be lost in the ether. Also worth noting: although hooks will still start execution in lexicographic order, making a hook async means that it's async. Subsequent hooks will not wait for it to finish.
Debounced Hooks
You can set the vimhook.debounce.wait: N option in a hook script to execute the script in a forked process after N seconds have elapsed since the last trigger of this particular hook. Useful if you tend to trigger a hook multiple times in rapid succession but only really care about the result of the last one (e.g., reloading a browser tab after making lots of tiny little tweaks). Debounced hooks are implicitly async, so the disclaimers in the section above hold for debounced hooks too.
Verbose Error Reporting
For synchronous hooks (still the default) that exit with a non-zero code you'll now see any stdout and stderr data echoed back to the screen. So now instead of just telling you that your build script exited with a non-zero code, it'll actually show you the stack traces and messages you'd see if you were running the build script directly in the console.
2.0.0 One Regex to Rule Them All
This release introduces breaking changes in the way VimHooks parses out event and filename matching information from the hook filename. Instead of three separate naming schemes for global, extension-specific, and filename-specific hooks, there is now just one single scheme. This simplifies a lot of the code and makes the naming scheme consistent, intuitive, and easier to remember. Additional bonus: the leading dot is now irrelevant, so you can make your hook files regular or hidden and either way is fine. The general form of this pattern is
[.][sortkey.][suffix.]eventname.vimhook[...]
The leading dot, sortkey, and suffix properties are optional. The sortkey property works the same as before, it just allows the user to ensure VimHooks execute in the correct lexicographic order. The suffix property is used to build a regex of the form .*suffix$. Any files whose autocmd event matches the hook event, and whose filename matches against this regex will trigger the hook. Thus, the global, extension-specific, and filename-specific use cases can all still be accomplished, but now this happens with just a single unified naming scheme.
- Global hooks will have no suffix, since all filenames will match against
.*$. - Extension-specific hooks will have their extension as a suffix since
.*js$matches all*.jsfiles. - Filename-specific hooks will have their entire filename as a suffix. Although technically
.*main.jswill match any file ending in "main.js", I think in practice the only file that will actually meet this criteria is "main.js" itself.
Fixed a few bugs and as usual made a million edits to the README. Check out that fancy vector graphic in the VimHook naming pattern section.
1.4.1
Options
This release introduces support for VimHook options which are set in the source code of a VimHook script. I chose this scheme because I wanted to avoid having to rely on some arbitrary config file. Users can simply insert specially-formatted comment lines in their hook scripts to set these options.
The only two options supported in this release are:
- vimhook.bufferoutput When
true, this tells the plugin to dump stdout from this hook script into a new scratch buffer which is opened in a new Vim window. If a window for this output already exists, it is updated with the latest stdout result. - vimhook.bufferoutput.vsplit When
true, the plugin will open the aforementioned "bufferoutput" window in a vertical split instead of a horizontal one. This option is only relevant when thevimhook.bufferoutputoption istrue.
Full details of the syntax of these option lines is available in the README.
Buffer Output
When the vimhook.bufferoutput option is set, the plugin will dump stdout from the hook script into a scratch buffer and open that buffer in a new Vim window. It does this by running
execute 'silent %!/path/to/vimhook.sh'The silent %! part means "run the following external script and dump the result between lines 1 and $ of this buffer (i.e., all the lines).
VimHook Model
The VimHook model now has a new property which I have given the wonderfully confusing name, optional. The optional property is a Dictionary mapping option key Strings to option value Strings or Numbers. The VimHook model also has a new Function, getOptionValue(String key) which attempts to coerce the option value corresponding to key into a Number value 0 or 1, but will just return the option value as a String if that fails.
So if a VimHook script contained the following lines:
# vimhook.bufferoutput
# vimhook.bufferoutput.vsplit
# vimhook.optionFoo = true
# vimhook.option.bar: falseThen the resulting VimHook model would have the following properties:
...(all the other properties I've documented in the past)optional"bufferoutput":1(option keys with no value default to 1)"buffferoutput.vsplit":1"optionFoo":"true""option.bar":"false"
getOptionValue(String key): Function. If value is"1"or"true"return1. Else if value is"0"or"false"return0. Else return the option value as a String.
Miscellaneous
- Pressing
din the:ListVimHookswindow will runrm -ion the shell, thus providing a crude method for removing hooks directly from the listing window. :CreateNewVimHookcommand implemented but not documented yet in the README. It's not quite ready for release but I had some git maintenance issues and ended up having to rewrite some history and in process, put this commit onmaster. Not ideal, but that's life.- Vim will print a warning (in
echohl Warningcolors) when a hook script returns a non-zero exit code. - Spell checking is always disabled in the VimHook listing window (
:set nospell)
History Rewrite
As mentioned above, I had to run a git push --force before this release and thus ended up rewriting my history on both master and development. The reason was I had merged a bunch of commits from development to master, ran a git rebase -i on master to clean things up, and then pushed both branches out. I didn't realize this would create a series of very similar (but not exactly identical, depending on which commits I squashed, reordered, etc. during the rebase) commit paths on master and development. Because the rebase forced master and development to become separate branches in the graph, I soon found out that any future merges from development to master would again apply what were effectively the same bunch of commits I had merged earlier. Probably no one will ever read this and even fewer people will care, so I bit the bullet and did what everyone always says to never ever do and ran git push --force to fix things up the way I wanted. So far the sky has not fallen and the Git police have not come knocking on my door to take away my programmer badge. Also I've learned a valuable lesson about git rebase -i and remote branches.
1.3.1
This release fixes the major bug where VimHooks were not executing in lexicographic order. The underlying implementation changed a fair amount in order to accommodate this fix. Specifically, all VimHooks are stored in a single list, and when one of the listened-for autocmd events fires (e.g., BufWritePost) the plugin will iterate through all members of this master list and for each member, check if the hook pattern matches the current filename. If there is a match, the plugin will add that VimHook to a dictionary which maps filenames to a list of VimHooks whose pattern matches against that filename. After this is done once for a given filename, the plugin will no longer iterate through every VimHook in the master list when it receives subsequent events associated with that filename. Thus, it sets up a sort of cache of VimHooks for each newly-encountered filename in order to minimize the number of times it must scan through the entire list of VimHooks. The benefit of all this is that the plugin can keep VimHooks in lexicographic order while it is adding them to the dictionary of filenames-to-list-of-VimHooks.
There is also a small enhancement which adjusts the width of various columns in the :ListVimHooks buffer in order to accommodate the longest string in each column.
Finally, the VimHook model has changed a bit. A VimHook has the following properties:
paththe relative path to the hookidthe relative path to the hook with ".disabled" removed from the name if it exists. This is done so that we can enable and disable the hook (which changes the hook filename) without having to change theidbaseNamethe filename part of the pathisEnabledunixStylePatternthe UNIX-style glob pattern corresponding to thepatternregex. Used in the:ListVimHooksoutput since regexes are more difficult to readeventtheautocmdevent name that triggers the hookpatternthe filename matching patternisIgnoreableused internally to stop triggering a VimHook without actually disabling it. For example, if the hook does not have the execute bit set and the user does not allow it to be set after the prompt, the plugin will set this flag to1in order to skip over it for the duration of the session and avoid continuously prompting the user to set the execute bit over and over again.
1.3.0
Mostly more work on :ListVimHooks. Created a NERDTree-like mapping for o and changed the mapping of <cr> to also act like o. Either of these keys will attempt to open a VimHook script in the previous window. If the buffer in the previous window is unmodified and isn't a special sort of window like quickfix or the NERDTree window itself, vim-hooks will overtake it with the script file. If the buffer is modified but it exists in more than one window it will also be overtaken. In the cases where these conditions are not met, VimHooks just creates a vertical split. I co-opted the code from NERDTree directly.
1.2.0
Revamped :ListVimHooks implementation. Instead of creating a hacky while loop to capture all keystrokes and handle individually, it just leaves everything as-is and does an nnoremap for the few special keys we want to override. It does this only for the hooks filetype, which is the filetype of the buffer that is opened with :ListVimHooks.
Key mappings for the :ListVimHooks buffer:
x: Toggle a VimHook script enabled/disabled stateq: Close the buffer<esc>: Close the buffer (duplicate mapping)<cr>: Close the buffer (duplicate mapping)i: Open the relevant VimHook script in a splits: Open the relevant VimHook script in a vertical split
Note: in general, prevents modification of the buffer by setting nomodifiable. Before each x toggle action we do have to set modifiable of course, but then nomodifiable is set again before finishing the function.
1.1.0
Added a nice pretty output for :ListVimHooks. Uses some Gmail-inspired controls. j and k move up and down. x enables/disables a VimHook. <ESC> exits.
Internally, the code also now uses VimHook class or whatever it's called in VimL to try and encapsulate some functionality and keep things sane.
A VimHook has the following properties:
pathisEnabledscope("global", "filename", or "extension")scopeKey(not applicable if a global scope VimHook; the filename or extension for filename- and extension-scoped VimHooks, respectively)
A VimHook has the following functions:
enable()Set the executable bit and ensure thatself.pathdoesn't end in ".disabled"disable()Append ".disabled" toself.pathtoggleIsEnabled()Do one of the above, depending on the value ofself.isEnabled
1.0.0
Hello! Basic functionality described in README is there. Following is an excerpt showing the commands available.
" Find all hook files in the current working directory
command! -nargs=0 FindHookFiles call <SID>findHookFiles()
" Manually execute hook files corresponding to whichever events are given as
" the arguments to this function. Will autocomplete event names. Example:
" :ExecuteHookFiles BufWritePost VimLeave. Currently only executes the
" global hook files.
command! -nargs=+ -complete=event ExecuteHookFiles call <SID>executeHookFiles(<f-args>)
" Manually start and stop executing hooks
command! -nargs=0 StopExecutingHooks call <SID>stopExecutingHooks()
command! -nargs=0 StartExecutingHooks call <SID>startExecutingHooks()
" Pretty-print a list of all the vimhook dictionaries for debugging
command! -nargs=0 ListVimHooks call <SID>listVimHooks()