structured-haskell-mode
This minor mode provides structured editing operations based on the syntax of Haskell. In short-hand it's called SHM and throughout the codebase, too. It acts a bit like, and is heavily inspired by, paredit-mode for Emacs.
In using structured-haskell-mode you will find that your layout style will change and become more regular as the editor does the menial work for you. Given that, some assumptions about style are made in structured-haskell-mode and are best described by this style guide.
Features
Its features work by parsing the current declaration with an
executable called structured-haskell-mode
, and then creates marks
for all the nodes' positions in the buffer.
In paredit-mode, manipulation of the tree is so enjoyable because all the boundaries of nodes are explicitly specified by parentheses. Not so, in Haskell. To get around this limitation, we have a โcurrentโ node, which is always highlighted with a background color. With that in place, one is able to do all of the operations that paredit can do.
Useful keybinding for case-split.
(define-key shm-map (kbd "C-c C-s") 'shm/case-split)
See shm.el
for other keybindings. You might want to disable or
change some of the bindings to suit your tastes.
How to enable
Clone the project:
$ git clone https://github.com/chrisdone/structured-haskell-mode.git
You need to install the structured-haskell-mode executable which does the parsing.
$ cd structured-haskell-mode
$ cabal install
$ cd elisp/
$ make
Add the elisp library to your load-path
and require the library.
(add-to-list 'load-path "/path/to/structured-haskell-mode/elisp")
(require 'shm)
Then add it to your haskell-mode-hook:
(add-hook 'haskell-mode-hook 'structured-haskell-mode)
Turn off haskell-indentation-modes. They are incompatible with structured-haskell-mode. It has its own indentation functionality.
You'll want to customize these two variables: shm-quarantine-face
and shm-current-face
to something that better suites your color
theme.
Solarized-light users
The following are apparently pretty good for solarized-light.
(set-face-background 'shm-current-face "#eee8d5")
(set-face-background 'shm-quarantine-face "lemonchiffon")
Checking it works
Some users have trouble with the executable being in their PATH properly. That's fine, here's how to check that you're setup.
-
Open a Haskell file and go to a syntactically valid declaration, e.g.
main = return ()
. -
Check that your modeline contains
SHM
only.SHM!
means a parse error.SHM?
means it hasn't been able to parse anything yet.
Both in this scenario should not appear, if they do, see the next steps.
If your modeline is not SHM
and the current declaration doesn't have
a grey box anywhere in it, then you have a problem.
Go back to the declaration and try M-x shm/test-exe
. You should be
taken to a *shm-scratch-test*
buffer containing a vector of source
spans. If you don't, and you have something more like "program not
found", then you need to make sure it's findable.
You can try:
-
Set the Emacs
PATH
:(setenv "PATH" (shell-command-to-string "echo $PATH"))
-
Set the binary path that SHM calls:
(setq shm-program-name "/absolute/path/to/structured-haskell-mode")
-
Get the
exec-path-from-shell
package here and try that.
After that, disable and re-enable structured-haskell-mode
.
Development
Byte-compiling:
make clean
make check
make
Run tests
You can run the tests with the following:
(require 'shm-test')
M-x shm-test/run-all
Or with make:
make clean
make test
Write tests
To write tests there's a script for making them. Run M-x shm-test/new
and follow the instructions that look something like
this:
-- Steps to create a test
--
-- 1. Insert the test-case setup code.
-- 2. Move the cursor to the starting point.
-- 3. Hit C-c C-c to record cursor position.
-- 4. Press F3 to begin recording the test actions.
-- 5. Do the action.
-- 6. Hit F4 to complete the action and run C-c C-c.
Then copy the resulting elisp to shm-tests.el and run the tests to check it works properly.
FAQ
What does it use to parse?
It uses haskell-src-exts to parse code. It could just as easily use GHC as a backend, but from benchmarks, GHC is only twice as fast. When it's the difference between 15ms and 30ms for a 400 line module, it really does not matter. We're parsing declarations and individual nodes. Plus the GHC tree is more annoying to traverse generically due to its partiality.
How do I disable some keybindings?
You can disable any keybinding in the structured-haskell-mode map by
defining the key as nil
:
(define-key shm-map (kbd "M-{") nil)
Reporting a bug
Note: If you get a parse error (e.g. via M-x shm/test-exe
) for
valid code that is using fairly new (read: couple years) a GHC
extension, you are probably hitting the fact that
HSE
doesn't parse a bunch of newer GHC extensions. SHM does not do any
parsing itself, it
uses HSE. There
are some patches in the HSE repo, provided as pull requests, which
you can try applying to a local copy of HSE and then recompile SHM
with the new version.
To get extra useful information, always run:
M-: (setq debug-on-error t)
And then re-run the same thing that gave you the problem. It will give you a backtrace that you can paste into the issue.
When reporting a bug, please write in the following format:
[Any general summary/comments if desired]
Steps to reproduce:
Type blah in the buffer.
Hit x key.
See some change z.
Hit y key.
Expected:
What I expected to see and happen.
Actual:
What actually happened.