I program in Vim, which means that everything I do when programming--making database migrations, using git, etc.-- happens in a terminal. I like to pack all of those terminals into a Tmux session to keep things organized.
My main focus recently has been TDDD (Test Driven Django Development). I'm testing constantly--so much so that switching back and forth between vim and a dedicated testing terminal once or twice a minute has become a real drag on my workflow.
Thankfully, vim being vim, someone has written a plugin that makes your vim-tmux workflow as seamless as possible. Vimux is a vim plugin that facilitates communication with the tmux session vim is running in. Its core feature is to open a small tmux pane beneath vim and send commands in it. This is perfect for running tests.
All you need to get unit testing bound to a vim shortcut is a script to find the appropriate tests and run them. I wrote a simple, Django-specific implementation in Haskell (I won't waste an opportunity to practice Haskell).
import System.Directory
import System.FilePath
import System.Process
import Control.Monad
import Data.Maybe
import Control.Exception
dirParents :: FilePath -> [FilePath]
= takeWhile (/="/") . iterate takeDirectory
dirParents
inDirConts :: String -> FilePath -> IO Bool
= elem x <$> getDirectoryContents y
inDirConts x y
lowMatch :: String -> FilePath -> IO (Maybe FilePath)
= listToMaybe <$> filterM (inDirConts x) (dirParents y)
lowMatch x y
cmdArgs :: IO (Maybe [FilePath])
= do
cmdArgs <- getCurrentDirectory >>= lowMatch "__init__.py"
mod_dir <- maybe (return Nothing) (lowMatch "manage.py") mod_dir
base_dir return $ sequence [base_dir, fmap takeBaseName mod_dir]
runCmd :: Maybe [String] -> IO ()
Just (x:y:[])) = callProcess "python" [x ++ "/manage.py", "test", y]
runCmd (= return ()
runCmd _
main :: IO ()
= catch runtest handler
main where
= readProcess "tmux" ["display-message", "-p", "'#F'"] []
flags = do
runtest >>= runCmd
cmdArgs notElem 'Z' <$> flags) >>= flip when (callCommand "tmux resize-pane -Z -t {top}")
("tmux display 'Tests Succesful'"
callCommand handler :: SomeException -> IO ()
= (elem 'Z' <$> flags) >>= flip when (callCommand "tmux resize-pane -Z") handler _
It searches up the directory hierarchy for the root of the python module, then for the directory holding manage.py, then runs unit testing for the module. If the tests fail it reveals the testing terminal so I can see what went wrong, and if the tests pass it hides the testing terminal and flashes a happy message.
Here is more or less the same thing in Python:
import os
from subprocess import call, check_output
def find_up(query_dir, string):
while query_dir is not "/":
if string in os.listdir(query_dir):
return query_dir
else:
= os.path.dirname(query_dir)
query_dir
def unzoom():
= check_output("tmux display-message -p '#F'".split(' '))
flags if "Z" in str(flags):
"tmux resize-pane -Z", shell=True)
call(
def assemble_command():
= find_up(os.getcwd(), "__init__.py")
module_dir try:
= os.path.basename(module_dir)
module_name = find_up(module_dir, "manage.py")
base_dir return "python {}/manage.py test {}".format(base_dir, module_name)
except AttributeError:
return None
def main():
= assemble_command()
command if command:
unzoom()=True)
call(command, shell
if __name__ == "__main__":
main()
For scripts like the above tot work they need to be run in the same
directory, so you'll need to use it in conjunction with
VimuxRunCommandInDir("/path/to/script", 0). the 0 indicates that the
filename won't be given as an argument to the preceding command. Bind
":call VimuxRunCommandInDir("/path/to/script", 0)" to, say,
There are many advantages to using Tmux: terminal multiplexing, detaching and attaching to session, and now with Vimux, integration with Vim to improve your efficiency. Happy testing!