blob: 415ce4d77c8ad8b29cc815a7627de17f1e872a0b [file] [log] [blame]
external-reality8f34d232014-02-22 21:35:47 -05001;;; shm.el --- Structured Haskell Mode
2
3;; Copyright (c) 2013 Chris Done. All rights reserved.
4;; Copyright (c) 1998 Heribert Schuetz, Graeme E Moss
5
6;; Author: Chris Done <chrisdone@gmail.com>
7;; Created: 19-Oct-2013
8;; Version: 1.0.2
9;; Keywords: development, haskell, structured
10;; Stability: unstable
11
12;; This file is free software; you can redistribute it and/or modify
13;; it under the terms of the GNU General Public License as published by
14;; the Free Software Foundation; either version 3, or (at your option)
15;; any later version.
16
17;; This file is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
23;; along with this program. If not, see <http://www.gnu.org/licenses/>.
24
25;;; Commentary:
26
27;; A minor mode for adding structured editing to Haskell.
28
29;;; Code:
30
31(require 'shm-customizations)
32(require 'shm-ast-documentation)
33(require 'shm-evaporate)
34(require 'shm-node)
35(require 'shm-lambda)
36(require 'shm-context-menu)
externalrealityf0eca722014-03-12 00:45:28 -040037(require 'hsq-cabal)
external-reality8f34d232014-02-22 21:35:47 -050038
39(require 'cl)
external-realitya56f1152014-03-04 21:57:05 -050040(require 'json)
external-reality8f34d232014-02-22 21:35:47 -050041
42(defvar shm-current-node-overlay nil
43 "Overlay to highlight the current node.")
44
45(defvar shm-decl-asts nil
46 "This is partly an optimization and partly for more
47functionality. We could parse the whole module, but that would be
48wasteful and expensive to lookup nodes every time we want a
49node. So it's cheaper to have the granularity of lookup start at
50the declaration's point and the node's span.
51
52Second it's better because a module may have unparseable content
53in it, but that doesn't mean we don't want structured editing to
54stop working on declarations that are fine. I've found in my use
55of SHM that this is a common use-case worth taking into account.")
56
57(defvar shm-string-node nil
58 "The string node that's currently being edited.")
59
60(defvar shm-string-buffer nil
61 "The buffer of the string node that's currently being edited.")
62
63(defvar shm-lighter " SHM?"
64 "The lighter for structured Haskell mode.")
65
66(defvar shm-last-point 0
67 "When moving around, the current node overlay will update
68 according to where you are. But often you can shrink/expand the
69 scope of the current node. This variable lets us avoid the node
70 being reset by realising we haven't actually moved the point.")
71
72(defvar shm-parsing-timer nil
73 "The timer used to re-parse every so often. The idle time can
74 be configured with `shm-idle-timeout'.")
75
76(defvar shm-last-parse-start 0
77 "This is used to avoid unnecessary work, if the start of the
78 declaration hasn't changed, and the end (see
79 `shm-last-parse-end') since we last parsed, don't bother
80 re-parsing.")
81
82(defvar shm-last-parse-end 0
83 "See `shm-last-parse-start' for explanation.")
84
85(defvar shm-last-yanked (list 0 0)
86 "When yanking, some text will be inserted, when popping a
87 yank (i.e. with M-y), you need to be able to erase the previous
88 yank. This is simply a region.")
89
90(defvar shm-map
91 (let ((map (make-sparse-keymap)))
92 ;; Navigation
93 (if (eq window-system 'x)
94 (define-key map (kbd "M-<return>") 'shm/present-actions-for-node)
95 (define-key map (kbd "M-]") 'shm/present-actions-for-node))
96 (define-key map (kbd "C-M-f") 'shm/forward-node)
97 (define-key map (kbd "C-M-b") 'shm/backward-node)
98 (define-key map (kbd "M-a") 'shm/goto-parent)
99 (define-key map (kbd "M-}") 'shm/forward-paragraph)
100 (define-key map (kbd "M-{") 'shm/backward-paragraph)
101 (define-key map (kbd "C-M-SPC") 'shm/mark-node)
102 (define-key map (kbd "C-c C-w") 'shm/goto-where)
103 (define-key map (kbd "C-c C-q") 'shm/qualify-import)
104 (define-key map (kbd "M-p") 'shm/walk)
105 map)
106 "Structural editing operations keymap. Any key bindings in this
107 map are intended to be only structural operations which operate
108 with the tree in mind.")
109
110;;;###autoload
111(define-minor-mode structured-haskell-mode
112 "Structured editing for Haskell."
113 :lighter shm-lighter
114 :keymap shm-map
115 (if structured-haskell-mode
116 (shm-mode-start)
117 (shm-mode-stop)))
118
119(defmacro shm-with-fallback (fallback &rest body)
120 "Perform the given action unless we're in a comment, in which
121 case run the fallback function insteaad."
122 `(if (shm-in-comment)
123 (call-interactively ',fallback)
124 (if debug-on-error
125 (progn ,@body)
126 (condition-case e
127 (progn ,@body)
128 (error
129 (message "(SHM command failed, falling back to %S. Run M-: (setq debug-on-error t) to see the error.)"
130 ',fallback)
131 (call-interactively ',fallback))))))
132
133(defun shm-mode-start ()
134 "Start the minor mode."
135 (set (make-local-variable 'shm-decl-asts)
136 nil)
137 (set (make-local-variable 'shm-current-node-overlay)
138 nil)
139 (add-hook 'post-self-insert-hook 'shm-post-self-insert nil t)
140 (unless shm-parsing-timer
141 (setq shm-parsing-timer
142 (run-with-idle-timer shm-idle-timeout t 'shm-reparsing-timer))))
143
144(defun shm-post-self-insert ()
145 "Self-insertion handler."
146 (save-excursion
147 (shm-appropriate-adjustment-point)
148 (forward-char -1)
149 (shm-adjust-dependents (point) 1)))
150
151(defun shm-mode-stop ()
152 "Stop the minor mode. Restore various settings and clean up any
153state that will hopefully be garbage collected."
154 ;; Kill the timer.
155 (cancel-timer shm-parsing-timer)
156 (setq shm-parsing-timer nil)
157 ;; Delete all markers.
158 (mapc (lambda (pair)
159 (mapc #'shm-node-delete-markers
160 (cdr pair))
161 (set-marker (car pair) nil))
162 shm-decl-asts)
163 ;; Delete all overlays.
164 (shm-delete-overlays (point-min) (point-max) 'shm-current-overlay)
165 (shm-delete-overlays (point-min) (point-max) 'shm-quarantine)
166 ;; Reset variables.
167 (setq shm-decl-asts nil)
168 (setq shm-current-node-overlay nil)
169 (setq shm-last-parse-start 0)
170 (setq shm-last-parse-end 0)
171 (setq shm-last-point 0))
172
173(defun shm-reparsing-timer ()
174 "Re-parse the tree on the idle timer."
175 (when structured-haskell-mode
176 (shm/reparse)))
177
178
179(defun shm-decl-ast (&optional reparse)
180 "Return the AST representing the current declaration at point.
181
182If the AST has already been loaded, that is returned immediately,
183otherwise it's regenerated. See the Internal AST section below
184for more information."
185 (let ((p (shm-decl-points)))
186 (when p
187 (shm-get-decl-ast (car p)
188 (cdr p)
189 reparse))))
190
191(defun shm-set-decl-ast (point ast)
192 "Store the given decl AST at the given POINT. If there is
193already an AST for a decl at the given point then remove that one
194and instate this one."
195 (setq shm-decl-asts
196 (cons
197 (cons (set-marker (make-marker) point) ast)
198 (remove-if (lambda (pair)
199 (when (= (marker-position (car pair))
200 point)
201 (set-marker (car pair) nil)
202 t))
203 shm-decl-asts)))
204 ast)
205
206(defun shm-get-decl-ast (start end &optional reparse)
207 "Get the AST of the declaration starting at POINT."
208 (let ((pair (car (remove-if-not (lambda (pair)
209 (= (marker-position (car pair))
210 start))
211 shm-decl-asts))))
212 (if (and (not reparse)
213 pair)
214 (cdr pair)
215 (progn
216 (when (or (/= start shm-last-parse-start)
217 (/= end shm-last-parse-end))
218 (setq shm-last-parse-start start)
219 (setq shm-last-parse-end end)
220 (let ((ast (shm-get-nodes (shm-get-ast "decl"
221 start
222 end)
223 start
224 end)))
225 (if ast
226 (progn (setq shm-lighter " SHM")
227 (when pair
228 (shm-delete-markers pair))
229 (shm-set-decl-ast start ast)
230 ;; Delete only quarantine overlays.
231 (shm-delete-overlays (point-min) (point-max) 'shm-quarantine)
232 (shm/init)
233 ast)
234 (progn
235 (when shm-display-quarantine
236 (shm-quarantine-overlay start end))
237 (setq shm-lighter " SHM!")
238 nil))))))))
239
240(defun shm-delete-markers (decl)
241 "Delete the markers in DECL."
242 (mapc #'shm-node-delete-markers
243 (cdr decl)))
244
245(defun shm-get-ast (type start end)
246 "Get the AST for the given region at START and END. Parses with TYPE.
247
248This currently launches a fresh process and uses this buffer
249nonsense, for any parse, which sucks, but is fast enough _right
250now_. Later on a possibility to make this much faster is to have
251a persistent running parser server and than just send requests to
252it, that should bring down the roundtrip time significantly, I'd
253imagine."
254 (let ((message-log-max nil)
255 (buffer (current-buffer)))
256 (when (> end (1+ start))
257 (with-temp-buffer
258 (let ((temp-buffer (current-buffer)))
259 (with-current-buffer buffer
260 (condition-case e
261 (call-process-region start
262 end
263 shm-program-name
264 nil
265 temp-buffer
266 nil
267 "parse"
268 "Emacs")
269 ((file-error)
270 (error "Unable to find structured-haskell-mode executable! See README for help.")))))
external-realitya56f1152014-03-04 21:57:05 -0500271 (json-read-from-string (buffer-string))))))
external-reality8f34d232014-02-22 21:35:47 -0500272
273(defun shm-lint-ast (type start end)
274 "Get refactor suggestions for the region of TYPE from START to END."
275 (let ((message-log-max nil)
276 (buffer (current-buffer)))
277 (when (> end (1+ start))
278 (with-temp-buffer
279 (let ((temp-buffer (current-buffer)))
280 (with-current-buffer buffer
281 (condition-case e
282 (call-process-region start
283 end
284 shm-program-name
285 nil
286 temp-buffer
287 nil
288 "hlint"
289 "Emacs")
290 ((file-error)
291 (error "Unable to find structured-haskell-mode executable! See README for help.")))))
292 (read (buffer-string))))))
293
294(defun shm-get-nodes (ast start end)
295 "Get the nodes of the given AST.
296
297We convert all the line-col numbers to Emacs points and then
298create markers out of them. We also store the type of the node,
299e.g. Exp, and the case of the node, e.g. Lit or Case or Let,
300which is helpful for doing node-specific operations like
301indentation.
302
303Any optimizations welcome."
304 (let* ((start-end (cons start end))
305 (start-column (save-excursion (goto-char start)
306 (current-column))))
307 (cond ((vectorp ast)
308 (save-excursion
309 (map 'vector
310 (lambda (node)
311 (vector
312 (elt node 0)
313 (elt node 1)
314 (progn (goto-char (car start-end))
315 (forward-line (1- (elt node 2)))
316 ;; This trick is to ensure that the first
317 ;; line's columns are offsetted for
318 ;; regions that don't start at column
319 ;; zero.
320 (goto-char (+ (if (= (elt node 2) 1)
321 start-column
322 0)
323 (1- (+ (point) (elt node 3)))))
324 (let ((marker (set-marker (make-marker) (point))))
325 marker))
326 (progn (goto-char (car start-end))
327 (forward-line (1- (elt node 4)))
328 ;; Same logic as commented above.
329 (goto-char (+ (if (= (elt node 4) 1)
330 start-column
331 0)
332 (1- (+ (point) (elt node 5)))))
333 ;; This avoids the case of:
334 (while (save-excursion (goto-char (line-beginning-position))
335 (or (looking-at "[ ]+-- ")
336 (looking-at "[ ]+$")))
337 (forward-line -1)
338 (goto-char (line-end-position)))
339 (let ((marker (set-marker (make-marker) (point))))
340 (set-marker-insertion-type marker t)
341 marker))))
342 ast)))
343 (t nil))))
344
345(defun shm-decl-points (&optional use-line-comments)
346 "Get the start and end position of the current
347declaration. This assumes that declarations start at column zero
348and that the rest is always indented by one space afterwards, so
349Template Haskell uses with it all being at column zero are not
350expected to work."
351 (cond
352 ;; If we're in a block comment spanning multiple lines then let's
353 ;; see if it starts at the beginning of the line (or if any comment
354 ;; is at the beginning of the line, we don't care to treat it as a
355 ;; proper declaration.
356 ((and (not use-line-comments)
357 (shm-in-comment)
358 (save-excursion (goto-char (line-beginning-position))
359 (shm-in-comment)))
360 nil)
361 ((save-excursion
362 (goto-char (line-beginning-position))
363 (or (looking-at "^-}$")
364 (looking-at "^{-$")))
365 nil)
366 ;; Otherwise we just do our line-based hack.
367 (t
368 (save-excursion
369 (let ((start (or (progn (goto-char (line-end-position))
370 (search-backward-regexp "^[^ \n]" nil t 1)
371 (unless (or (looking-at "^-}$")
372 (looking-at "^{-$"))
373 (point)))
374 0))
375 (end (progn (goto-char (1+ (point)))
376 (or (when (search-forward-regexp "[\n]+[^ \n]" nil t 1)
377 (forward-char -1)
378 (search-backward-regexp "[^\n ]" nil t)
379 (forward-char)
380 (point))
381 (point-max)))))
382 (cons start end))))))
383
384(defun shm-decl-node (start)
385 "Get the top-level node of the declaration."
386 (let* ((vector (save-excursion (goto-char start)
387 (shm-decl-ast))))
388 (elt vector 0)))
389
390(defun shm/backward-kill-word ()
391 "Kill the word backwards."
392 (interactive)
393 (let ((to-be-deleted (save-excursion (backward-word)
394 (point))))
395 (save-excursion
396 (shm-appropriate-adjustment-point)
397 (shm-adjust-dependents (point) (* -1 (- (point) to-be-deleted))))
398 (backward-kill-word 1)))
399
400(defun shm/delete ()
401 "Delete the current node."
402 (interactive)
403 (let ((current (shm-current-node))
404 (inhibit-read-only t))
405 (delete-region (shm-node-start current)
406 (shm-node-end current))))
407
408(defun shm/mark-node ()
409 "Set the active mark to the current node."
410 (interactive)
411 (let ((current (shm-current-node)))
412 (goto-char (shm-node-start current))
413 (set-mark (shm-node-end current))))
414
415(defun shm/type-of-node ()
416 (interactive)
417 (let ((current (shm-current-node)))
418 (cond
419 ((or (string= (shm-node-type-name current) "Exp")
420 (string= (shm-node-type-name current) "Decl")
421 (string= (shm-node-type-name current) "Pat")
422 (string= (shm-node-type-name current) "QOp"))
423 (let ((type-info (shm-node-type-info current)))
424 (if type-info
425 (shm-present-type-info current type-info)
426 (if (and shm-type-info-fallback-to-ghci
427 (fboundp 'haskell-process-do-type))
428 (haskell-process-do-type)
429 (error "Unable to get type information for that node.")))))
430 ((and (string= (shm-node-type-name current) "Name")
431 (let ((parent-name (shm-node-type-name (cdr (shm-node-parent (shm-current-node-pair))))))
432 (or (string= parent-name "Match")
433 (string= parent-name "Decl"))))
434 (let* ((node (cdr (shm-node-parent (shm-current-node-pair))))
435 (type-info (shm-node-type-info node)))
436 (if type-info
437 (shm-present-type-info node type-info)
438 (if (and shm-type-info-fallback-to-ghci
439 (fboundp 'haskell-process-do-type))
440 (haskell-process-do-type)
441 (error "Unable to get type information for that node (tried the whole decl, too).")))))
442 (t (error "Not an expression, operator, pattern binding or declaration.")))))
443
444(defun shm/describe-node (&optional node)
445 "Present a description of the current node in the minibuffer.
446
447Very useful for debugging and also a bit useful for newbies."
448 (interactive)
449 (let ((node (or node (shm-current-node))))
450 (if node
451 (message "%s" (shm-node-description node))
452 (error "No current node."))))
453
454
455(defun shm/goto-where ()
456 "Either make or go to a where clause of the current right-hand-side."
457 (interactive)
458 (let ((node-pair (shm-current-node-pair))
459 (vector (shm-decl-ast)))
460 (loop for i
461 downfrom (car node-pair)
462 to -1
463 until (or (= i -1)
464 (let ((node (elt vector i)))
465 (and (string= "Rhs"
466 (shm-node-type-name node))
467 (<= (shm-node-start node)
468 (shm-node-start (cdr node-pair)))
469 (>= (shm-node-end node)
470 (shm-node-end (cdr node-pair))))))
471 finally (return
472 (when (>= i 0)
473 (let ((rhs (elt vector i)))
474 (goto-char (shm-node-end rhs))
475 (cond
476 ((looking-at "[\n ]*where")
477 (search-forward-regexp "where[ \n]*"))
478 (t
479 (unless (= (line-beginning-position) (point))
480 (newline))
481 (indent-to
482 (+ 2
483 (shm-node-start-column
484 (cdr (shm-node-parent (cons i rhs))))))
485 (insert "where ")))))))))
486
487
488
489(defun shm-find-furthest-parent-on-line (current)
490 "Find the parent which starts nearest to column 0 on the
491current line.
492
493This is used when indenting dangling expressions."
494 (let ((parent (shm-node-parent current)))
495 (if parent
496 (if (= (line-beginning-position)
497 (save-excursion (goto-char (shm-node-start (cdr parent)))
498 (line-beginning-position)))
499 (shm-find-furthest-parent-on-line parent)
500 current)
501 current)))
502
503
504(defun shm/forward-paragraph ()
505 "Go forward one declaration."
506 (interactive)
507 (unless (/= (point)
508 (goto-char (cdr (shm-decl-points t))))
509 (search-forward-regexp "[^\n ]" nil t 1)
510 (backward-char)))
511
512(defun shm/backward-paragraph ()
513 "Go backward one declaration."
514 (interactive)
515 (unless (/= (point)
516 (goto-char (car (shm-decl-points t))))
517 (search-backward-regexp "[^\n ]" nil t 1)
518 (forward-char)))
519
520(defun shm/walk ()
521 (interactive)
522 (shm/reparse)
523 (shm/goto-parent-end))
524
525(defun shm/close-paren ()
526 "Either insert a close paren or go to the end of the node."
527 (interactive)
528 (shm-with-fallback
529 self-insert-command
530 (if (shm-literal-insertion)
531 (shm-insert-string ")")
532 (progn (shm/reparse)
533 (shm/goto-parent-end)))))
534
535(defun shm/close-bracket ()
536 "Either insert a close bracket or go to the end of the node."
537 (interactive)
538 (shm-with-fallback
539 self-insert-command
540 (if (shm-literal-insertion)
541 (shm-insert-string "]")
542 (progn (shm/reparse)
543 (shm/goto-parent-end)))))
544
545(defun shm/close-brace ()
546 "Either insert a close brace or go to the end of the node."
547 (interactive)
548 (shm-with-fallback
549 self-insert-command
550 (if (shm-literal-insertion)
551 (shm-insert-string "}")
552 (progn (shm/reparse)
553 (shm/goto-parent-end)))))
554
555(defun shm/goto-parent-end ()
556 "Set the current node overlay to the parent node, but go to the
557 end rather than the start."
558 (interactive)
559 (shm/goto-parent nil 'end))
560
561(defun shm/forward-node ()
562 "Go forward by node, i.e. go to the next of the current node. If
563we're already at the end of the current node, jump to the next
564node."
565 (interactive)
566 (let* ((current-pair (shm-current-node-pair))
567 (current (cdr current-pair)))
568 (if (= (point) (shm-node-end current))
569 (let ((next-pair (shm-node-next current-pair)))
570 (goto-char (shm-node-start (cdr next-pair))))
571 (goto-char (shm-node-end current)))))
572
573(defun shm/backward-node ()
574 "Go backward by node, i.e. go to the previous of the current node. If
575we're already at the start of the current node, jump to the previous
576node."
577 (interactive)
578 (let* ((current-pair (shm-current-node-pair))
579 (current (cdr current-pair)))
580 (if (= (point) (shm-node-start current))
581 (let ((prev-pair (shm-node-previous current-pair)))
582 (goto-char (shm-node-start (cdr prev-pair))))
583 (goto-char (shm-node-start current)))))
584
585(defun shm/goto-parent (&optional node-pair direction)
586 "Set the current node overlay to the parent node-pair"
587 (interactive)
588 (let ((direction (or direction 'start)))
589 (if shm-current-node-overlay
590 (let* ((o shm-current-node-overlay)
591 (parent-pair (shm-node-parent (or node-pair
592 (shm-current-workable-node)))))
593 (when parent-pair
594 (let ((parent (cdr parent-pair)))
595 (if (and o
596 (overlay-buffer o)
597 (>= (shm-node-start parent)
598 (overlay-start o))
599 (<= (shm-node-end parent)
600 (overlay-end o)))
601 (shm/goto-parent parent-pair direction)
602 (shm-set-node-overlay parent-pair direction)))))
603 (when node-pair
604 (shm-set-node-overlay node-pair direction)))))
605
606(defun shm/reparse ()
607 "Re-parse the current node.
608
609This is used on the reparsing timer, but also on commands that
610really need accurate AST information *right now*, so this will
611force a reparse immediately (if necessary)."
612 (interactive)
613 (shm-decl-ast t)
614 (when (/= shm-last-point (point))
615 (shm-set-node-overlay)))
616
617(defun shm-current-node ()
618 "Return just the current node, without its index.
619
620See `shm-current-node-pair' for what 'current' means."
621 (cdr (shm-current-node-pair)))
622
623(defun shm-actual-node ()
624 "Return just the actual current node, without its index.
625
626Normally node functions only care about the current workable
627node. This function will return the *actual* node at point. See
628`shm-current-node-pair' for what 'workable' means."
629 (cdr (shm-node-backwards)))
630
631(defun shm-current-node-pair ()
632 "Return the current workable node at point.
633
634Workable means that it is something that we want to be able to
635parse.
636
637For example, if we're looking at a Name,
638
639foobar
640
641then that is all well and good, but we don't want to edit a Name,
642nor a QName (the parent), we want to edit an Exp (parent-parent)
643whose constructor will be a Var."
644 (let ((current (shm-node-backwards)))
645 (when current
646 (if (and shm-current-node-overlay
647 (overlay-buffer shm-current-node-overlay)
648 (or (= (shm-node-start (cdr current))
649 (overlay-start shm-current-node-overlay))
650 (= (shm-node-end (cdr current))
651 (overlay-end shm-current-node-overlay))))
652 (overlay-get shm-current-node-overlay 'node-pair)
653 (shm-workable-node current)))))
654
655(defun shm-current-workable-node ()
656 "Returns the same as `shm-current-node' but including the index."
657 (let ((current (shm-node-backwards)))
658 (when current
659 (shm-workable-node current))))
660
661(defun shm-workable-node (current-pair)
662 "Assume that the given CURRENT node is not workable, and look
663at the parent. If the parent has the same start/end position,
664then the parent is the correct one to work with."
665 (let* ((parent-pair (shm-node-parent current-pair))
666 (parent (cdr parent-pair))
667 (current (cdr current-pair)))
668 (cond
669
670 (t (if parent
671 (if (and (= (shm-node-start current)
672 (shm-node-start parent))
673 (= (shm-node-end current)
674 (shm-node-end parent)))
675 (if (string= (shm-node-type current) (shm-node-type parent))
676 current-pair
677 (shm-workable-node parent-pair))
678 current-pair)
679 current-pair)))))
680
681(defun shm-node-previous (node-pair)
682 "Get the previous node of NODE-PAIR."
683 (let ((vector (shm-decl-ast)))
684 (loop for i
685 downfrom (car node-pair)
686 to -1
687 until (or (= i -1)
688 (let ((node (elt vector i)))
689 (<= (shm-node-end node)
690 (shm-node-start (cdr node-pair)))))
691 finally (return
692 (when (>= i 0)
693 (shm-workable-node (cons i
694 (elt vector i))))))))
695
696(defun shm-node-delete-markers (n)
697 "Set the markers to NIL, which is about the best we can do for
698deletion. The markers will be garbage collected eventually."
699 (set-marker (elt n 2) nil)
700 (set-marker (elt n 3) nil))
701
702(defun shm-in-comment ()
703 "Are we currently in a comment?"
704 (or (and (eq 'font-lock-comment-delimiter-face
705 (get-text-property (point) 'face))
706 ;; This is taking liberties, but I'm not too sad about it.
707 (not (save-excursion (goto-char (line-beginning-position))
708 (looking-at "{-"))))
709 (eq 'font-lock-doc-face
710 (get-text-property (point) 'face))
711 (and (eq 'font-lock-comment-face
712 (get-text-property (point) 'face))
713 (not (save-excursion (goto-char (line-beginning-position))
714 (looking-at "{-"))))
715 (save-excursion (goto-char (line-beginning-position))
716 (looking-at "^\-\- "))))
717
718(defun shm-in-string ()
719 "Are we in a string?"
720 (or (eq 'font-lock-string-face
721 (get-text-property (point) 'face))))
722
723(defun shm-find-overlay (type)
724 "Find overlays at point."
725 (remove-if-not (lambda (o) (overlay-get o type))
726 (overlays-in (point-min) (point-max))))
727
728(defun shm-current-overlay (start end node-pair)
729 "Make the overlay for current node at START to END, setting the
730NODE-PAIR in the overlay."
731 (let ((o (make-overlay start end nil nil t)))
732 (overlay-put o 'shm-current-overlay t)
733 (overlay-put o 'face 'shm-current-face)
734 (overlay-put o 'node-pair node-pair)
735 (overlay-put o 'priority 1)
736 o))
737
738(defun shm-quarantine-overlay (start end)
739 "Make a quarantine from START to END."
740 (let ((o (make-overlay start end nil nil t)))
741 (overlay-put o 'shm-quarantine t)
742 (overlay-put o 'face 'shm-quarantine-face)
743 (overlay-put o 'priority 0)
744 o))
745
746(defun shm-set-node-overlay (&optional node-pair jump-direction)
747 "Set the current overlay for the current node. Optionally pass
748NODE-PAIR to use the specific node-pair (index + node)."
749 (setq shm-current-node-overlay nil)
750 (shm-delete-overlays (point-min)
751 (point-max)
752 'shm-current-overlay)
753 (let* ((node-pair (or node-pair
754 (shm-current-node-pair)))
755 (node (cdr node-pair)))
756 (when jump-direction
757 (if (eq jump-direction 'end)
758 (goto-char (shm-node-end node))
759 (goto-char (shm-node-start node))))
760 (setq shm-last-point (point))
761 (setq shm-current-node-overlay
762 (when node
763 (shm-current-overlay (shm-node-start node)
764 (shm-node-end node)
765 node-pair)))))
766
767(defun shm-delete-overlays (start end type)
768 "Delete overlays of the given type. This is used for both
769current overlay and quarantines."
770 (mapc (lambda (o)
771 (when (overlay-get o type)
772 (delete-overlay o)))
773 (overlays-in start end)))
774
775(defun shm/init (&optional force-renew)
776 "Initialize the current node overlay at point.
777
778FORCE-RENEW would be used when the buffer has changed and
779therefore the current overlay should be re-initialized."
780 (interactive)
781 (when force-renew
782 (setq shm-current-node-overlay nil))
783 (shm-set-node-overlay))
784
785(defun shm-type-of-region (beg end)
786 "Get a type for the region."
787 (let ((types (shm-types-at-point beg)))
788 (loop for type
789 in types
790 do (when (and (= (elt type 0) beg)
791 (= (elt type 1)
792 end))
793 (return (elt type 2))))))
794
795(defun shm-types-at-point (point)
796 "Get a list of spans and types for the current point."
797 (save-excursion
798 (goto-char point)
799 (let ((line (line-number-at-pos))
800 (col (1+ (current-column)))
801 (file-name (buffer-file-name)))
802 (cond
803 (shm-use-hdevtools
804 (shm-parse-hdevtools-type-info
805 (with-temp-buffer
806 (call-process "hdevtools" nil t nil "type" "-g" "-fdefer-type-errors"
807 file-name
808 (number-to-string line)
809 (number-to-string col))
810 (buffer-string))))))))
811
812(defun shm-parse-hdevtools-type-info (string)
813 "Parse type information from the output of hdevtools."
814 (let ((lines (split-string string "\n+")))
815 (loop for line
816 in lines
817 while (string-match "\\([0-9]+\\) \\([0-9]+\\) \\([0-9]+\\) \\([0-9]+\\) \"\\(.+\\)\"$"
818 line)
819 do (goto-char (point-min))
820 collect
821 (let ((start-line (string-to-number (match-string 1 line)))
822 (end-line (string-to-number (match-string 3 line))))
823 (vector (progn (forward-line (1- start-line))
824 (+ (line-beginning-position)
825 (1- (string-to-number (match-string 2 line)))))
826 (progn (when (/= start-line end-line)
827 (forward-line (1- (- start-line end-line))))
828 (+ (line-beginning-position)
829 (1- (string-to-number (match-string 4 line)))))
830 (match-string 5 line))))))
831
832
833(defun shm/qualify-import ()
834 "Toggle the qualification of the import at point."
835 (interactive)
836 (save-excursion
837 (let ((points (shm-decl-points)))
838 (goto-char (car points))
839 (shm/reparse)
840 (let ((current (shm-current-node)))
841 (when (and current
842 (string= "ImportDecl"
843 (shm-node-type-name current)))
844 (cond
845 ((looking-at "import[\n ]+qualified[ \n]+")
846 (search-forward-regexp "qualified" (shm-node-end current) t 1)
847 (delete-region (point)
848 (search-backward-regexp "qualified"))
849 (just-one-space 1))
850 (t
851 (search-forward-regexp "import")
852 (shm-insert-string " qualified")
853 (just-one-space 1))))))))
854
855(defun shm/modify-type-constraint ()
856 "Modify a type signatures constraint"
857 (interactive)
858 (let* ((pair (shm-current-node-pair))
859 (current-node (cdr pair)))
860 (if (shm-type-signature-with-constraint-p pair)
861 (shm-add-additional-type-constraint current-node)
862 (add-initial-type-constraint current-node))))
863
864(provide 'shm)
865
866;;; shm.el ends here
867;; Local Variables:
868;; byte-compile-warnings: (not cl-functions)
869;; End: