Code Monkey home page Code Monkey logo

vim-hooks's Introduction

VimHooks

Introduction

This is a Vim plugin that lets you automatically execute arbitrary shell scripts after specific autocmd events are fired while editing certain files. It does this by looking for specially-named scripts in your current working directory (as well as ~/.vimhooks/) that have names like .bufwritepost.vimhook.rb or .cursorhold.vimhook.sh and executes those scripts whenever โ€” in this example โ€” Vim fires the BufWritePost and CursorHold autocmd events, respectively.

In Other Words

If you came here thinking, "I'd like to run a script every time I save a file in Vim" then this plugin is for you. Keep reading.

VimHooks Flow

VimHook scripts, which I refer to as "hook scripts," or just "hooks" throughout this document, can live at the project level or at a global level in ~/.vimhooks/. Hooks can be synchronous (the default) or asynchronous (in a fire-and-forget sort of way). The autocmd triggers can be debounced so hooks are only executed once within a specified window of time. The stdout produced by hook scripts can be loaded into a split window that refreshes automatically every time the hook is executed. Hooks that are configured to run silently will still report stderr when they exit with a non-zero exit code. Finally, the :ListVimHooks command provides a listing of all enabled and disabled hook scripts available in a particular session. They are listed in the order they would (synchronously) execute and can be toggled on and off interactively. You can make edits to hook scripts on the fly and the changes will be reflected the next time they are run.

Demos

Here is your obligatory set of live-demo gifs. The first is the original example I have used since creating this plugin, and the second is one I created recently, which makes use of the new "buffer output" feature.

Sass Recompilation and Browser Reload

Recompile a Sass file and then reload Chrome, Firefox, and Safari using AppleScript. See the code that does this. (Yes, this example is a little dated. I still use this for quick things but Browsersync might be a better solution for you.)

VimHooks Reload GIF

Vim as REPL

Execute whatever code you're currently editing and see the result from stdout opened in a new window. See the code that does this.

VimHooks Buffer Output GIF

Installation

If you don't have a preferred installation method, I recommend installing pathogen.vim, and then simply copy and paste:

cd ~/.vim/bundle
git clone https://github.com/ahw/vim-hooks.git

How it works

When some event E is fired from a Vim buffer named F (i.e., the filename), VimHooks looks through the list of all hook scripts in the current working directory and ~/.vimhooks/, and executes those hooks whose event property is E and whose matching suffix property matches F.1 These and other properties are embedded in the hook script filename itself and follow a specific naming pattern so that the plugin can parse them out. This pattern is described below.

VimHook naming pattern

VimHook Naming Structure

Property Description
leading dot (optional) Doesn't matter whether the file is hidden or not.
sort key (optional) There can be multiple VimHooks set to trigger on the same autocmd event, and they are executed in lexicographical order. If it is important that certain hooks execute in a specific order, you can add one or more digits in this space to ensure the ordering is correct.
matching suffix (optional) Assuming the event property has matched, a VimHook will trigger for all files ending with this matching suffix. If matching suffix is "js", the hook will trigger for all files ending in "js". When matching suffix is "main.js" the hook will trigger for all files ending in "main.js" (including "main.js" itself). If there is no matching suffix the hook becomes global: it will trigger for all files. The matching suffix can contain dots.
event The name of the autocmd event the hook is triggered on. Case insensitive.
"vimhook" Identifies this as a VimHook script. Nothing after "vimhook" is parsed out by the plugin.

Note that in general, each component of the pattern is separated by a "." from the other components, though the matching suffix can itself contains dots and VimHooks knows how to accommodate these. When you leave off one of the optional pattern components (e.g., sort key) you do not need to include the dot marking its place. Thus, bufwritepost.vimhook.sh, js.bufwritepost.vimhook.rb, and .001.foo.bar.py.bufwritepost.vimhook.js are all valid VimHook filenames.

Arguments provided to a hook script

Each script is passed

  1. the name of the current buffer
  2. the triggered event name
  3. the path to the current file without the extension
  4. the path to the parent directory of the current file

as command-line arguments, in that order. So in a Bash shell script you could, for example, use $1, $2, $3, and $4 to access these values.

