|
|
;;; haskell-indent.el --- "semi-intelligent" indentation module for Haskell Mode -*- lexical-binding: t -*- |
|
|
|
|
|
;; Copyright 2004, 2005, 2007, 2008, 2009 Free Software Foundation, Inc. |
|
|
;; Copyright 1997-1998 Guy Lapalme |
|
|
|
|
|
;; Author: 1997-1998 Guy Lapalme <lapalme@iro.umontreal.ca> |
|
|
|
|
|
;; Keywords: indentation Haskell layout-rule |
|
|
;; URL: http://www.iro.umontreal.ca/~lapalme/layout/index.html |
|
|
|
|
|
;; This file is not part of GNU Emacs. |
|
|
|
|
|
;; This file is free software; you can redistribute it and/or modify |
|
|
;; it under the terms of the GNU General Public License as published by |
|
|
;; the Free Software Foundation; either version 3, or (at your option) |
|
|
;; any later version. |
|
|
|
|
|
;; This file is distributed in the hope that it will be useful, |
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
;; GNU General Public License for more details. |
|
|
|
|
|
;; You should have received a copy of the GNU General Public License |
|
|
;; along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
|
|
;;; Commentary: |
|
|
|
|
|
;; Purpose: |
|
|
;; |
|
|
;; To support automatic indentation of Haskell programs using |
|
|
;; the layout rule described in section 1.5 and appendix B.3 of the |
|
|
;; the Haskell report. The rationale and the implementation principles |
|
|
;; are described in an article to appear in Journal of Functional Programming. |
|
|
;; "Dynamic tabbing for automatic indentation with the layout rule" |
|
|
;; |
|
|
;; It supports literate scripts. |
|
|
;; Haskell indentation is performed |
|
|
;; within \begin{code}...\end{code} sections of a literate script |
|
|
;; and in lines beginning with > with Bird style literate script |
|
|
;; TAB aligns to the left column outside of these sections. |
|
|
;; |
|
|
;; Installation: |
|
|
;; |
|
|
;; To turn indentation on for all Haskell buffers under the Haskell |
|
|
;; mode of Moss&Thorn <http://www.haskell.org/haskell-mode/> |
|
|
;; add this to .emacs: |
|
|
;; |
|
|
;; (add-hook 'haskell-mode-hook 'turn-on-haskell-indent) |
|
|
;; |
|
|
;; Otherwise, call `turn-on-haskell-indent'. |
|
|
;; |
|
|
;; |
|
|
;; Customisation: |
|
|
;; The "standard" offset for statements is 4 spaces. |
|
|
;; It can be changed by setting the variable "haskell-indent-offset" to |
|
|
;; another value |
|
|
;; |
|
|
;; The default number of blanks after > in a Bird style literate script |
|
|
;; is 1; it can be changed by setting the variable |
|
|
;; "haskell-indent-literate-Bird-default-offset" |
|
|
;; |
|
|
;; `haskell-indent-hook' is invoked if not nil. |
|
|
;; |
|
|
;; All functions/variables start with |
|
|
;; `(turn-(on/off)-)haskell-indent' or `haskell-indent-'. |
|
|
|
|
|
;; This file can also be used as a hook for the Hugs Mode developed by |
|
|
;; Chris Van Humbeeck <chris.vanhumbeeck@cs.kuleuven.ac.be> |
|
|
;; It can be obtained at: |
|
|
;; http://www-i2.informatik.rwth-aachen.de/Forschung/FP/Haskell/hugs-mode.el |
|
|
;; |
|
|
;; For the Hugs mode put the following in your .emacs |
|
|
;; |
|
|
;;(setq auto-mode-alist (append auto-mode-alist '(("\\.hs\\'" . hugs-mode)))) |
|
|
;;(autoload 'hugs-mode "hugs-mode" "Go into hugs mode" t) |
|
|
;; |
|
|
;; If only the indentation mode is used then replace the two |
|
|
;; preceding lines with |
|
|
;;(setq auto-mode-alist (append auto-mode-alist |
|
|
;; '(("\\.hs\\'" . turn-on-haskell-indent)))) |
|
|
;;(autoload 'turn-on-haskell-indent "hindent" "Indentation mode for Haskell" t) |
|
|
;; |
|
|
;; For indentation in both cases then add the following to your .emacs |
|
|
;;(add-hook 'hugs-mode-hook 'turn-on-haskell-indent) |
|
|
;;(autoload 'haskell-indent-cycle "hindent" "Indentation cycle for Haskell" t) |
|
|
;; |
|
|
|
|
|
;;; Code: |
|
|
|
|
|
(require 'cl-lib) |
|
|
(require 'haskell-string) |
|
|
|
|
|
(defvar haskell-literate) |
|
|
|
|
|
;;;###autoload |
|
|
(defgroup haskell-indent nil |
|
|
"Haskell indentation." |
|
|
:group 'haskell |
|
|
:link '(custom-manual "(haskell-mode)Indentation") |
|
|
:prefix "haskell-indent-") |
|
|
|
|
|
;;;###autoload |
|
|
(defcustom haskell-indent-offset 4 |
|
|
"Indentation of Haskell statements with respect to containing block." |
|
|
:type 'integer |
|
|
:safe #'natnump |
|
|
:group 'haskell-indent) |
|
|
|
|
|
;;;###autoload |
|
|
(defcustom haskell-indent-literate-Bird-default-offset 1 |
|
|
"Default number of blanks after > in a Bird style literate script." |
|
|
:type 'integer |
|
|
:safe #'natnump |
|
|
:group 'haskell-indent) |
|
|
|
|
|
;;;###autoload |
|
|
(defcustom haskell-indent-rhs-align-column 0 |
|
|
"Column on which to align right-hand sides (use 0 for ad-hoc alignment)." |
|
|
:type 'integer |
|
|
:safe #'natnump |
|
|
:group 'haskell-indent) |
|
|
|
|
|
(defun haskell-indent-point-to-col (apoint) |
|
|
"Return the column number of APOINT." |
|
|
(save-excursion |
|
|
(goto-char apoint) |
|
|
(current-column))) |
|
|
|
|
|
(defconst haskell-indent-start-keywords-re |
|
|
(concat "\\<" |
|
|
(regexp-opt '("class" "data" "import" "infix" "infixl" "infixr" |
|
|
"instance" "module" "newtype" "primitive" "type") t) |
|
|
"\\>") |
|
|
"Regexp for keywords to complete when standing at the first word of a line.") |
|
|
|
|
|
|
|
|
;; Customizations for different kinds of environments |
|
|
;; in which dealing with low-level events are different. |
|
|
(defun haskell-indent-mark-active () |
|
|
(if (featurep 'xemacs) |
|
|
(if zmacs-regions |
|
|
zmacs-region-active-p |
|
|
t) |
|
|
mark-active)) |
|
|
|
|
|
;; for pushing indentation information |
|
|
|
|
|
(defvar haskell-indent-info) ;Used with dynamic scoping. |
|
|
|
|
|
(defun haskell-indent-push-col (col &optional name) |
|
|
"Push indentation information for the column COL. |
|
|
The info is followed by NAME (if present). |
|
|
Makes sure that the same indentation info is not pushed twice. |
|
|
Uses free var `haskell-indent-info'." |
|
|
(let ((tmp (cons col name))) |
|
|
(if (member tmp haskell-indent-info) |
|
|
haskell-indent-info |
|
|
(push tmp haskell-indent-info)))) |
|
|
|
|
|
(defun haskell-indent-push-pos (pos &optional name) |
|
|
"Push indentation information for POS followed by NAME (if present)." |
|
|
(haskell-indent-push-col (haskell-indent-point-to-col pos) name)) |
|
|
|
|
|
;; (defvar haskell-indent-tab-align nil |
|
|
;; "Align all indentations on TAB stops.") |
|
|
|
|
|
(defun haskell-indent-column+offset (column offset) |
|
|
(unless offset (setq offset haskell-indent-offset)) |
|
|
(setq column (+ column offset)) |
|
|
;; (if (and haskell-indent-tab-align (> offset 0)) |
|
|
;; (* 8 (/ (+ column 7) 8)) |
|
|
column) ;; ) |
|
|
|
|
|
(defun haskell-indent-push-pos-offset (pos &optional offset) |
|
|
"Pushes indentation information for the column corresponding to POS |
|
|
followed by an OFFSET (if present use its value otherwise use |
|
|
`haskell-indent-offset')." |
|
|
(haskell-indent-push-col (haskell-indent-column+offset |
|
|
(haskell-indent-point-to-col pos) |
|
|
offset))) |
|
|
|
|
|
;; redefinition of some Emacs function for dealing with |
|
|
;; Bird Style literate scripts |
|
|
|
|
|
(defun haskell-indent-bolp () |
|
|
"`bolp' but dealing with Bird-style literate scripts." |
|
|
(or (bolp) |
|
|
(and (eq haskell-literate 'bird) |
|
|
(<= (current-column) (1+ haskell-indent-literate-Bird-default-offset)) |
|
|
(eq (char-after (line-beginning-position)) ?\>)))) |
|
|
|
|
|
(defun haskell-indent-empty-line-p () |
|
|
"Checks if the current line is empty; deals with Bird style scripts." |
|
|
(save-excursion |
|
|
(beginning-of-line) |
|
|
(if (and (eq haskell-literate 'bird) |
|
|
(eq (following-char) ?\>)) |
|
|
(forward-char 1)) |
|
|
(looking-at "[ \t]*$"))) |
|
|
|
|
|
(defun haskell-indent-back-to-indentation () |
|
|
"`back-to-indentation' function but dealing with Bird-style literate scripts." |
|
|
(if (and (eq haskell-literate 'bird) |
|
|
(progn (beginning-of-line) (eq (following-char) ?\>))) |
|
|
(progn |
|
|
(forward-char 1) |
|
|
(skip-chars-forward " \t")) |
|
|
(back-to-indentation))) |
|
|
|
|
|
(defun haskell-indent-current-indentation () |
|
|
"`current-indentation' function dealing with Bird-style literate scripts." |
|
|
(if (eq haskell-literate 'bird) |
|
|
(save-excursion |
|
|
(haskell-indent-back-to-indentation) |
|
|
(current-column)) |
|
|
(current-indentation))) |
|
|
|
|
|
(defun haskell-indent-backward-to-indentation (n) |
|
|
"`backward-to-indentation' function dealing with Bird-style literate scripts." |
|
|
(if (eq haskell-literate 'bird) |
|
|
(progn |
|
|
(forward-line (- n)) |
|
|
(haskell-indent-back-to-indentation)) |
|
|
(backward-to-indentation n))) |
|
|
|
|
|
(defun haskell-indent-forward-line (&optional n) |
|
|
"`forward-line' function but dealing with Bird-style literate scripts." |
|
|
(prog1 |
|
|
(forward-line n) |
|
|
(if (and (eq haskell-literate 'bird) (eq (following-char) ?\>)) |
|
|
(progn (forward-char 1) ; skip > and initial blanks... |
|
|
(skip-chars-forward " \t"))))) |
|
|
|
|
|
(defun haskell-indent-line-to (n) |
|
|
"`indent-line-to' function but dealing with Bird-style literate scripts." |
|
|
(if (eq haskell-literate 'bird) |
|
|
(progn |
|
|
(beginning-of-line) |
|
|
(if (eq (following-char) ?\>) |
|
|
(delete-char 1)) |
|
|
(delete-horizontal-space) ; remove any starting TABs so |
|
|
(indent-line-to n) ; that indent-line only adds spaces |
|
|
(save-excursion |
|
|
(beginning-of-line) |
|
|
(if (> n 0) (delete-char 1)) ; delete the first space before |
|
|
(insert ?\>))) ; inserting a > |
|
|
(indent-line-to n))) |
|
|
|
|
|
(defun haskell-indent-skip-blanks-and-newlines-forward (end) |
|
|
"Skip forward blanks, tabs and newlines until END. |
|
|
Take account of Bird-style literate scripts." |
|
|
(skip-chars-forward " \t\n" end) |
|
|
(if (eq haskell-literate 'bird) |
|
|
(while (and (bolp) (eq (following-char) ?\>)) |
|
|
(forward-char 1) ; skip > |
|
|
(skip-chars-forward " \t\n" end)))) |
|
|
|
|
|
(defun haskell-indent-skip-blanks-and-newlines-backward (start) |
|
|
"Skip backward blanks, tabs and newlines up to START. |
|
|
Take account of Bird-style literate scripts." |
|
|
(skip-chars-backward " \t\n" start) |
|
|
(if (eq haskell-literate 'bird) |
|
|
(while (and (eq (current-column) 1) |
|
|
(eq (preceding-char) ?\>)) |
|
|
(forward-char -1) ; skip back > |
|
|
(skip-chars-backward " \t\n" start)))) |
|
|
|
|
|
;; specific functions for literate code |
|
|
|
|
|
(defun haskell-indent-within-literate-code () |
|
|
"Check if point is within a part of literate Haskell code. |
|
|
If so, return its start; otherwise return nil: |
|
|
If it is Bird-style, then return the position of the >; |
|
|
otherwise return the ending position of \\begin{code}." |
|
|
(save-excursion |
|
|
(cl-case haskell-literate |
|
|
(bird |
|
|
(beginning-of-line) |
|
|
(if (or (eq (following-char) ?\>) |
|
|
(and (bolp) (forward-line -1) (eq (following-char) ?\>))) |
|
|
(progn |
|
|
(while (and (zerop (forward-line -1)) |
|
|
(eq (following-char) ?\>))) |
|
|
(if (not (eq (following-char) ?\>)) |
|
|
(forward-line)) |
|
|
(point)))) |
|
|
;; Look for a \begin{code} or \end{code} line. |
|
|
((latex tex) |
|
|
(if (re-search-backward |
|
|
"^\\(\\\\begin{code}$\\)\\|\\(\\\\end{code}$\\)" nil t) |
|
|
;; within a literate code part if it was a \\begin{code}. |
|
|
(match-end 1))) |
|
|
(t (error "haskell-indent-within-literate-code: should not happen!"))))) |
|
|
|
|
|
(defun haskell-indent-put-region-in-literate (beg end &optional arg) |
|
|
"Put lines of the region as a piece of literate code. |
|
|
With prefix arg, remove indication that the region is literate code. |
|
|
It deals with both Bird style and non Bird-style scripts." |
|
|
(interactive "r\nP") |
|
|
(unless haskell-literate |
|
|
(error "Cannot put a region in literate in a non literate script")) |
|
|
(if (eq haskell-literate 'bird) |
|
|
(let ((comment-start "> ") ; Change dynamic bindings for |
|
|
(comment-start-skip "^> ?") ; comment-region. |
|
|
(comment-end "") |
|
|
(comment-end-skip "\n") |
|
|
(comment-style 'plain)) |
|
|
(comment-region beg end arg)) |
|
|
;; Not Bird style. |
|
|
(if arg ; Remove the literate indication. |
|
|
(save-excursion |
|
|
(goto-char end) ; Remove end. |
|
|
(if (re-search-backward "^\\\\end{code}[ \t\n]*\\=" |
|
|
(line-beginning-position -2) t) |
|
|
(delete-region (point) (line-beginning-position 2))) |
|
|
(goto-char beg) ; Remove end. |
|
|
(beginning-of-line) |
|
|
(if (looking-at "\\\\begin{code}") |
|
|
(kill-line 1))) |
|
|
(save-excursion ; Add the literate indication. |
|
|
(goto-char end) |
|
|
(unless (bolp) (newline)) |
|
|
(insert "\\end{code}\n") |
|
|
(goto-char beg) |
|
|
(unless (bolp) (newline)) |
|
|
(insert "\\begin{code}\n"))))) |
|
|
|
|
|
;;; Start of indentation code |
|
|
;;;###autoload |
|
|
(defcustom haskell-indent-look-past-empty-line t |
|
|
"If nil, indentation engine will not look past an empty line for layout points." |
|
|
:group 'haskell-indent |
|
|
:safe #'booleanp |
|
|
:type 'boolean) |
|
|
|
|
|
(defun haskell-indent-start-of-def () |
|
|
"Return the position of the start of a definition. |
|
|
The start of a def is expected to be recognizable by starting in column 0, |
|
|
unless `haskell-indent-look-past-empty-line' is nil, in which case we |
|
|
take a coarser approximation and stop at the first empty line." |
|
|
(save-excursion |
|
|
(let ((start-code (and haskell-literate |
|
|
(haskell-indent-within-literate-code))) |
|
|
(top-col (if (eq haskell-literate 'bird) 2 0)) |
|
|
(save-point (point))) |
|
|
;; determine the starting point of the current piece of code |
|
|
(setq start-code (if start-code (1+ start-code) (point-min))) |
|
|
;; go backward until the first preceding empty line |
|
|
(haskell-indent-forward-line -1) |
|
|
(while (and (if haskell-indent-look-past-empty-line |
|
|
(or (> (haskell-indent-current-indentation) top-col) |
|
|
(haskell-indent-empty-line-p)) |
|
|
(and (> (haskell-indent-current-indentation) top-col) |
|
|
(not (haskell-indent-empty-line-p)))) |
|
|
(> (point) start-code) |
|
|
(= 0 (haskell-indent-forward-line -1)))) |
|
|
;; go forward after the empty line |
|
|
(if (haskell-indent-empty-line-p) |
|
|
(haskell-indent-forward-line 1)) |
|
|
(setq start-code (point)) |
|
|
;; find the first line of code which is not a comment |
|
|
(forward-comment (point-max)) |
|
|
(if (> (point) save-point) |
|
|
start-code |
|
|
(point))))) |
|
|
|
|
|
(defun haskell-indent-open-structure (start end) |
|
|
"If any structure (list or tuple) is not closed, between START and END, |
|
|
returns the location of the opening symbol, nil otherwise." |
|
|
(save-excursion |
|
|
(nth 1 (parse-partial-sexp start end)))) |
|
|
|
|
|
(defun haskell-indent-in-string (start end) |
|
|
"If a string is not closed , between START and END, returns the |
|
|
location of the opening symbol, nil otherwise." |
|
|
(save-excursion |
|
|
(let ((pps (parse-partial-sexp start end))) |
|
|
(if (nth 3 pps) (nth 8 pps))))) |
|
|
|
|
|
(defun haskell-indent-in-comment (start end) |
|
|
"Check, starting from START, if END is at or within a comment. |
|
|
Returns the location of the start of the comment, nil otherwise." |
|
|
(let (pps) |
|
|
(cl-assert (<= start end)) |
|
|
(cond ((= start end) nil) |
|
|
((nth 4 (save-excursion (setq pps (parse-partial-sexp start end)))) |
|
|
(nth 8 pps)) |
|
|
;; We also want to say that we are *at* the beginning of a comment. |
|
|
((and (not (nth 8 pps)) |
|
|
(>= (point-max) (+ end 2)) |
|
|
(nth 4 (save-excursion |
|
|
(setq pps (parse-partial-sexp end (+ end 2)))))) |
|
|
(nth 8 pps))))) |
|
|
|
|
|
(defvar haskell-indent-off-side-keywords-re |
|
|
"\\<\\(do\\|let\\|of\\|where\\|mdo\\|rec\\)\\>[ \t]*") |
|
|
|
|
|
(defun haskell-indent-type-at-point () |
|
|
"Return the type of the line (also puts information in `match-data')." |
|
|
(cond |
|
|
((haskell-indent-empty-line-p) 'empty) |
|
|
((haskell-indent-in-comment (point-min) (point)) 'comment) |
|
|
((looking-at "\\(\\([[:alpha:]]\\(\\sw\\|'\\)*\\)\\|_\\)[ \t\n]*") |
|
|
'ident) |
|
|
((looking-at "\\(|[^|]\\)[ \t\n]*") 'guard) |
|
|
((looking-at "\\(=[^>=]\\|::\\|∷\\|→\\|←\\|->\\|<-\\)[ \t\n]*") 'rhs) |
|
|
(t 'other))) |
|
|
|
|
|
(defvar haskell-indent-current-line-first-ident "" |
|
|
"Global variable that keeps track of the first ident of the line to indent.") |
|
|
|
|
|
|
|
|
(defun haskell-indent-contour-line (start end) |
|
|
"Generate contour information between START and END points." |
|
|
(if (< start end) |
|
|
(save-excursion |
|
|
(goto-char end) |
|
|
(haskell-indent-skip-blanks-and-newlines-backward start) |
|
|
(let ((cur-col (current-column)) ; maximum column number |
|
|
(fl 0) ; number of lines that forward-line could not advance |
|
|
contour) |
|
|
(while (and (> cur-col 0) (= fl 0) (>= (point) start)) |
|
|
(haskell-indent-back-to-indentation) |
|
|
(if (< (point) start) (goto-char start)) |
|
|
(and (not (member (haskell-indent-type-at-point) |
|
|
'(empty comment))) ; skip empty and comment lines |
|
|
(< (current-column) cur-col) ; less indented column found |
|
|
(push (point) contour) ; new contour point found |
|
|
(setq cur-col (current-column))) |
|
|
(setq fl (haskell-indent-forward-line -1))) |
|
|
contour)))) |
|
|
|
|
|
(defun haskell-indent-next-symbol (end) |
|
|
"Move point to the next symbol." |
|
|
(skip-syntax-forward ")" end) |
|
|
(if (< (point) end) |
|
|
(progn |
|
|
(forward-sexp 1) |
|
|
(haskell-indent-skip-blanks-and-newlines-forward end)))) |
|
|
|
|
|
(defun haskell-indent-next-symbol-safe (end) |
|
|
"Puts point to the next following symbol, or to end if there are no more symbols in the sexp." |
|
|
(condition-case _errlist (haskell-indent-next-symbol end) |
|
|
(error (goto-char end)))) |
|
|
|
|
|
(defun haskell-indent-separate-valdef (start end) |
|
|
"Return a list of positions for important parts of a valdef." |
|
|
(save-excursion |
|
|
(let (valname valname-string aft-valname |
|
|
guard aft-guard |
|
|
rhs-sign aft-rhs-sign |
|
|
type) |
|
|
;; "parse" a valdef separating important parts |
|
|
(goto-char start) |
|
|
(setq type (haskell-indent-type-at-point)) |
|
|
(if (or (memq type '(ident other))) ; possible start of a value def |
|
|
(progn |
|
|
(if (eq type 'ident) |
|
|
(progn |
|
|
(setq valname (match-beginning 0)) |
|
|
(setq valname-string (match-string 0)) |
|
|
(goto-char (match-end 0))) |
|
|
(skip-chars-forward " \t" end) |
|
|
(setq valname (point)) ; type = other |
|
|
(haskell-indent-next-symbol-safe end)) |
|
|
(while (and (< (point) end) |
|
|
(setq type (haskell-indent-type-at-point)) |
|
|
(or (memq type '(ident other)))) |
|
|
(if (null aft-valname) |
|
|
(setq aft-valname (point))) |
|
|
(haskell-indent-next-symbol-safe end)))) |
|
|
(if (and (< (point) end) (eq type 'guard)) ; start of a guard |
|
|
(progn |
|
|
(setq guard (match-beginning 0)) |
|
|
(goto-char (match-end 0)) |
|
|
(while (and (< (point) end) |
|
|
(setq type (haskell-indent-type-at-point)) |
|
|
(not (eq type 'rhs))) |
|
|
(if (null aft-guard) |
|
|
(setq aft-guard (point))) |
|
|
(haskell-indent-next-symbol-safe end)))) |
|
|
(if (and (< (point) end) (eq type 'rhs)) ; start of a rhs |
|
|
(progn |
|
|
(setq rhs-sign (match-beginning 0)) |
|
|
(goto-char (match-end 0)) |
|
|
(if (< (point) end) |
|
|
(setq aft-rhs-sign (point))))) |
|
|
(list valname valname-string aft-valname |
|
|
guard aft-guard rhs-sign aft-rhs-sign)))) |
|
|
|
|
|
(defsubst haskell-indent-no-otherwise (guard) |
|
|
"Check if there is no otherwise at GUARD." |
|
|
(save-excursion |
|
|
(goto-char guard) |
|
|
(not (looking-at "|[ \t]*otherwise\\>")))) |
|
|
|
|
|
|
|
|
(defun haskell-indent-guard (start end end-visible indent-info) |
|
|
"Find indentation information for a line starting with a guard." |
|
|
(save-excursion |
|
|
(let* ((haskell-indent-info indent-info) |
|
|
(sep (haskell-indent-separate-valdef start end)) |
|
|
(valname (nth 0 sep)) |
|
|
(guard (nth 3 sep)) |
|
|
(rhs-sign (nth 5 sep))) |
|
|
;; push information indentation for the visible part |
|
|
(if (and guard (< guard end-visible) (haskell-indent-no-otherwise guard)) |
|
|
(haskell-indent-push-pos guard) |
|
|
(if rhs-sign |
|
|
(haskell-indent-push-pos rhs-sign) ; probably within a data definition... |
|
|
(if valname |
|
|
(haskell-indent-push-pos-offset valname)))) |
|
|
haskell-indent-info))) |
|
|
|
|
|
(defun haskell-indent-rhs (start end end-visible indent-info) |
|
|
"Find indentation information for a line starting with a rhs." |
|
|
(save-excursion |
|
|
(let* ((haskell-indent-info indent-info) |
|
|
(sep (haskell-indent-separate-valdef start end)) |
|
|
(valname (nth 0 sep)) |
|
|
(guard (nth 3 sep)) |
|
|
(rhs-sign (nth 5 sep))) |
|
|
;; push information indentation for the visible part |
|
|
(if (and rhs-sign (< rhs-sign end-visible)) |
|
|
(haskell-indent-push-pos rhs-sign) |
|
|
(if (and guard (< guard end-visible)) |
|
|
(haskell-indent-push-pos-offset guard) |
|
|
(if valname ; always visible !! |
|
|
(haskell-indent-push-pos-offset valname)))) |
|
|
haskell-indent-info))) |
|
|
|
|
|
(defconst haskell-indent-decision-table |
|
|
(let ((or "\\)\\|\\(")) |
|
|
(concat "\\(" |
|
|
"1.1.11" or ; 1= vn gd rh arh |
|
|
"1.1.10" or ; 2= vn gd rh |
|
|
"1.1100" or ; 3= vn gd agd |
|
|
"1.1000" or ; 4= vn gd |
|
|
"1.0011" or ; 5= vn rh arh |
|
|
"1.0010" or ; 6= vn rh |
|
|
"110000" or ; 7= vn avn |
|
|
"100000" or ; 8= vn |
|
|
"001.11" or ; 9= gd rh arh |
|
|
"001.10" or ;10= gd rh |
|
|
"001100" or ;11= gd agd |
|
|
"001000" or ;12= gd |
|
|
"000011" or ;13= rh arh |
|
|
"000010" or ;14= rh |
|
|
"000000" ;15= |
|
|
"\\)"))) |
|
|
|
|
|
(defun haskell-indent-find-case (test) |
|
|
"Find the index that matches TEST in the decision table." |
|
|
(if (string-match haskell-indent-decision-table test) |
|
|
;; use the fact that the resulting match-data is a list of the form |
|
|
;; (0 6 [2*(n-1) nil] 0 6) where n is the number of the matching regexp |
|
|
;; so n= ((length match-data)/2)-1 |
|
|
(- (/ (length (match-data 'integers)) 2) 1) |
|
|
(error "haskell-indent-find-case: impossible case: %s" test))) |
|
|
|
|
|
(defun haskell-indent-empty (start end end-visible indent-info) |
|
|
"Find indentation points for an empty line." |
|
|
(save-excursion |
|
|
(let* ((haskell-indent-info indent-info) |
|
|
(sep (haskell-indent-separate-valdef start end)) |
|
|
(valname (pop sep)) |
|
|
(valname-string (pop sep)) |
|
|
(aft-valname (pop sep)) |
|
|
(guard (pop sep)) |
|
|
(aft-guard (pop sep)) |
|
|
(rhs-sign (pop sep)) |
|
|
(aft-rhs-sign (pop sep)) |
|
|
(last-line (= end end-visible)) |
|
|
(test (string |
|
|
(if valname ?1 ?0) |
|
|
(if (and aft-valname (< aft-valname end-visible)) ?1 ?0) |
|
|
(if (and guard (< guard end-visible)) ?1 ?0) |
|
|
(if (and aft-guard (< aft-guard end-visible)) ?1 ?0) |
|
|
(if (and rhs-sign (< rhs-sign end-visible)) ?1 ?0) |
|
|
(if (and aft-rhs-sign (< aft-rhs-sign end-visible)) ?1 ?0)))) |
|
|
(if (and valname-string ; special case for start keywords |
|
|
(string-match haskell-indent-start-keywords-re valname-string)) |
|
|
(progn |
|
|
(haskell-indent-push-pos valname) |
|
|
;; very special for data keyword |
|
|
(if (string-match "\\<data\\>" valname-string) |
|
|
(if rhs-sign (haskell-indent-push-pos rhs-sign) |
|
|
(haskell-indent-push-pos-offset valname)) |
|
|
(haskell-indent-push-pos-offset valname))) |
|
|
(cl-case ; general case |
|
|
(haskell-indent-find-case test) |
|
|
;; "1.1.11" 1= vn gd rh arh |
|
|
(1 (haskell-indent-push-pos valname) |
|
|
(haskell-indent-push-pos valname valname-string) |
|
|
(if (haskell-indent-no-otherwise guard) (haskell-indent-push-pos guard "| ")) |
|
|
(haskell-indent-push-pos aft-rhs-sign)) |
|
|
;; "1.1.10" 2= vn gd rh |
|
|
(2 (haskell-indent-push-pos valname) |
|
|
(haskell-indent-push-pos valname valname-string) |
|
|
(if last-line |
|
|
(haskell-indent-push-pos-offset guard) |
|
|
(if (haskell-indent-no-otherwise guard) (haskell-indent-push-pos guard "| ")))) |
|
|
;; "1.1100" 3= vn gd agd |
|
|
(3 (haskell-indent-push-pos valname) |
|
|
(haskell-indent-push-pos aft-guard) |
|
|
(if last-line (haskell-indent-push-pos-offset valname))) |
|
|
;; "1.1000" 4= vn gd |
|
|
(4 (haskell-indent-push-pos valname) |
|
|
(if last-line (haskell-indent-push-pos-offset guard 2))) |
|
|
;; "1.0011" 5= vn rh arh |
|
|
(5 (haskell-indent-push-pos valname) |
|
|
(if (or (and aft-valname (= (char-after rhs-sign) ?\=)) |
|
|
(= (char-after rhs-sign) ?\:)) |
|
|
(haskell-indent-push-pos valname valname-string)) |
|
|
(haskell-indent-push-pos aft-rhs-sign)) |
|
|
;; "1.0010" 6= vn rh |
|
|
(6 (haskell-indent-push-pos valname) |
|
|
(haskell-indent-push-pos valname valname-string) |
|
|
(if last-line (haskell-indent-push-pos-offset valname))) |
|
|
;; "110000" 7= vn avn |
|
|
(7 (haskell-indent-push-pos valname) |
|
|
(if last-line |
|
|
(haskell-indent-push-pos aft-valname) |
|
|
(haskell-indent-push-pos valname valname-string))) |
|
|
;; "100000" 8= vn |
|
|
(8 (haskell-indent-push-pos valname)) |
|
|
;; "001.11" 9= gd rh arh |
|
|
(9 (if (haskell-indent-no-otherwise guard) (haskell-indent-push-pos guard "| ")) |
|
|
(haskell-indent-push-pos aft-rhs-sign)) |
|
|
;; "001.10" 10= gd rh |
|
|
(10 (if (haskell-indent-no-otherwise guard) (haskell-indent-push-pos guard "| ")) |
|
|
(if last-line (haskell-indent-push-pos-offset guard))) |
|
|
;; "001100" 11= gd agd |
|
|
(11 (if (haskell-indent-no-otherwise guard) (haskell-indent-push-pos guard "| ")) |
|
|
(haskell-indent-push-pos aft-guard)) |
|
|
;; "001000" 12= gd |
|
|
(12 (if (haskell-indent-no-otherwise guard) (haskell-indent-push-pos guard "| ")) |
|
|
(if last-line (haskell-indent-push-pos-offset guard 2))) |
|
|
;; "000011" 13= rh arh |
|
|
(13 (haskell-indent-push-pos aft-rhs-sign)) |
|
|
;; "000010" 14= rh |
|
|
(14 (if last-line (haskell-indent-push-pos-offset rhs-sign 2 ))) |
|
|
;; "000000" 15= |
|
|
(t (error "haskell-indent-empty: %s impossible case" test )))) |
|
|
haskell-indent-info))) |
|
|
|
|
|
(defun haskell-indent-ident (start end end-visible indent-info) |
|
|
"Find indentation points for a line starting with an identifier." |
|
|
(save-excursion |
|
|
(let* |
|
|
((haskell-indent-info indent-info) |
|
|
(sep (haskell-indent-separate-valdef start end)) |
|
|
(valname (pop sep)) |
|
|
(valname-string (pop sep)) |
|
|
(aft-valname (pop sep)) |
|
|
(guard (pop sep)) |
|
|
(aft-guard (pop sep)) |
|
|
(rhs-sign (pop sep)) |
|
|
(aft-rhs-sign (pop sep)) |
|
|
(last-line (= end end-visible)) |
|
|
(is-where |
|
|
(string-match "where[ \t]*" haskell-indent-current-line-first-ident)) |
|
|
(diff-first ; not a function def with the same name |
|
|
(or (null valname-string) |
|
|
(not (string= (haskell-string-trim valname-string) |
|
|
(haskell-string-trim haskell-indent-current-line-first-ident))))) |
|
|
|
|
|
;; (is-type-def |
|
|
;; (and rhs-sign (eq (char-after rhs-sign) ?\:))) |
|
|
(test (string |
|
|
(if valname ?1 ?0) |
|
|
(if (and aft-valname (< aft-valname end-visible)) ?1 ?0) |
|
|
(if (and guard (< guard end-visible)) ?1 ?0) |
|
|
(if (and aft-guard (< aft-guard end-visible)) ?1 ?0) |
|
|
(if (and rhs-sign (< rhs-sign end-visible)) ?1 ?0) |
|
|
(if (and aft-rhs-sign (< aft-rhs-sign end-visible)) ?1 ?0)))) |
|
|
(if (and valname-string ; special case for start keywords |
|
|
(string-match haskell-indent-start-keywords-re valname-string)) |
|
|
(progn |
|
|
(haskell-indent-push-pos valname) |
|
|
(if (string-match "\\<data\\>" valname-string) |
|
|
;; very special for data keyword |
|
|
(if aft-rhs-sign (haskell-indent-push-pos aft-rhs-sign) |
|
|
(haskell-indent-push-pos-offset valname)) |
|
|
(if (not (string-match |
|
|
haskell-indent-start-keywords-re |
|
|
haskell-indent-current-line-first-ident)) |
|
|
(haskell-indent-push-pos-offset valname)))) |
|
|
(if (string= haskell-indent-current-line-first-ident "::") |
|
|
(if valname (haskell-indent-push-pos valname)) |
|
|
(cl-case ; general case |
|
|
(haskell-indent-find-case test) |
|
|
;; "1.1.11" 1= vn gd rh arh |
|
|
(1 (if is-where |
|
|
(haskell-indent-push-pos guard) |
|
|
(haskell-indent-push-pos valname) |
|
|
(if diff-first (haskell-indent-push-pos aft-rhs-sign)))) |
|
|
;; "1.1.10" 2= vn gd rh |
|
|
(2 (if is-where |
|
|
(haskell-indent-push-pos guard) |
|
|
(haskell-indent-push-pos valname) |
|
|
(if last-line |
|
|
(haskell-indent-push-pos-offset guard)))) |
|
|
;; "1.1100" 3= vn gd agd |
|
|
(3 (if is-where |
|
|
(haskell-indent-push-pos-offset guard) |
|
|
(haskell-indent-push-pos valname) |
|
|
(if diff-first |
|
|
(haskell-indent-push-pos aft-guard)))) |
|
|
;; "1.1000" 4= vn gd |
|
|
(4 (if is-where |
|
|
(haskell-indent-push-pos guard) |
|
|
(haskell-indent-push-pos valname) |
|
|
(if last-line |
|
|
(haskell-indent-push-pos-offset guard 2)))) |
|
|
;; "1.0011" 5= vn rh arh |
|
|
(5 (if is-where |
|
|
(haskell-indent-push-pos-offset valname) |
|
|
(haskell-indent-push-pos valname) |
|
|
(if diff-first |
|
|
(haskell-indent-push-pos aft-rhs-sign)))) |
|
|
;; "1.0010" 6= vn rh |
|
|
(6 (if is-where |
|
|
(haskell-indent-push-pos-offset valname) |
|
|
(haskell-indent-push-pos valname) |
|
|
(if last-line |
|
|
(haskell-indent-push-pos-offset valname)))) |
|
|
;; "110000" 7= vn avn |
|
|
(7 (if is-where |
|
|
(haskell-indent-push-pos-offset valname) |
|
|
(haskell-indent-push-pos valname) |
|
|
(if last-line |
|
|
(haskell-indent-push-pos aft-valname)))) |
|
|
;; "100000" 8= vn |
|
|
(8 (if is-where |
|
|
(haskell-indent-push-pos-offset valname) |
|
|
(haskell-indent-push-pos valname))) |
|
|
;; "001.11" 9= gd rh arh |
|
|
(9 (if is-where |
|
|
(haskell-indent-push-pos guard) |
|
|
(haskell-indent-push-pos aft-rhs-sign))) |
|
|
;; "001.10" 10= gd rh |
|
|
(10 (if is-where |
|
|
(haskell-indent-push-pos guard) |
|
|
(if last-line |
|
|
(haskell-indent-push-pos-offset guard)))) |
|
|
;; "001100" 11= gd agd |
|
|
(11 (if is-where |
|
|
(haskell-indent-push-pos guard) |
|
|
(if (haskell-indent-no-otherwise guard) |
|
|
(haskell-indent-push-pos aft-guard)))) |
|
|
;; "001000" 12= gd |
|
|
(12 (if last-line (haskell-indent-push-pos-offset guard 2))) |
|
|
;; "000011" 13= rh arh |
|
|
(13 (haskell-indent-push-pos aft-rhs-sign)) |
|
|
;; "000010" 14= rh |
|
|
(14 (if last-line (haskell-indent-push-pos-offset rhs-sign 2))) |
|
|
;; "000000" 15= |
|
|
(t (error "haskell-indent-ident: %s impossible case" test ))))) |
|
|
haskell-indent-info))) |
|
|
|
|
|
(defun haskell-indent-other (start end end-visible indent-info) |
|
|
"Find indentation points for a non-empty line starting with something other |
|
|
than an identifier, a guard or rhs." |
|
|
(save-excursion |
|
|
(let* ((haskell-indent-info indent-info) |
|
|
(sep (haskell-indent-separate-valdef start end)) |
|
|
(valname (pop sep)) |
|
|
(valname-string (pop sep)) |
|
|
(aft-valname (pop sep)) |
|
|
(guard (pop sep)) |
|
|
(aft-guard (pop sep)) |
|
|
(rhs-sign (pop sep)) |
|
|
(aft-rhs-sign (pop sep)) |
|
|
(last-line (= end end-visible)) |
|
|
(test (string |
|
|
(if valname ?1 ?0) |
|
|
(if (and aft-valname (< aft-valname end-visible)) ?1 ?0) |
|
|
(if (and guard (< guard end-visible)) ?1 ?0) |
|
|
(if (and aft-guard (< aft-guard end-visible)) ?1 ?0) |
|
|
(if (and rhs-sign (< rhs-sign end-visible)) ?1 ?0) |
|
|
(if (and aft-rhs-sign (< aft-rhs-sign end-visible)) ?1 ?0)))) |
|
|
(if (and valname-string ; special case for start keywords |
|
|
(string-match haskell-indent-start-keywords-re valname-string)) |
|
|
(haskell-indent-push-pos-offset valname) |
|
|
(cl-case ; general case |
|
|
(haskell-indent-find-case test) |
|
|
;; "1.1.11" 1= vn gd rh arh |
|
|
(1 (haskell-indent-push-pos aft-rhs-sign)) |
|
|
;; "1.1.10" 2= vn gd rh |
|
|
(2 (if last-line |
|
|
(haskell-indent-push-pos-offset guard) |
|
|
(haskell-indent-push-pos-offset rhs-sign 2))) |
|
|
;; "1.1100" 3= vn gd agd |
|
|
(3 (haskell-indent-push-pos aft-guard)) |
|
|
;; "1.1000" 4= vn gd |
|
|
(4 (haskell-indent-push-pos-offset guard 2)) |
|
|
;; "1.0011" 5= vn rh arh |
|
|
(5 (haskell-indent-push-pos valname) |
|
|
(haskell-indent-push-pos aft-rhs-sign)) |
|
|
;; "1.0010" 6= vn rh |
|
|
(6 (if last-line |
|
|
(haskell-indent-push-pos-offset valname) |
|
|
(haskell-indent-push-pos-offset rhs-sign 2))) |
|
|
;; "110000" 7= vn avn |
|
|
(7 (haskell-indent-push-pos-offset aft-valname)) |
|
|
;; "100000" 8= vn |
|
|
(8 (haskell-indent-push-pos valname)) |
|
|
;; "001.11" 9= gd rh arh |
|
|
(9 (haskell-indent-push-pos aft-rhs-sign)) |
|
|
;; "001.10" 10= gd rh |
|
|
(10 (if last-line |
|
|
(haskell-indent-push-pos-offset guard) |
|
|
(haskell-indent-push-pos-offset rhs-sign 2))) |
|
|
;; "001100" 11= gd agd |
|
|
(11 (if (haskell-indent-no-otherwise guard) |
|
|
(haskell-indent-push-pos aft-guard))) |
|
|
;; "001000" 12= gd |
|
|
(12 (if last-line (haskell-indent-push-pos-offset guard 2))) |
|
|
;; "000011" 13= rh arh |
|
|
(13 (haskell-indent-push-pos aft-rhs-sign)) |
|
|
;; "000010" 14= rh |
|
|
(14 (if last-line (haskell-indent-push-pos-offset rhs-sign 2))) |
|
|
;; "000000" 15= |
|
|
(t (error "haskell-indent-other: %s impossible case" test )))) |
|
|
haskell-indent-info))) |
|
|
|
|
|
(defun haskell-indent-valdef-indentation (start end end-visible curr-line-type |
|
|
indent-info) |
|
|
"Find indentation information for a value definition." |
|
|
(let ((haskell-indent-info indent-info)) |
|
|
(if (< start end-visible) |
|
|
(cl-case curr-line-type |
|
|
(empty (haskell-indent-empty start end end-visible indent-info)) |
|
|
(ident (haskell-indent-ident start end end-visible indent-info)) |
|
|
(guard (haskell-indent-guard start end end-visible indent-info)) |
|
|
(rhs (haskell-indent-rhs start end end-visible indent-info)) |
|
|
(comment (error "Comment indent should never happen")) |
|
|
(other (haskell-indent-other start end end-visible indent-info))) |
|
|
haskell-indent-info))) |
|
|
|
|
|
(defun haskell-indent-line-indentation (line-start line-end end-visible |
|
|
curr-line-type indent-info) |
|
|
"Compute indentation info between LINE-START and END-VISIBLE. |
|
|
Separate a line of program into valdefs between offside keywords |
|
|
and find indentation info for each part." |
|
|
(save-excursion |
|
|
;; point is (already) at line-start |
|
|
(cl-assert (eq (point) line-start)) |
|
|
(let ((haskell-indent-info indent-info) |
|
|
(start (or (haskell-indent-in-comment line-start line-end) |
|
|
(haskell-indent-in-string line-start line-end)))) |
|
|
(if start ; if comment at the end |
|
|
(setq line-end start)) ; end line before it |
|
|
;; loop on all parts separated by off-side-keywords |
|
|
(while (and (re-search-forward haskell-indent-off-side-keywords-re |
|
|
line-end t) |
|
|
(not (or (haskell-indent-in-comment line-start (point)) |
|
|
(haskell-indent-in-string line-start (point))))) |
|
|
(let ((beg-match (match-beginning 0)) ; save beginning of match |
|
|
(end-match (match-end 0))) ; save end of match |
|
|
;; Do not try to find indentation points if off-side-keyword at |
|
|
;; the start... |
|
|
(if (or (< line-start beg-match) |
|
|
;; Actually, if we're looking at a "let" inside a "do", we |
|
|
;; should add the corresponding indentation point. |
|
|
(eq (char-after beg-match) ?l)) |
|
|
(setq haskell-indent-info |
|
|
(haskell-indent-valdef-indentation line-start beg-match |
|
|
end-visible |
|
|
curr-line-type |
|
|
haskell-indent-info))) |
|
|
;; ...but keep the start of the line if keyword alone on the line |
|
|
(if (= line-end end-match) |
|
|
(haskell-indent-push-pos beg-match)) |
|
|
(setq line-start end-match) |
|
|
(goto-char line-start))) |
|
|
(haskell-indent-valdef-indentation line-start line-end end-visible |
|
|
curr-line-type haskell-indent-info)))) |
|
|
|
|
|
|
|
|
(defun haskell-indent-layout-indent-info (start contour-line) |
|
|
(let ((haskell-indent-info nil) |
|
|
(curr-line-type (haskell-indent-type-at-point)) |
|
|
line-start line-end end-visible) |
|
|
(save-excursion |
|
|
(if (eq curr-line-type 'ident) |
|
|
(let ; guess the type of line |
|
|
((sep |
|
|
(haskell-indent-separate-valdef |
|
|
(point) (line-end-position)))) |
|
|
;; if the first ident is where or the start of a def |
|
|
;; keep it in a global variable |
|
|
(setq haskell-indent-current-line-first-ident |
|
|
(if (string-match "where[ \t]*" (nth 1 sep)) |
|
|
(nth 1 sep) |
|
|
(if (nth 5 sep) ; is there a rhs-sign |
|
|
(if (= (char-after (nth 5 sep)) ?\:) ;is it a typdef |
|
|
"::" (nth 1 sep)) |
|
|
""))))) |
|
|
(while contour-line ; explore the contour points |
|
|
(setq line-start (pop contour-line)) |
|
|
(goto-char line-start) |
|
|
(setq line-end (line-end-position)) |
|
|
(setq end-visible ; visible until the column of the |
|
|
(if contour-line ; next contour point |
|
|
(save-excursion |
|
|
(move-to-column |
|
|
(haskell-indent-point-to-col (car contour-line))) |
|
|
(point)) |
|
|
line-end)) |
|
|
(unless (or (haskell-indent-open-structure start line-start) |
|
|
(haskell-indent-in-comment start line-start)) |
|
|
(setq haskell-indent-info |
|
|
(haskell-indent-line-indentation line-start line-end |
|
|
end-visible curr-line-type |
|
|
haskell-indent-info))))) |
|
|
haskell-indent-info)) |
|
|
|
|
|
(defun haskell-indent-find-matching-start (regexp limit &optional pred start) |
|
|
(let ((open (haskell-indent-open-structure limit (point)))) |
|
|
(if open (setq limit (1+ open)))) |
|
|
(unless start (setq start (point))) |
|
|
(when (re-search-backward regexp limit t) |
|
|
(let ((nestedcase (match-end 1)) |
|
|
(outer (or (haskell-indent-in-string limit (point)) |
|
|
(haskell-indent-in-comment limit (point)) |
|
|
(haskell-indent-open-structure limit (point)) |
|
|
(if (and pred (funcall pred start)) (point))))) |
|
|
(cond |
|
|
(outer |
|
|
(goto-char outer) |
|
|
(haskell-indent-find-matching-start regexp limit pred start)) |
|
|
(nestedcase |
|
|
;; Nested case. |
|
|
(and (haskell-indent-find-matching-start regexp limit pred) |
|
|
(haskell-indent-find-matching-start regexp limit pred start))) |
|
|
(t (point)))))) |
|
|
|
|
|
(defun haskell-indent-filter-let-no-in (start) |
|
|
"Return non-nil if point is in front of a `let' that has no `in'. |
|
|
START is the position of the presumed `in'." |
|
|
;; We're looking at either `in' or `let'. |
|
|
(when (looking-at "let") |
|
|
(ignore-errors |
|
|
(save-excursion |
|
|
(forward-word 1) |
|
|
(forward-comment (point-max)) |
|
|
(if (looking-at "{") |
|
|
(progn |
|
|
(forward-sexp 1) |
|
|
(forward-comment (point-max)) |
|
|
(< (point) start)) |
|
|
;; Use the layout rule to see whether this let is already closed |
|
|
;; without an `in'. |
|
|
(let ((col (current-column))) |
|
|
(while (progn (forward-line 1) (haskell-indent-back-to-indentation) |
|
|
(< (point) start)) |
|
|
(when (< (current-column) col) |
|
|
(setq col nil) |
|
|
(goto-char start))) |
|
|
(null col))))))) |
|
|
|
|
|
(defun haskell-indent-comment (open start) |
|
|
"Compute indent info for comments and text inside comments. |
|
|
OPEN is the start position of the comment in which point is." |
|
|
;; Ideally we'd want to guess whether it's commented out code or |
|
|
;; whether it's text. Instead, we'll assume it's text. |
|
|
(save-excursion |
|
|
(if (= open (point)) |
|
|
;; We're actually just in front of a comment: align with following |
|
|
;; code or with comment on previous line. |
|
|
(let ((prev-line-info |
|
|
(cond |
|
|
((eq (char-after) ?\{) nil) ;Align as if it were code. |
|
|
((and (forward-comment -1) |
|
|
(> (line-beginning-position 3) open)) |
|
|
;; We're after another comment and there's no empty line |
|
|
;; between us. |
|
|
(list (list (haskell-indent-point-to-col (point))))) |
|
|
(t nil)))) ;Else align as if it were code |
|
|
;; Align with following code. |
|
|
(forward-comment (point-max)) |
|
|
;; There are several possible indentation points for this code-line, |
|
|
;; but the only valid indentation point for the comment is the one |
|
|
;; that the user will select for the code-line. Obviously we can't |
|
|
;; know that, so we just assume that the code-line is already at its |
|
|
;; proper place. |
|
|
;; Strictly speaking "assume it's at its proper place" would mean |
|
|
;; we'd just use (current-column), but since this is using info from |
|
|
;; lines further down and it's common to reindent line-by-line, |
|
|
;; we'll align not with the current indentation, but with the |
|
|
;; one that auto-indentation "will" select. |
|
|
(append |
|
|
prev-line-info |
|
|
(let ((indent-info (save-excursion |
|
|
(haskell-indent-indentation-info start))) |
|
|
(col (current-column))) |
|
|
;; Sort the indent-info so that the current indentation comes |
|
|
;; out first. |
|
|
(setq indent-info |
|
|
(sort indent-info |
|
|
(lambda (x y) |
|
|
(<= (abs (- col (car x))) (abs (- col (car y))))))) |
|
|
indent-info))) |
|
|
|
|
|
;; We really are inside a comment. |
|
|
(if (looking-at "-}") |
|
|
(progn |
|
|
(forward-char 2) |
|
|
(forward-comment -1) |
|
|
(list (list (1+ (haskell-indent-point-to-col (point)))))) |
|
|
(let ((offset (if (looking-at "--?") |
|
|
(- (match-beginning 0) (match-end 0))))) |
|
|
(forward-line -1) ;Go to previous line. |
|
|
(haskell-indent-back-to-indentation) |
|
|
(if (< (point) start) (goto-char start)) |
|
|
|
|
|
(list (list (if (looking-at comment-start-skip) |
|
|
(if offset |
|
|
(+ 2 offset (haskell-indent-point-to-col (point))) |
|
|
(haskell-indent-point-to-col (match-end 0))) |
|
|
(haskell-indent-point-to-col (point)))))))))) |
|
|
|
|
|
;;;###autoload |
|
|
(defcustom haskell-indent-thenelse 0 |
|
|
"If non-nil, \"then\" and \"else\" are indented. |
|
|
This is necessary in the \"do\" layout under Haskell-98. |
|
|
See http://hackage.haskell.org/trac/haskell-prime/wiki/DoAndIfThenElse" |
|
|
:group 'haskell-indent |
|
|
:safe #'booleanp |
|
|
:type 'integer) |
|
|
|
|
|
(defun haskell-indent-closing-keyword (start) |
|
|
(let ((open (save-excursion |
|
|
(haskell-indent-find-matching-start |
|
|
(cl-case (char-after) |
|
|
(?i "\\<\\(?:\\(in\\)\\|let\\)\\>") |
|
|
(?o "\\<\\(?:\\(of\\)\\|case\\)\\>") |
|
|
(?t "\\<\\(?:\\(then\\)\\|if\\)\\>") |
|
|
(?e "\\<\\(?:\\(else\\)\\|if\\)\\>")) |
|
|
start |
|
|
(if (eq (char-after) ?i) |
|
|
;; Filter out the `let's that have no `in'. |
|
|
'haskell-indent-filter-let-no-in))))) |
|
|
;; For a "hanging let/case/if at EOL" we should use a different |
|
|
;; indentation scheme. |
|
|
(save-excursion |
|
|
(goto-char open) |
|
|
(if (haskell-indent-hanging-p) |
|
|
(setq open (haskell-indent-virtual-indentation start)))) |
|
|
;; FIXME: we should try and figure out if the `if' is in a `do' layout |
|
|
;; before using haskell-indent-thenelse. |
|
|
(list (list (+ (if (memq (char-after) '(?t ?e)) haskell-indent-thenelse 0) |
|
|
(haskell-indent-point-to-col open)))))) |
|
|
|
|
|
;;;###autoload |
|
|
(defcustom haskell-indent-after-keywords |
|
|
'(("where" 2 0) |
|
|
("of" 2) |
|
|
("do" 2) |
|
|
("mdo" 2) |
|
|
("rec" 2) |
|
|
("in" 2 0) |
|
|
("{" 2) |
|
|
"if" |
|
|
"then" |
|
|
"else" |
|
|
"let") |
|
|
"Keywords after which indentation should be indented by some offset. |
|
|
Each keyword info can have the following forms: |
|
|
|
|
|
KEYWORD | (KEYWORD OFFSET [OFFSET-HANGING]) |
|
|
|
|
|
If absent OFFSET-HANGING defaults to OFFSET. |
|
|
If absent OFFSET defaults to `haskell-indent-offset'. |
|
|
|
|
|
OFFSET-HANGING is the offset to use in the case where the keyword |
|
|
is at the end of an otherwise-non-empty line." |
|
|
:group 'haskell-indent |
|
|
:type '(repeat (choice string |
|
|
(cons :tag "" (string :tag "keyword:") |
|
|
(cons :tag "" (integer :tag "offset") |
|
|
(choice (const nil) |
|
|
(list :tag "" |
|
|
(integer :tag "offset-pending")))))))) |
|
|
|
|
|
(defun haskell-indent-skip-lexeme-forward () |
|
|
(and (zerop (skip-syntax-forward "w")) |
|
|
(skip-syntax-forward "_") |
|
|
(skip-syntax-forward "(") |
|
|
(skip-syntax-forward ")"))) |
|
|
|
|
|
(defvar haskell-indent-inhibit-after-offset nil) |
|
|
|
|
|
(defun haskell-indent-offset-after-info () |
|
|
"Return the info from `haskell-indent-after-keywords' for keyword at point." |
|
|
(let ((id (buffer-substring |
|
|
(point) |
|
|
(save-excursion |
|
|
(haskell-indent-skip-lexeme-forward) |
|
|
(point))))) |
|
|
(or (assoc id haskell-indent-after-keywords) |
|
|
(car (member id haskell-indent-after-keywords))))) |
|
|
|
|
|
;;;###autoload |
|
|
(defcustom haskell-indent-dont-hang '("(") |
|
|
"Lexemes that should never be considered as hanging." |
|
|
:group 'haskell-indent |
|
|
:type '(repeat string)) |
|
|
|
|
|
(defun haskell-indent-hanging-p () |
|
|
;; A Hanging keyword is one that's at the end of a line except it's not at |
|
|
;; the beginning of a line. |
|
|
(not (or (= (current-column) (haskell-indent-current-indentation)) |
|
|
(save-excursion |
|
|
(let ((lexeme |
|
|
(buffer-substring |
|
|
(point) |
|
|
(progn (haskell-indent-skip-lexeme-forward) (point))))) |
|
|
(or (member lexeme haskell-indent-dont-hang) |
|
|
(> (line-end-position) |
|
|
(progn (forward-comment (point-max)) (point))))))))) |
|
|
|
|
|
(defun haskell-indent-after-keyword-column (offset-info start &optional default) |
|
|
(unless offset-info |
|
|
(setq offset-info (haskell-indent-offset-after-info))) |
|
|
(unless default (setq default haskell-indent-offset)) |
|
|
(setq offset-info |
|
|
(if haskell-indent-inhibit-after-offset '(0) (cdr-safe offset-info))) |
|
|
(if (not (haskell-indent-hanging-p)) |
|
|
(haskell-indent-column+offset (current-column) |
|
|
(or (car offset-info) default)) |
|
|
;; The keyword is hanging at the end of the line. |
|
|
(haskell-indent-column+offset |
|
|
(haskell-indent-virtual-indentation start) |
|
|
(or (cadr offset-info) (car offset-info) default)))) |
|
|
|
|
|
(defun haskell-indent-inside-paren (open) |
|
|
;; there is an open structure to complete |
|
|
(if (looking-at "\\s)\\|[;,]") |
|
|
;; A close-paren or a , or ; can only correspond syntactically to |
|
|
;; the open-paren at `open'. So there is no ambiguity. |
|
|
(progn |
|
|
(if (or (and (eq (char-after) ?\;) (eq (char-after open) ?\()) |
|
|
(and (eq (char-after) ?\,) (eq (char-after open) ?\{))) |
|
|
(message "Mismatched punctuation: `%c' in %c...%c" |
|
|
(char-after) (char-after open) |
|
|
(if (eq (char-after open) ?\() ?\) ?\}))) |
|
|
(save-excursion |
|
|
(goto-char open) |
|
|
(list (list |
|
|
(if (haskell-indent-hanging-p) |
|
|
(haskell-indent-virtual-indentation nil) |
|
|
(haskell-indent-point-to-col open)))))) |
|
|
;; There might still be layout within the open structure. |
|
|
(let* ((end (point)) |
|
|
(basic-indent-info |
|
|
;; Anything else than a ) is subject to layout. |
|
|
(if (looking-at "\\s.\\|\\$ ") |
|
|
(haskell-indent-point-to-col open) ; align a punct with ( |
|
|
(let ((follow (save-excursion |
|
|
(goto-char (1+ open)) |
|
|
(haskell-indent-skip-blanks-and-newlines-forward end) |
|
|
(point)))) |
|
|
(if (= follow end) |
|
|
(save-excursion |
|
|
(goto-char open) |
|
|
(haskell-indent-after-keyword-column nil nil 1)) |
|
|
(haskell-indent-point-to-col follow))))) |
|
|
(open-column (haskell-indent-point-to-col open)) |
|
|
(contour-line (haskell-indent-contour-line (1+ open) end))) |
|
|
(if (null contour-line) |
|
|
(list (list basic-indent-info)) |
|
|
(let ((indent-info |
|
|
(haskell-indent-layout-indent-info |
|
|
(1+ open) contour-line))) |
|
|
;; Fix up indent info. |
|
|
(let ((base-elem (assoc open-column indent-info))) |
|
|
(if base-elem |
|
|
(progn (setcar base-elem basic-indent-info) |
|
|
(setcdr base-elem nil)) |
|
|
(setq indent-info |
|
|
(append indent-info (list (list basic-indent-info))))) |
|
|
indent-info)))))) |
|
|
|
|
|
(defun haskell-indent-virtual-indentation (start) |
|
|
"Compute the \"virtual indentation\" of text at point. |
|
|
The \"virtual indentation\" is the indentation that text at point would have |
|
|
had, if it had been placed on its own line." |
|
|
(let ((col (current-column)) |
|
|
(haskell-indent-inhibit-after-offset (haskell-indent-hanging-p))) |
|
|
(if (save-excursion (skip-chars-backward " \t") (bolp)) |
|
|
;; If the text is indeed on its own line, than the virtual indent is |
|
|
;; the current indentation. |
|
|
col |
|
|
;; Else, compute the indentation that it would have had. |
|
|
(let ((info (haskell-indent-indentation-info start)) |
|
|
(max -1)) |
|
|
;; `info' is a list of possible indent points. Each indent point is |
|
|
;; assumed to correspond to a different parse. So we need to find |
|
|
;; the parse that corresponds to the case at hand (where there's no |
|
|
;; line break), which is assumed to always be the |
|
|
;; deepest indentation. |
|
|
(dolist (x info) |
|
|
(setq x (car x)) |
|
|
;; Sometimes `info' includes the current indentation (or yet |
|
|
;; deeper) by mistake, because haskell-indent-indentation-info |
|
|
;; wasn't designed to be called on a piece of text that is not at |
|
|
;; BOL. So ignore points past `col'. |
|
|
(if (and (> x max) (not (>= x col))) |
|
|
(setq max x))) |
|
|
;; In case all the indent points are past `col', just use `col'. |
|
|
(if (>= max 0) max col))))) |
|
|
|
|
|
(defun haskell-indent-indentation-info (&optional start) |
|
|
"Return a list of possible indentations for the current line. |
|
|
These are then used by `haskell-indent-cycle'. |
|
|
START if non-nil is a presumed start pos of the current definition." |
|
|
(unless start (setq start (haskell-indent-start-of-def))) |
|
|
(let (open contour-line) |
|
|
(cond |
|
|
;; in string? |
|
|
((setq open (haskell-indent-in-string start (point))) |
|
|
(list (list (+ (haskell-indent-point-to-col open) |
|
|
(if (looking-at "\\\\") 0 1))))) |
|
|
|
|
|
;; in comment ? |
|
|
((setq open (haskell-indent-in-comment start (point))) |
|
|
(haskell-indent-comment open start)) |
|
|
|
|
|
;; Closing the declaration part of a `let' or the test exp part of a case. |
|
|
((looking-at "\\(?:in\\|of\\|then\\|else\\)\\>") |
|
|
(haskell-indent-closing-keyword start)) |
|
|
|
|
|
;; Right after a special keyword. |
|
|
((save-excursion |
|
|
(forward-comment (- (point-max))) |
|
|
(when (and (not (zerop (skip-syntax-backward "w"))) |
|
|
(setq open (haskell-indent-offset-after-info))) |
|
|
(list (list (haskell-indent-after-keyword-column open start)))))) |
|
|
|
|
|
;; open structure? ie ( { [ |
|
|
((setq open (haskell-indent-open-structure start (point))) |
|
|
(haskell-indent-inside-paren open)) |
|
|
|
|
|
;; full indentation |
|
|
((setq contour-line (haskell-indent-contour-line start (point))) |
|
|
(haskell-indent-layout-indent-info start contour-line)) |
|
|
|
|
|
(t |
|
|
;; simple contour just one indentation at start |
|
|
(list (list (if (and (eq haskell-literate 'bird) |
|
|
(eq (haskell-indent-point-to-col start) 1)) |
|
|
;; for a Bird style literate script put default offset |
|
|
;; in the case of no indentation |
|
|
(1+ haskell-indent-literate-Bird-default-offset) |
|
|
(haskell-indent-point-to-col start)))))))) |
|
|
|
|
|
(defvar haskell-indent-last-info nil) |
|
|
|
|
|
|
|
|
(defun haskell-indent-cycle () |
|
|
"Indentation cycle. |
|
|
We stay in the cycle as long as the TAB key is pressed." |
|
|
(interactive "*") |
|
|
(if (and haskell-literate |
|
|
(not (haskell-indent-within-literate-code))) |
|
|
;; use the ordinary tab for text... |
|
|
(funcall (default-value 'indent-line-function)) |
|
|
(let ((marker (if (> (current-column) (haskell-indent-current-indentation)) |
|
|
(point-marker))) |
|
|
(bol (progn (beginning-of-line) (point)))) |
|
|
(haskell-indent-back-to-indentation) |
|
|
(unless (and (eq last-command this-command) |
|
|
(eq bol (car haskell-indent-last-info))) |
|
|
(save-excursion |
|
|
(setq haskell-indent-last-info |
|
|
(list bol (haskell-indent-indentation-info) 0 0)))) |
|
|
|
|
|
(let* ((il (nth 1 haskell-indent-last-info)) |
|
|
(index (nth 2 haskell-indent-last-info)) |
|
|
(last-insert-length (nth 3 haskell-indent-last-info)) |
|
|
(indent-info (nth index il))) |
|
|
|
|
|
(haskell-indent-line-to (car indent-info)) ; insert indentation |
|
|
(delete-char last-insert-length) |
|
|
(setq last-insert-length 0) |
|
|
(let ((text (cdr indent-info))) |
|
|
(if text |
|
|
(progn |
|
|
(insert text) |
|
|
(setq last-insert-length (length text))))) |
|
|
|
|
|
(setq haskell-indent-last-info |
|
|
(list bol il (% (1+ index) (length il)) last-insert-length)) |
|
|
|
|
|
(if (= (length il) 1) |
|
|
(message "Sole indentation") |
|
|
(message "Indent cycle (%d)..." (length il))) |
|
|
|
|
|
(if marker |
|
|
(goto-char (marker-position marker))))))) |
|
|
|
|
|
(defun haskell-indent-region (_start _end) |
|
|
(error "Auto-reindentation of a region is not supported")) |
|
|
|
|
|
;;; alignment functions |
|
|
|
|
|
(defun haskell-indent-shift-columns (dest-column region-stack) |
|
|
"Shift columns in REGION-STACK to go to DEST-COLUMN. |
|
|
Elements of the stack are pairs of points giving the start and end |
|
|
of the regions to move." |
|
|
(let (reg col diffcol reg-end) |
|
|
(while (setq reg (pop region-stack)) |
|
|
(setq reg-end (copy-marker (cdr reg))) |
|
|
(goto-char (car reg)) |
|
|
(setq col (current-column)) |
|
|
(setq diffcol (- dest-column col)) |
|
|
(if (not (zerop diffcol)) |
|
|
(catch 'end-of-buffer |
|
|
(while (<= (point) (marker-position reg-end)) |
|
|
(if (< diffcol 0) |
|
|
(backward-delete-char-untabify (- diffcol) nil) |
|
|
(insert-char ?\ diffcol)) |
|
|
(end-of-line 2) ; should be (forward-line 1) |
|
|
(if (eobp) ; but it adds line at the end... |
|
|
(throw 'end-of-buffer nil)) |
|
|
(move-to-column col))))))) |
|
|
|
|
|
(defun haskell-indent-align-def (p-arg type) |
|
|
"Align guards or rhs within the current definition before point. |
|
|
If P-ARG is t align all defs up to the mark. |
|
|
TYPE is either 'guard or 'rhs." |
|
|
(save-excursion |
|
|
(let (start-block end-block |
|
|
(maxcol (if (eq type 'rhs) haskell-indent-rhs-align-column 0)) |
|
|
contour sep defname defnamepos |
|
|
defcol pos lastpos |
|
|
regstack eqns-start start-found) |
|
|
;; find the starting and ending boundary points for alignment |
|
|
(if p-arg |
|
|
(if (mark) ; aligning everything in the region |
|
|
(progn |
|
|
(when (> (mark) (point)) (exchange-point-and-mark)) |
|
|
(setq start-block |
|
|
(save-excursion |
|
|
(goto-char (mark)) |
|
|
(line-beginning-position))) |
|
|
(setq end-block |
|
|
(progn (if (haskell-indent-bolp) |
|
|
(haskell-indent-forward-line -1)) |
|
|
(line-end-position)))) |
|
|
(error "The mark is not set for aligning definitions")) |
|
|
;; aligning the current definition |
|
|
(setq start-block (haskell-indent-start-of-def)) |
|
|
(setq end-block (line-end-position))) |
|
|
;; find the start of the current valdef using the contour line |
|
|
;; in reverse order because we need the nearest one from the end |
|
|
(setq contour |
|
|
(reverse (haskell-indent-contour-line start-block end-block))) |
|
|
(setq pos (car contour)) ; keep the start of the first contour |
|
|
;; find the nearest start of a definition |
|
|
(while (and (not defname) contour) |
|
|
(goto-char (pop contour)) |
|
|
(if (haskell-indent-open-structure start-block (point)) |
|
|
nil |
|
|
(setq sep (haskell-indent-separate-valdef (point) end-block)) |
|
|
(if (nth 5 sep) ; is there a rhs? |
|
|
(progn (setq defnamepos (nth 0 sep)) |
|
|
(setq defname (nth 1 sep)))))) |
|
|
;; start building the region stack |
|
|
(if defnamepos |
|
|
(progn ; there is a valdef |
|
|
;; find the start of each equation or guard |
|
|
(if p-arg ; when indenting a region |
|
|
;; accept any start of id or pattern as def name |
|
|
(setq defname "\\<\\|(")) |
|
|
(setq defcol (haskell-indent-point-to-col defnamepos)) |
|
|
(goto-char pos) |
|
|
(setq end-block (line-end-position)) |
|
|
(catch 'top-of-buffer |
|
|
(while (and (not start-found) |
|
|
(>= (point) start-block)) |
|
|
(if (<= (haskell-indent-current-indentation) defcol) |
|
|
(progn |
|
|
(move-to-column defcol) |
|
|
(if (and (looking-at defname) ; start of equation |
|
|
(not (haskell-indent-open-structure start-block (point)))) |
|
|
(push (cons (point) 'eqn) eqns-start) |
|
|
;; found a less indented point not starting an equation |
|
|
(setq start-found t))) |
|
|
;; more indented line |
|
|
(haskell-indent-back-to-indentation) |
|
|
(if (and (eq (haskell-indent-type-at-point) 'guard) ; start of a guard |
|
|
(not (haskell-indent-open-structure start-block (point)))) |
|
|
(push (cons (point) 'gd) eqns-start))) |
|
|
(if (bobp) |
|
|
(throw 'top-of-buffer nil) |
|
|
(haskell-indent-backward-to-indentation 1)))) |
|
|
;; remove the spurious guards before the first equation |
|
|
(while (and eqns-start (eq (cdar eqns-start) 'gd)) |
|
|
(pop eqns-start)) |
|
|
;; go through each equation to find the region to indent |
|
|
(while eqns-start |
|
|
(let ((eqn (caar eqns-start))) |
|
|
(setq lastpos (if (cdr eqns-start) |
|
|
(save-excursion |
|
|
(goto-char (cl-caadr eqns-start)) |
|
|
(haskell-indent-forward-line -1) |
|
|
(line-end-position)) |
|
|
end-block)) |
|
|
(setq sep (haskell-indent-separate-valdef eqn lastpos))) |
|
|
(if (eq type 'guard) |
|
|
(setq pos (nth 3 sep)) |
|
|
;; check if what follows a rhs sign is more indented or not |
|
|
(let ((rhs (nth 5 sep)) |
|
|
(aft-rhs (nth 6 sep))) |
|
|
(if (and rhs aft-rhs |
|
|
(> (haskell-indent-point-to-col rhs) |
|
|
(haskell-indent-point-to-col aft-rhs))) |
|
|
(setq pos aft-rhs) |
|
|
(setq pos rhs)))) |
|
|
(if pos |
|
|
(progn ; update region stack |
|
|
(push (cons pos (or lastpos pos)) regstack) |
|
|
(setq maxcol ; find the highest column number |
|
|
(max maxcol |
|
|
(progn ;find the previous non-empty column |
|
|
(goto-char pos) |
|
|
(skip-chars-backward |
|
|
" \t" |
|
|
(line-beginning-position)) |
|
|
(if (haskell-indent-bolp) |
|
|
;;if on an empty prefix |
|
|
(haskell-indent-point-to-col pos) ;keep original indent |
|
|
(1+ (haskell-indent-point-to-col (point))))))))) |
|
|
(pop eqns-start)) |
|
|
;; now shift according to the region stack |
|
|
(if regstack |
|
|
(haskell-indent-shift-columns maxcol regstack))))))) |
|
|
|
|
|
(defun haskell-indent-align-guards-and-rhs (_start _end) |
|
|
"Align the guards and rhs of functions in the region, which must be active." |
|
|
;; The `start' and `end' args are dummys right now: they're just there so |
|
|
;; we can use the "r" interactive spec which properly signals an error. |
|
|
(interactive "*r") |
|
|
(haskell-indent-align-def t 'guard) |
|
|
(haskell-indent-align-def t 'rhs)) |
|
|
|
|
|
;;; insertion functions |
|
|
|
|
|
(defun haskell-indent-insert-equal () |
|
|
"Insert an = sign and align the previous rhs of the current function." |
|
|
(interactive "*") |
|
|
(if (or (haskell-indent-bolp) |
|
|
(/= (preceding-char) ?\ )) |
|
|
(insert ?\ )) |
|
|
(insert "= ") |
|
|
(haskell-indent-align-def (haskell-indent-mark-active) 'rhs)) |
|
|
|
|
|
(defun haskell-indent-insert-guard (&optional text) |
|
|
"Insert and align a guard sign (|) followed by optional TEXT. |
|
|
Alignment works only if all guards are to the south-east of their |." |
|
|
(interactive "*") |
|
|
(let ((pc (if (haskell-indent-bolp) ?\012 |
|
|
(preceding-char))) |
|
|
(pc1 (or (char-after (- (point) 2)) 0))) |
|
|
;; check what guard to insert depending on the previous context |
|
|
(if (= pc ?\ ) ; x = any char other than blank or | |
|
|
(if (/= pc1 ?\|) |
|
|
(insert "| ") ; after " x" |
|
|
()) ; after " |" |
|
|
(if (= pc ?\|) |
|
|
(if (= pc1 ?\|) |
|
|
(insert " | ") ; after "||" |
|
|
(insert " ")) ; after "x|" |
|
|
(insert " | "))) ; general case |
|
|
(if text (insert text)) |
|
|
(haskell-indent-align-def (haskell-indent-mark-active) 'guard))) |
|
|
|
|
|
(defun haskell-indent-insert-otherwise () |
|
|
"Insert a guard sign (|) followed by `otherwise'. |
|
|
Also align the previous guards of the current function." |
|
|
(interactive "*") |
|
|
(haskell-indent-insert-guard "otherwise") |
|
|
(haskell-indent-insert-equal)) |
|
|
|
|
|
(defun haskell-indent-insert-where () |
|
|
"Insert a where keyword at point and indent resulting line. |
|
|
One indentation cycle is used." |
|
|
(interactive "*") |
|
|
(insert "where ") |
|
|
(haskell-indent-cycle)) |
|
|
|
|
|
|
|
|
;;; haskell-indent-mode |
|
|
|
|
|
(defvar haskell-indent-mode nil |
|
|
"Non-nil if the semi-intelligent Haskell indentation mode is in effect.") |
|
|
(make-variable-buffer-local 'haskell-indent-mode) |
|
|
|
|
|
(defvar haskell-indent-map |
|
|
(let ((map (make-sparse-keymap))) |
|
|
;; Removed: remapping DEL seems a bit naughty --SDM |
|
|
;; (define-key map "\177" 'backward-delete-char-untabify) |
|
|
;; The binding to TAB is already handled by indent-line-function. --Stef |
|
|
;; (define-key map "\t" 'haskell-indent-cycle) |
|
|
(define-key map [?\C-c ?\C-=] 'haskell-indent-insert-equal) |
|
|
(define-key map [?\C-c ?\C-|] 'haskell-indent-insert-guard) |
|
|
;; Alternate binding, in case C-c C-| is too inconvenient to type. |
|
|
;; Duh, C-g is a special key, let's not use it here. |
|
|
;; (define-key map [?\C-c ?\C-g] 'haskell-indent-insert-guard) |
|
|
(define-key map [?\C-c ?\C-o] 'haskell-indent-insert-otherwise) |
|
|
(define-key map [?\C-c ?\C-w] 'haskell-indent-insert-where) |
|
|
(define-key map [?\C-c ?\C-.] 'haskell-indent-align-guards-and-rhs) |
|
|
(define-key map [?\C-c ?\C->] 'haskell-indent-put-region-in-literate) |
|
|
map)) |
|
|
|
|
|
;;;###autoload |
|
|
(defun turn-on-haskell-indent () |
|
|
"Turn on ``intelligent'' Haskell indentation mode." |
|
|
(set (make-local-variable 'indent-line-function) 'haskell-indent-cycle) |
|
|
(set (make-local-variable 'indent-region-function) 'haskell-indent-region) |
|
|
(setq haskell-indent-mode t) |
|
|
;; Activate our keymap. |
|
|
(let ((map (current-local-map))) |
|
|
(while (and map (not (eq map haskell-indent-map))) |
|
|
(setq map (keymap-parent map))) |
|
|
(if map |
|
|
;; haskell-indent-map is already active: nothing to do. |
|
|
nil |
|
|
;; Put our keymap on top of the others. We could also put it in |
|
|
;; second place, or in a minor-mode. The minor-mode approach would be |
|
|
;; easier, but it's harder for the user to override it. This approach |
|
|
;; is the closest in behavior compared to the previous code that just |
|
|
;; used a bunch of local-set-key. |
|
|
(set-keymap-parent haskell-indent-map (current-local-map)) |
|
|
;; Protect our keymap. |
|
|
(setq map (make-sparse-keymap)) |
|
|
(set-keymap-parent map haskell-indent-map) |
|
|
(use-local-map map))) |
|
|
(run-hooks 'haskell-indent-hook)) |
|
|
|
|
|
(defun turn-off-haskell-indent () |
|
|
"Turn off ``intelligent'' Haskell indentation mode." |
|
|
(kill-local-variable 'indent-line-function) |
|
|
(kill-local-variable 'indent-region-function) |
|
|
;; Remove haskell-indent-map from the local map. |
|
|
(let ((map (current-local-map))) |
|
|
(while map |
|
|
(let ((parent (keymap-parent map))) |
|
|
(if (eq haskell-indent-map parent) |
|
|
(set-keymap-parent map (keymap-parent parent)) |
|
|
(setq map parent))))) |
|
|
(setq haskell-indent-mode nil)) |
|
|
|
|
|
;; Put this minor mode on the global minor-mode-alist. |
|
|
(or (assq 'haskell-indent-mode (default-value 'minor-mode-alist)) |
|
|
(setq-default minor-mode-alist |
|
|
(append (default-value 'minor-mode-alist) |
|
|
'((haskell-indent-mode " Ind"))))) |
|
|
|
|
|
;;;###autoload |
|
|
(defun haskell-indent-mode (&optional arg) |
|
|
"``Intelligent'' Haskell indentation mode. |
|
|
This deals with the layout rule of Haskell. |
|
|
\\[haskell-indent-cycle] starts the cycle which proposes new |
|
|
possibilities as long as the TAB key is pressed. Any other key |
|
|
or mouse click terminates the cycle and is interpreted except for |
|
|
RET which merely exits the cycle. |
|
|
Other special keys are: |
|
|
\\[haskell-indent-insert-equal] |
|
|
inserts an = |
|
|
\\[haskell-indent-insert-guard] |
|
|
inserts an | |
|
|
\\[haskell-indent-insert-otherwise] |
|
|
inserts an | otherwise = |
|
|
these functions also align the guards and rhs of the current definition |
|
|
\\[haskell-indent-insert-where] |
|
|
inserts a where keyword |
|
|
\\[haskell-indent-align-guards-and-rhs] |
|
|
aligns the guards and rhs of the region |
|
|
\\[haskell-indent-put-region-in-literate] |
|
|
makes the region a piece of literate code in a literate script |
|
|
|
|
|
Invokes `haskell-indent-hook' if not nil." |
|
|
(interactive "P") |
|
|
(setq haskell-indent-mode |
|
|
(if (null arg) (not haskell-indent-mode) |
|
|
(> (prefix-numeric-value arg) 0))) |
|
|
(if haskell-indent-mode |
|
|
(turn-on-haskell-indent) |
|
|
(turn-off-haskell-indent))) |
|
|
|
|
|
(provide 'haskell-indent) |
|
|
|
|
|
;;; haskell-indent.el ends here
|
|
|
|