Configure Emacs haskell-mode with projectile
Projectile is a powerful emacs mode especially when coupled with helm. One important feature for anyone writing tests is the ability to toggle between a source file and its test file. This is accomplished in projectile by invoking the interactive function, projectile-toggle-between-implementation-and-test
or the default key binding of C-c p t
. Unfortunately, this may not work right out of the box for you depending on the test suffix used in your project.
If you are using stack, which is a great tool for Haskell development, then the default configuration for projectile will need tuning. Check out Stack if you haven’t already. It might change the way you develop Haskell applications.
There are three goals for configuring my Haskell project with projectile in Emacs.
- Configure projectile’s build command
C-c p c
, to use stack - Configure projectile’s test command
C-c p P
, to use stack - Hook into projectile’s toggle between test and implementation for test files ending in “Spec”
Below I will present two different options for configuring Haskell and Projectile. First, global editor configuration for your project settings. Second, project specific settings for each project. There are pros and cons to each approach.
1st Approach, global settings
The first approach, with global settings, is useful where customizations don’t change often.
Like your build command, test command, and test suffix, for example. There are disadvantages to this approach if you work on several projects of the same type with different conventions.
If you happen to work in a project that uses cabal while another project uses stack then this is less desirable and even more severe if you have a project using multiple languages or frameworks.
(defun es/projectile-test-suffix (project-type)
"Return test files of Spec for haskell-cabal projects
Use -spec for all other project types"
(if (eq project-type 'haskell-cabal)
"Spec"
"-spec"))
(custom-set-variables
'(projectile-test-files-suffices
'("_test" "_spec" "Spec" "Test" "-test" "-spec"))
'(projectile-test-suffix-function #'es/projectile-test-suffix)
'(projectile-haskell-cabal-test-cmd
(concat haskell-process-path-stack " test"))
'(projectile-haskell-cabal-compile-cmd
(concat haskell-process-path-stack " build")))
2nd Approach, .dir-locals.el
The project specific settings, is powerful but has consequences. In this approach we utilize a special file named .dir-locals.el which allows you to specify project specific settings.
In our case, the test suffix function, the build command, and test command. The power of .dir-locals.el comes from the ability to specify configuration based on current mode, current directory, or sub-directory. This is powerful in a web application where you have a test command for the back-end and a separate test command for Javascript tests.
The biggest disadvantage is safety. If you check your .dir-locals.el file into source control, then Emacs will automatically attempt to load these variables while opening each and every buffer. This will result in annoying prompting and potentially the execution of arbitrary code if you are not careful.
Now, you can get around this by declaring variables as safe in your Emacs configuration as I will show you below. This essentially adds commands verified by you to a white-list. For the annoyance factor alone, I would recommend not checking this file into source control and provide friendly instructions on your Wiki for your fellow Emacsers.
Do not create a situation where your project will attempt to execute custom settings for fellow developers.
Here are the contents of a .dir-locals.el
in the base of my project.
((nil . ((projectile-test-suffix-function .
(lambda (project-type) "" "Spec"))
(eval . (progn
(require 'projectile)
(puthash (projectile-project-root)
(concat haskell-process-path-stack " build")
projectile-compilation-cmd-map)
(puthash (projectile-project-root)
(concat haskell-process-path-stack " test")
projectile-test-cmd-map))))))
Directory Local Variables have a special form. The outermost nil basically says to apply these configurations in every circumstance. Instead, you can supply a mode or even a sub-directory.
To disable prompting of safe file variables you’ll need to declare these variables as safe. If you follow this approach, you should use this mechanism to white list the configuration for your project.
Here is an example of white-listing the settings from the dir-locals in your local Emacs configuration.
(custom-set-variables
'(safe-local-variable-values
(quote
((projectile-test-suffix-function lambda
(project-type)
"" "Spec")
(eval progn
(require
(quote projectile))
(puthash
(projectile-project-root)
(concat haskell-process-path-stack " build")
projectile-compilation-cmd-map)
(puthash
(projectile-project-root)
(concat haskell-process-path-stack " test")
projectile-test-cmd-map))))))
Happy Emacs hacking. If you have any questions, please follow me on Twitter. Let me know how this works for you.