Example

Let's assume

  • You're editing a file named Circle.java and
  • You have a VimHook named Circle.java.bufwritepost.vimhook.sh in your current working directory

Then the table below shows which arguments map to which things.

Bash code inside Circle.java.bufwritepost.vimhook.sh Output
echo $1 Circle.java
echo $2 bufwritepost
echo $3 ./Circle
echo $4 .
echo ${1%.java} Circle

VimHook Options

VimHooks supports additional functionality that is exposed by setting various VimHook options. Option flags are set either

  1. ๐Ÿ“„ Locally in the source code of a hook script, or
  2. ๐ŸŒ Globally, via global variables in your ~/.vimrc.

How to set options

To set an option flag and value in your VimHook script, add a line anywhere in the file that follows the convention <comment_token> vimhook.myOptionKey = myOptionValue. For example, in a Bash script this would be a valid way to set an option:

# vimhook.bufferoutput = true

Available options

๐Ÿ“„ Option Key / ๐ŸŒŽ Global Variable Behavior
๐Ÿ“„ย vimhook.bufferoutput
๐ŸŒŽย g:vimhooks_bufferoutput
When true, dump the stdout from this hook script into a new scratch buffer, opened automatically in a new window. If the buffer already exists, overwrite it and refresh the window. When false, VimHook scripts are executed silently, though stderr is still reported when scripts exit with a non-zero exit code. Default: false
๐Ÿ“„ย vimhook.bufferoutput.vsplit
๐ŸŒย g:vimhooks_bufferoutput_vsplit
When true, open the buffer output window in a vertical split instead of the default horizontal. When false or omitted, buffer output window is opened in a horizontal split. This option is only relevant when vimhook.bufferoutput is true. Default: false
๐Ÿ“„ย vimhook.bufferoutput.filetype
๐ŸŒย g:vimhooks_bufferoutput_filetype
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. Default: unset
๐Ÿ“„ย vimhook.bufferoutput.wrap_mode
๐ŸŒŽย g:vimhooks_bufferoutput_wrap_mode
Executes :set wrap or :set nowrap in the output buffer depending on the value. The value should either be wrap or nowrap. Default: nowrap
๐Ÿ“„ย vimhook.bufferoutput.feedkeys
๐ŸŒย g:vimhooks_bufferoutput_feedkeys
Executes whatever Normal commands are provided. For example, vimhook.bufferoutput.feedkeys = G would cause the output buffer to always scroll to the bottom. Default: unset
๐Ÿ“„ย vimhook.async
๐ŸŒย g:vimhooks_async
When true, execute this hook in a forked process. The exit code, stdout, and stderr will all be lost to the ether ("fire and forget"). Default: false
๐Ÿ“„ย vimhook.debounce.wait: N
๐ŸŒŽย g:vimhooks_debounce_wait
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. Debounced hooks are implicitly async, so the disclaimers described for that option hold for debounced hooks too. Default: unset
๐Ÿ“„ย (Not applicable)
๐ŸŒย g:vimhooks_list_enabled_first
When explicitly set to false, :ListVimHooks will stop grouping enabled hooks first and disabled hooks second. Instead, all hooks are listed in lexicographical order. Default: true

More on setting options

Global option settings are applied first and overridden on a per-hook basis wherever they are used.

The option line can begin with anything you want (like a comment character) but should not have anything after the myOptionValue part. Whitespace around the = sign is irrelevant. You can use a : instead of an = sign if you prefer. Options with no value defined are implicitly set to "true". For example, here are some equivalent ways of setting the option bufferoutput to true.

# vimhook.bufferoutput = true
// vimhook.bufferoutput : true
-- vimhook.bufferoutput:1
// some other comment here then vimhook.bufferoutput

The following are all equivalent ways of setting the bufferoutput key to false.

# vimhook.bufferoutput = false
>>> vimhook.bufferoutput : false
" vimhook.bufferoutput:0

For the sake of showing an example of a non-boolean option, these are all equivalent ways of setting the option debounce.wait to 2 seconds.

# vimhook.debounce.wait = 2
// vimhook.debounce.wait: 2
-- vimhook.debounce.wait : 2

Commands

ListVimHooks

The :ListVimHooks command takes zero arguments. It opens a new unmodifiable buffer in a horizontal split which lists all of the VimHook script files the plugin has found after scanning the current working directory as well as the ~/.vimhooks/ directory. The enabled hook scripts are listed before the disabled ones. Helpfully, within each of these groupings, the relative order of the hook scripts matches their order of execution.

There are two sections in this buffer: the Mappings section which shows a "cheat sheet" of the buffer-local mappings and the Hooks section which, for each VimHook script, shows a checkbox indicating the enabled/disabled state of the script (checked means enabled), the matching suffix (where * represents a UNIX-style blob), the autocommand event which triggers the script, and the path to the script. The x mapping is particularly useful as it allows you to quickly toggle on and off individual VimHook scripts as you move between projects that require different hooks.

Pressing s, i, o, or <CR> will open the hook file for editing in one way or another. If you make changes to a hook file and save it, the plugin will automatically pick up those changes. Isn't that nice? It does this by listening for BufWritePost events on *vimhook*-patterned filenames and re-running :FindHookFiles for you.

ListVimHooks GIF

Note: this gif is slightly out of date. The :ListVimHooks command has since been modified to always list active VimHook scripts first at the top of the Hooks section before listing the inactive ones.

The buffer-local mappings are inspired from NERDTree:

  • x Toggles the enabled/disabled state of a VimHook script (this only has an effect when the cursor is on one of the lines in the "Hooks" section.
  • d delete a VimHook (runs rm -i so you will be prompted to confirm)
  • r run a VimHook manually in "debug" mode (it runs :!VIMHOOK_PATH CONTENT_OF_#_REGISTER VIMHOOK_EVENTNAME so all output is echoed to screen)
  • s Opens a VimHook script in a vertical split
  • i Opens a VimHook script in a horizontal split
  • o Opens a VimHook script in the previous window. (If not possible, it will open in a vertical split.)
  • <CR> Opens a VimHook script in the current window

FindHookFiles

The :FindHookFiles command re-runs the same initializing logic VimHooks runs when you start Vim with the plugin installed. It will "forget" any VimHook scripts it may have previously found and re-scan the current working directory as well as the ~/.vimhooks/ directory. Use this command if you have created a new VimHook script and want to start using it without closing and re-opening your entire Vim session.

ExecuteHookFiles

The :ExecuteHookFiles command takes a single argument which is the name of a Vim autocmd you would like to manually "trigger." The event name can be tab-completed. For example, if you would like to verify the VimHook scripts listening for the VimEnter event are functioning correctly you can manually fire them off by running :ExecuteHookFiles VimEnter.

StopExecutingHooks

The :StopExecutingHooks command will temporarily disable triggering of all VimHook scripts.

StartExecutingHooks

The :StartExecutingHooks command turns VimHook script triggering back on.

FAQs

What if I want to manually fire hooks?

You may find yourself wishing you could turn off automatic triggering of hooks and instead fire them manually via some convenient key mapping. Though I'll admit this feels a bit crude, I've had good luck with this straight-forward mapping in my ~/.vimrc.

nnoremap gh :StartExecutingHooks<cr>:ExecuteHookFiles BufWritePost<cr>:StopExecutingHooks<cr>
nnoremap ghl :StartExecutingHooks<cr>:ExecuteHookFiles VimLeave<cr>:StopExecutingHooks<cr>

Which autocmd events are exposed by Vim Hooks?

A handful that seemed useful, but certainly not all of them. It would be simple for me to add other events not on this list, but BufWritePost and VimLeave have been enough for all my use cases so far. Feel free to file an issue if you'd like others. Here is the exhaustive list at the moment:

  • BufAdd
  • BufNew
  • VimEnter
  • VimLeave
  • BufEnter
  • BufLeave
  • BufDelete
  • BufUnload
  • BufWinLeave
  • BufWritePost
  • BufReadPost

Example Usage

Troubleshooting

My bufwritepost hooks aren't firing

It seems that sometimes vim-fugitive puts Vim in a weird state where it thinks it's in diff mode. Or at least, VimHooks thinks it's in diff mode. The VimHooks plugin does not fire hooks while in diff mode and this might be why your hooks aren't firing. It's probably a bug in VimHooks, but until I get to the bottom of it, my remedy is to manually re-enter and then exit :Gdiff-mode.

:Gdiff " Opens up split diff window
:q " Quit the left-hand side diff window
" VimHooks should start working again.

Hooks aren't working with Python virtualenv

Two things you can try to fix this:

  1. Set your PYTHONPATH environment variable in your hook script.
  2. source your virtualenv activate script in your hook script.

For example:

#!/bin/sh
# vimhook.bufferoutput

# This may NOT be necessary if you've installed Python in a "standard" location.
PYTHONPATH=/Users/andrew/python/lib/python2.7

# Source the activate script for your virtualenv
source ~/public/my-env/bin/activate

babel $1 > main.js

# Now you can use Python modules installed in your virtualenv
aws s3 cp main.js s3://example.bucket/

The reason behind this: Vim executes shell commands in a different environment than your normal login shell. You may have come across this before while trying to make use of custom aliases defined in your ~/.zshrc or similar and discovering they don't work when you attempt :!ll or some other shell alias.

Footnotes

1 Actually, VimHooks only iterates over all the hook files for the very first trigger of some new filename/event combination. It then populates a cache which is accessed whenever that same filename/event combination is fired again.

vim-hooks's People

Contributors

ahw avatar

Stargazers

Borja Martinena avatar Dyxn avatar  avatar  avatar frankfanslc avatar Jason R. Stevens, CFA avatar Manoj S K avatar  avatar Allan Jacquet-Cretides avatar SimbaFs avatar Roman Dobiรกลก avatar Xvezda avatar fx-kirin avatar Shay Palachy-Affek avatar Ryan Young avatar Matt Gleich avatar Stanislav Karkavin avatar  avatar TANIGUCHI Masaya avatar Adam Wagner avatar Richard avatar  avatar Magnus Henkel avatar  avatar Travis Mattera avatar KaฤŸan UฤŸur avatar Christophe Pollet avatar Brett Neese avatar Ramon Mendes de Souza avatar dns avatar  avatar  avatar ik5 avatar superpanda avatar Tsuyoshi CHO avatar Anony Moose avatar  avatar Nick S. B. avatar Jimmy Klingwall avatar AVA avatar Damien Szczyt avatar hisky.blogger avatar Volodia Manivchuk avatar  avatar PegasusWang avatar Grรฉgory Horion avatar Suraj Dash avatar tricarte avatar iosmanthus avatar Andrew Lee avatar Cody avatar Kelby Ludwig avatar ADoyle avatar Zhong Liu avatar cnxzcxy avatar Bad Sir Brian avatar Todd Freed avatar Todd Freed avatar Suchan An avatar Aidan avatar Vince van Oosten avatar Artur Marzano avatar Hector Leiva avatar Askar Yusupov avatar Ian Link avatar vincent avatar Eugene avatar Renato Rufino dos Santos avatar Eugen avatar Nathan Farrar avatar Yuval Kogman avatar Sam Moore avatar Jacob Angel avatar Iryna Shestak avatar Angus H. avatar Chiu-Hsiang Hsu avatar Jacob Pan avatar Stefano Zaghi avatar mitsuse avatar David O'Trakoun avatar Yuki Iwanaga avatar Masaki ISHIYAMA avatar deris avatar Peter Ajtai avatar Koichi Shiraishi avatar 9r0k avatar Nicholas Cartwright avatar Nicolai Skogheim avatar Niels-Oliver Walkowski avatar Andrea Bonomi avatar Jiang Yucheng avatar Brian Fontenot avatar Tomonori Kimura avatar Damian Baar avatar Petr Skocik avatar Korjavin Ivan avatar catfist avatar Jerome Coloma avatar Matt Petty avatar Ling Yang avatar

Watchers

James Cloos avatar  avatar Koichi Shiraishi avatar Manoj S K avatar  avatar

Forkers

forzenheart

vim-hooks's Issues

Allow more movement in ListVimHooks, like 0 and $

Really annoying when you call :ListVimHooks with the cursor at some really far-right column in whatever source you're looking at, because the listing window pops up with the cursor also moved as far right as possible, and since j, k are the only movements available, it's impossible to see what's to the left.

:ListVimHooks shows incorrect hook name

  1. :ListVimHooks
  2. Enable a disabled hook
  3. :q (close the ListVimHooks buffer)
  4. :ListVimHooks
  5. Note how the hook you just enabled ends in ".disabled" even though that part was supposedly removed when you first enabled it.

Add a way to turn off automatic execution but still allow manual

Getting tired of having somewhat long-ish build processes kick off after every file write. Currently getting around this with nnoremap gh :StartExecutingHooks<cr>:ExecuteHookFiles BufWritePost<cr>:StopExecutingHooks<cr>.

Maybe just document this in the README.

List Vim hooks in a nice pretty buffer

Make the :ListVimHooks command open a new read-only scratch buffer that lists the various hooks all nice and pretty with colors and stuff. Currently just echos them directly to the screen.

Dump stdout and stderror into different buffers when vimhook.bufferoutput is set

Should be able to send both to the same buffer (current default behavior?) or to different buffers. If different buffers, then declaring one of them will imply that you wish to ignore the other unless additional options are specified elsewhere explicitly including it.

vimhook.bufferoutput.stdout
# implies stderr will be ignored unless further option settings say otherwise

vimhook.bufferoutput.stderr
# implies stdout will be ignored unless other option settings say otherwise.
# in this case, stdout will get its own buffer since it was specified above.

vimhook.bufferoutput
# if only this option is set, then both stdout and stderr will be dumped,
# and both will be dumped to the same buffer

Document how hooks don't run in diff mode

And explain that somehow Vim thinks a file is in diff mode after running Gdiff even though it's not and how to solve this (re-run :Gdiff and then manually close the left-hand-side temp file that it creates to put the original file back in regular, non-diff mode).

Document flexibility in filename conventions

For example, .bufwritepost.vimhook.chrome-reloader.sh or .bufwritepost.vimhook.chrome-reloader.sh.disabled. Check if this also works for extension- and filename-specific hooks.

Allow persistent toggling of specific Vim hooks' disabled/enabled state

Ideally make the :ListVimHooks command (or some other different command?) spit out a special buffer which can be navigated with j and k. Pressing x will enable/disable each hook script on an individual basis. Works like Gmail. Disabling will probably amount to actually changing the filename to end with ".disabled" so as to keep the change persistent. This way you can keep useful scripts in ~/.vimhooks/ and toggle them on and off whenever you switch from one sort of use case to another as you switch projects.

Allow asynchronous script execution

Not sure how feasible this is. My best idea at the moment is to create a dependency on vim-dispatch which already encapsulates whatever black magic tpope has written to allow async execution, and somehow leverage that in a sideways fashion. But I don't use vim-dispatch much myself so even that may turn out to be a dead end. In any case, there does not seem to be an obvious clean solution to this; as far as I can tell, Vim really wants to be single-threaded and synchronous.

Generalize hook naming convention

It's weird that the patterns are [SORT].[EVENT].[EXT].vimhook, [SORT].[EVENT].vimhook and [FILENAME].vimhook. A better pattern might be ^\.?(\d+\.)?(.+\.)?(\w+)\.vimhook which is best visualized on Regexper. Basically it allows an optional sort key, an optional "suffix" (which could be just "js" or the whole filename itself), and the event name. They each have a "." dot between them and it allows for an optional dot at the beginning, which lets the user decide whether or not to make the file hidden.

What user is the shell script run as and are there options to change?

I'm trying to do some automated builds (jade, sass, browserify), but I'm getting npm not found errors. Guessing it's because the user the script is run as is not the one that has node installed. Are there any options that can be set around the user to run the script, or does it have to be baked into the script itself?

vimhook.ignore_other_scripts

Or something like that. Provide a way for a hook to say, "if I'm being executed then ignore hooks A and B because they are redundant."

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.