From 66eed9372c26e88b19cbfb8bab5ebd54160939fd Mon Sep 17 00:00:00 2001 From: Kristof Bastiaensen Date: Fri, 27 Feb 2015 14:56:51 +0100 Subject: [PATCH] merged hi2 (https://github.com/errge/hi2). Conflicts: haskell-indentation.el --- haskell-indentation.el | 878 +++++++++++++++++++++++------------------ 1 file changed, 484 insertions(+), 394 deletions(-) diff --git a/haskell-indentation.el b/haskell-indentation.el index 94cb80194..189c5b07f 100644 --- a/haskell-indentation.el +++ b/haskell-indentation.el @@ -1,9 +1,11 @@ ;;; haskell-indentation.el -- indentation module for Haskell Mode -;; Copyright (C) 2009 Kristof Bastiaensen +;; Copyright (C) 2013 Kristof Bastiaensen, Gergely Risko ;; Author: Kristof Bastiaensen - +;; Author: Gergely Risko +;; Keywords: indentation haskell +;; URL: https://github.com/haskell/haskell-mode ;; This file is not part of GNU Emacs. ;; This file is free software; you can redistribute it and/or modify @@ -32,22 +34,14 @@ ;;; Code: +(require 'hl-line) (require 'syntax) (require 'cl-lib) -(defvar delete-active-region) - -;; Dynamically scoped variables. -(defvar following-token) -(defvar current-token) -(defvar left-indent) -(defvar starter-indent) -(defvar current-indent) -(defvar layout-indent) -(defvar parse-line-number) -(defvar possible-indentations) -(defvar indentation-point) -(defvar haskell-literate) +(defvar haskell-indentation-dyn-first-position) +(defvar haskell-indentation-dyn-last-direction) +(defvar haskell-indentation-dyn-last-indentations) + (defgroup haskell-indentation nil "Haskell indentation." @@ -55,26 +49,32 @@ :group 'haskell :prefix "haskell-indentation-") -(defcustom haskell-indentation-cycle-warn t - "Warn before moving to the leftmost indentation, if you tab at the rightmost one." +(defcustom haskell-indentation-show-indentations t + "If t the current line's indentation points will be showed as +underscore overlays in new haskell-mode buffers. Use +`haskell-indentation-enable-show-indentations' and `haskell-indentation-disable-show-indentations' +to switch the behavior for already existing buffers." :type 'boolean :group 'haskell-indentation) -(defcustom haskell-indentation-delete-backward-indentation t - "Delete backward removes indentation." +(defcustom haskell-indentation-show-indentations-after-eol t + "If t, try to show indentation points after the end of line. +This requires strange overlay hacks and can collide with other +modes (e.g. fill-column-indicator)." :type 'boolean :group 'haskell-indentation) -(defcustom haskell-indentation-delete-backward-jump-line nil - "Delete backward jumps to the previous line when removing last indentation." - :type 'boolean +(defface haskell-indentation-show-normal-face + '((t :underline t)) + "Default face for indentations overlay." :group 'haskell-indentation) -(defcustom haskell-indentation-delete-indentation t - "Delete removes indentation." - :type 'boolean +(defface haskell-indentation-show-hl-line-face + '((t :underline t :inherit hl-line)) + "Face used for indentations overlay after EOL if hl-line mode is enabled." :group 'haskell-indentation) + (defcustom haskell-indentation-indent-leftmost 'both "Indent to the left margin after certain keywords (for example after let .. in, case .. of). If set to t it will only indent to the left. If nil only relative to the containing expression. If set to the keyword 'both then both positions are allowed." :type 'symbol @@ -110,29 +110,12 @@ :type 'integer :group 'haskell-indentation) -(defcustom haskell-indentation-birdtrack-extra-space t - "Append a space after every birdtrack in literate mode." - :type 'boolean - :group 'haskell-indentation) - - -;; Avoid a global bogus definition (which the original run-time -;; `defun' made), and support Emacs 21 without the syntax.el add-on. -(eval-when-compile - (unless (fboundp 'syntax-ppss) - (defsubst syntax-ppss (&rest pos) - (parse-partial-sexp (point-min) (or pos (point)))))) - (defconst haskell-indentation-mode-map (let ((keymap (make-sparse-keymap))) - (define-key keymap (kbd "RET") 'haskell-newline-and-indent) - (define-key keymap (kbd "DEL") 'haskell-indentation-delete-backward-char) - (define-key keymap (kbd "") 'haskell-indentation-delete-char) + (define-key keymap (kbd "RET") 'haskell-indentation-newline-and-indent) + (define-key keymap (kbd "") 'haskell-indentation-indent-backwards) keymap)) -;; Used internally -(defvar haskell-indent-last-position) - ;;;###autoload (define-minor-mode haskell-indentation-mode "Haskell indentation mode that deals with the layout rule. @@ -142,15 +125,14 @@ autofill-mode." :lighter " Ind" :keymap haskell-indentation-mode-map (kill-local-variable 'indent-line-function) + (kill-local-variable 'indent-region-function) (kill-local-variable 'normal-auto-fill-function) (when haskell-indentation-mode (setq max-lisp-eval-depth (max max-lisp-eval-depth 600)) ;; set a higher limit for recursion - (set (make-local-variable 'indent-line-function) - 'haskell-indentation-indent-line) - (set (make-local-variable 'normal-auto-fill-function) - 'haskell-indentation-auto-fill-function) - (set (make-local-variable 'haskell-indent-last-position) - nil))) + (set (make-local-variable 'indent-line-function) 'haskell-indentation-indent-line) + (set (make-local-variable 'indent-region-function) 'haskell-indentation-indent-region) + (set (make-local-variable 'normal-auto-fill-function) 'haskell-indentation-auto-fill-function) + (when haskell-indentation-show-indentations (haskell-indentation-enable-show-indentations)))) ;;;###autoload (defun turn-on-haskell-indentation () @@ -173,277 +155,335 @@ autofill-mode." ,except (message "%s" (cdr parse-error-string))))) -(defun haskell-current-column () - "Compute current column according to haskell syntax rules, - correctly ignoring composition." - (save-excursion - (let ((start (point)) - (cc 0)) - (beginning-of-line) - (while (< (point) start) - (if (= (char-after) ?\t) - (setq cc (* 8 (+ 1 (/ cc 8)))) - (cl-incf cc)) - (forward-char)) - cc))) - -(defun kill-indented-line (&optional arg) - "`kill-line' for indented text. -Preserves indentation and removes extra whitespace" - (interactive "P") - (let ((col (haskell-current-column)) - (old-point (point))) - (cond ((or (and (numberp arg) (< arg 0)) - (and (not (looking-at "[ \t]*$")) - (or (not (numberp arg)) (zerop arg)))) - ;use default behavior when calling with a negative argument - ;or killing (once) from the middle of a line - (kill-line arg)) - ((and (skip-chars-backward " \t") ;always true - (bolp) - (save-excursion - (forward-line arg) - (not (looking-at "[ \t]*$")))) - ; killing from an empty line: - ; preserve indentation of the next line - (kill-region (point) - (save-excursion - (forward-line arg) - (point))) - (skip-chars-forward " \t") - (if (> (haskell-current-column) col) - (move-to-column col))) - (t ; killing from not empty line: - ; kill all indentation - (goto-char old-point) - (kill-region (point) - (save-excursion - (forward-line arg) - (skip-chars-forward " \t") - (point))))))) +(defvar haskell-literate) +(defun haskell-indentation-birdp () + "Return t if this is a literate haskell buffer in bird style, nil otherwise." + (eq haskell-literate 'bird)) + +;;---------------------------------------- UI starts here (defun haskell-indentation-auto-fill-function () - (when (> (haskell-current-column) fill-column) - (while (> (haskell-current-column) fill-column) + (when (> (current-column) fill-column) + (while (> (current-column) fill-column) (skip-syntax-backward "-") (skip-syntax-backward "^-")) - (let ((auto-fill-function nil) - (indent (car (last (haskell-indentation-find-indentations))))) + (let ((indent (car (last (haskell-indentation-find-indentations-safe))))) + (delete-horizontal-space) (newline) - (when (eq haskell-literate 'bird) - (insert ">")) + (when (haskell-indentation-birdp) (insert ">")) (indent-to indent) (end-of-line)))) -(defun haskell-indentation-reindent (col) - (beginning-of-line) - (delete-region (point) - (progn - (when (and (eq haskell-literate 'bird) - (eq (char-after) ?>)) - (forward-char)) - (skip-syntax-forward "-") - (point))) - (when (eq haskell-literate 'bird) - (insert ">")) - (indent-to col)) +(defun haskell-indentation-reindent-to (col &optional move) + "Reindent current line to COL, also move the point there if MOVE" + (let* ((cc (current-column)) + (ci (haskell-indentation-current-indentation))) + (save-excursion + (move-to-column ci) + (if (<= ci col) + (insert-before-markers (make-string (- col ci) ? )) + (delete-char (- col ci)))) + (when move + (move-to-column col)))) + +(defun haskell-indentation-indent-rigidly (start end arg) + "Indent all lines starting in the region sideways by ARG columns. +Called from a program, takes three arguments, START, END and ARG. +You can remove all indentation from a region by giving a large negative ARG. +Handles bird style literate haskell too." + (interactive "r\np") + (save-excursion + (goto-char end) + (let ((end-marker (point-marker))) + (goto-char start) + (or (bolp) (forward-line 0)) + (while (< (point) end-marker) + (let ((ci (haskell-indentation-current-indentation))) + (when (and t + (eq (char-after) ?>)) + (forward-char 1)) + (skip-syntax-forward "-") + (unless (eolp) + (haskell-indentation-reindent-to (max 0 (+ ci arg)))) + (forward-line 1))) + (move-marker end-marker nil)))) (defun haskell-indentation-current-indentation () - (if (eq haskell-literate 'bird) - (save-excursion - (beginning-of-line) - (forward-char) - (skip-syntax-forward "-") - (current-column)) - (current-indentation))) + "Column position of first non whitespace character in current line" + (save-excursion + (beginning-of-line) + (when (haskell-indentation-birdp) (forward-char)) + (skip-syntax-forward "-") + (current-column))) -(defun haskell-indentation-outside-bird-line () - (and (eq haskell-literate 'bird) +(defun haskell-indentation-bird-outside-codep () + "True iff we are in bird literate mode, but outside of code" + (and (haskell-indentation-birdp) (or (< (current-column) 2) (save-excursion (beginning-of-line) (not (eq (char-after) ?>)))))) -(defun haskell-newline-and-indent () +(defun haskell-indentation-delete-horizontal-space-and-newline () + (delete-horizontal-space) + (newline)) + +(defun haskell-indentation-newline-and-indent () + "Ran on C-j or RET" (interactive) - (if (haskell-indentation-outside-bird-line) - (progn - (delete-horizontal-space) - (newline)) + ;; On RET (or C-j), we: + ;; - just jump to the next line if literate haskell, but outside code + (if (haskell-indentation-bird-outside-codep) + (haskell-indentation-delete-horizontal-space-and-newline) + ;; - just jump to the next line if parse-error (on-parse-error - (newline) - (let* ((cc (haskell-current-column)) + (haskell-indentation-delete-horizontal-space-and-newline) + (let* ((cc (current-column)) (ci (haskell-indentation-current-indentation)) - (indentations (haskell-indentation-find-indentations))) + (indentations (haskell-indentation-find-indentations-safe))) + ;; - jump to the next line and reindent to at the least same level + ;; if parsing was OK (skip-syntax-forward "-") - (if (prog1 (and (eolp) - (not (= (haskell-current-column) ci))) - (delete-horizontal-space) - (if (not (eq haskell-literate 'bird)) - (newline) - (when haskell-indentation-birdtrack-extra-space - (indent-to 2)) - (newline) - (insert "> "))) - (haskell-indentation-reindent - (max (haskell-indentation-butlast indentations) - (haskell-indentation-matching-indentation - ci indentations))) - (haskell-indentation-reindent (haskell-indentation-matching-indentation - cc indentations))))))) - -(defun haskell-indentation-one-indentation (col indentations) - (let* ((last-pair (last indentations))) - (cond ((null indentations) - col) - ((null (cdr indentations)) - (car indentations)) - ((<= col (car last-pair)) - col) - (t (car last-pair))))) - -(defun haskell-indentation-butlast (indentations) - (when (consp (cdr indentations)) - (while (cddr indentations) - (setq indentations (cdr indentations)))) - (car indentations)) - -(defun haskell-indentation-next-indentation (col indentations) - "Find the lefmost indentation which is greater than COL." - (catch 'return - (while indentations - (if (or (< col (car indentations)) - (null (cdr indentations))) - (throw 'return (car indentations)) - (setq indentations (cdr indentations)))) - col)) - -(defun haskell-indentation-previous-indentation (col indentations) + (haskell-indentation-delete-horizontal-space-and-newline) + (when (haskell-indentation-birdp) (insert "> ")) + (haskell-indentation-reindent-to + (haskell-indentation-next-indentation (- ci 1) indentations 'nofail) + 'move))))) + +(defun haskell-indentation-next-indentation (col indentations &optional nofail) + "Find the leftmost indentation which is greater than COL. +Or returns the last indentation if there are no bigger ones and +NOFAIL is non-nil." + (when (null indentations) (error "haskell-indentation-next-indentation called with empty list")) + (or (cl-find-if #'(lambda (i) (> i col)) indentations) + (when nofail (car (last indentations))))) + +(defun haskell-indentation-previous-indentation (col indentations &optional nofail) "Find the rightmost indentation which is less than COL." - (and indentations - (> col (car indentations)) - (catch 'return - (while indentations - (if (or (null (cdr indentations)) - (<= col (cadr indentations))) - (throw 'return (car indentations)) - (setq indentations (cdr indentations)))) - col))) - -(defun haskell-indentation-matching-indentation (col indentations) - "Find the leftmost indentation which is greater than or equal to COL." - (catch 'return - (while indentations - (if (or (<= col (car indentations)) - (null (cdr indentations))) - (throw 'return (car indentations)) - (setq indentations (cdr indentations)))) - col)) + (when (null indentations) (error "haskell-indentation-previous-indentation called with empty list")) + (let ((rev (reverse indentations))) + (or (cl-find-if #'(lambda (i) (< i col)) rev) + (when nofail (car rev))))) (defun haskell-indentation-indent-line () - (when (save-excursion - (beginning-of-line) - (not (nth 8 (syntax-ppss)))) - (let ((ci (haskell-indentation-current-indentation)) - (start-column (haskell-current-column))) - (cond ((> (haskell-current-column) ci) - (save-excursion - (move-to-column ci) - (haskell-indentation-reindent - (haskell-indentation-one-indentation - ci (haskell-indentation-find-indentations))))) - - ((= (haskell-current-column) ci) - (haskell-indentation-reindent - (haskell-indentation-next-indentation - ci (haskell-indentation-find-indentations)))) - - (t (move-to-column ci) - (haskell-indentation-reindent - (haskell-indentation-matching-indentation - ci (haskell-indentation-find-indentations))))) - (cond ((not (= (haskell-current-column) start-column)) - (setq haskell-indent-last-position nil)) - ((not haskell-indentation-cycle-warn) - (haskell-indentation-reindent - (haskell-indentation-next-indentation - -1 - (haskell-indentation-find-indentations)))) - ((not (equal (point) haskell-indent-last-position)) - (message "Press TAB again to go to the leftmost indentation") - (setq haskell-indent-last-position (point))) - (t - (haskell-indentation-reindent - (haskell-indentation-next-indentation - -1 - (haskell-indentation-find-indentations)))))))) - -(defun haskell-indentation-delete-backward-char (n) - (interactive "p") - (on-parse-error - (delete-char (- n)) - (cond - ((haskell-indentation-outside-bird-line) - (delete-char (- n))) - ((and (use-region-p) - delete-active-region - (not (= (point) (mark)))) - (delete-region (mark) (point))) - ((or (= (haskell-current-column) 0) - (> (haskell-current-column) (haskell-indentation-current-indentation)) - (nth 8 (syntax-ppss))) - (delete-char (- n))) - (haskell-indentation-delete-backward-indentation - (let* ((ci (haskell-indentation-current-indentation)) - (pi (haskell-indentation-previous-indentation - ci (haskell-indentation-find-indentations)))) - (save-excursion - (cond (pi - (move-to-column pi) - (delete-region (point) - (progn (move-to-column ci) - (point)))) - (t - (if (not haskell-indentation-delete-backward-jump-line) - (delete-char (- 1)) - (beginning-of-line) - (delete-region (max (point-min) (- (point) 1)) - (progn (move-to-column ci) - (point))))))))) - (t (delete-char (- n)))))) - -(defun haskell-indentation-delete-char (n) - (interactive "p") - (if (haskell-indentation-outside-bird-line) - (delete-char n) - (on-parse-error (delete-char n) - (cond - ((and delete-selection-mode - mark-active - (not (= (point) (mark)))) - (delete-region (mark) (point))) - ((and (eq haskell-literate 'bird) - (looking-at "\n> ")) - (delete-char (+ n 2))) - ((or (eolp) - (>= (haskell-current-column) (haskell-indentation-current-indentation)) - (nth 8 (syntax-ppss))) - (delete-char n)) - (haskell-indentation-delete-indentation - (let* ((ci (haskell-indentation-current-indentation)) - (pi (haskell-indentation-previous-indentation - ci (haskell-indentation-find-indentations)))) - (save-excursion - (if (and pi (> pi (haskell-current-column))) - (move-to-column pi)) - (delete-region (point) - (progn (move-to-column ci) - (point)))))) - (t (delete-char (- n))))))) + (interactive) + "Auto indentation on TAB. +Do nothing inside multiline comments and multiline strings. +Start enumerating the indentation points to the right. The user +can continue by repeatedly pressing TAB. When there is no more +indentation points to the right, we switch going to the left." + ;; try to repeat + (when (not (haskell-indentation-indent-line-repeat)) + (setq haskell-indentation-dyn-last-direction nil) + ;; do nothing if we're inside a string or comment + (unless (save-excursion + (beginning-of-line) + (nth 8 (syntax-ppss))) + ;; parse error is intentionally not catched here, it may come from + ;; haskell-indentation-find-indentations-safe, but escapes the scope and aborts the + ;; opertaion before any moving happens + (let* ((cc (current-column)) + (ci (haskell-indentation-current-indentation)) + (inds (save-excursion + (move-to-column ci) + (haskell-indentation-find-indentations-safe))) + (valid (memq ci inds)) + (cursor-in-whitespace (< cc ci))) + ;; can't happen right now, because of -safe, but we may want to have this in the future + ;; (when (null inds) + ;; (error "returned indentations empty, but no parse error")) + (if (and valid cursor-in-whitespace) + (move-to-column ci) + (haskell-indentation-reindent-to (haskell-indentation-next-indentation ci inds 'nofail) cursor-in-whitespace)) + (setq haskell-indentation-dyn-last-direction 'right) + (setq haskell-indentation-dyn-first-position (haskell-indentation-current-indentation)) + (setq haskell-indentation-dyn-last-indentations inds))))) + +(defun haskell-indentation-indent-line-repeat () + "Ran if the user repeatedly presses the TAB key" + (cond + ((and (memq last-command '(indent-for-tab-command haskell-indentation-indent-backwards)) + (eq haskell-indentation-dyn-last-direction 'region)) + (let ((mark-even-if-inactive t)) + (haskell-indentation-indent-rigidly (region-beginning) (region-end) 1)) + t) + ((and (eq last-command 'indent-for-tab-command) + (memq haskell-indentation-dyn-last-direction '(left right)) + haskell-indentation-dyn-last-indentations) + (let* ((cc (current-column)) + (ci (haskell-indentation-current-indentation))) + (if (eq haskell-indentation-dyn-last-direction 'left) + (haskell-indentation-reindent-to (haskell-indentation-previous-indentation ci haskell-indentation-dyn-last-indentations 'nofail)) + ;; right + (if (haskell-indentation-next-indentation ci haskell-indentation-dyn-last-indentations) + (haskell-indentation-reindent-to (haskell-indentation-next-indentation ci haskell-indentation-dyn-last-indentations 'nofail)) + ;; but failed, switch to left + (setq haskell-indentation-dyn-last-direction 'left) + ;; and skip to the point where the user started pressing TABs. + ;; except if there are <= 2 indentation points, because this + ;; behavior is very confusing in that case + (when (< 2 (length haskell-indentation-dyn-last-indentations)) + (haskell-indentation-reindent-to haskell-indentation-dyn-first-position)) + (haskell-indentation-indent-line-repeat)))) + t) + (t nil))) + +(defun haskell-indentation-indent-region (start end) + (setq haskell-indentation-dyn-last-direction 'region) + (haskell-indentation-indent-rigidly start end 1) + (message "Press TAB or S-TAB again to indent the region more")) + +(defun haskell-indentation-indent-backwards () + "Indent the current line to the previous indentation point" + (interactive) + (cond + ((and (memq last-command '(indent-for-tab-command haskell-indentation-indent-backwards)) + (eq haskell-indentation-dyn-last-direction 'region)) + (let ((mark-even-if-inactive t)) + (haskell-indentation-indent-rigidly (region-beginning) (region-end) -1))) + ((use-region-p) + (setq haskell-indentation-dyn-last-direction 'region) + (haskell-indentation-indent-rigidly (region-beginning) (region-end) -1) + (message "Press TAB or S-TAB again to indent the region more")) + (t + (setq haskell-indentation-dyn-last-direction nil) + (let* ((cc (current-column)) + (ci (haskell-indentation-current-indentation)) + (inds (save-excursion + (move-to-column ci) + (haskell-indentation-find-indentations-safe))) + (cursor-in-whitespace (< cc ci)) + (pi (haskell-indentation-previous-indentation ci inds))) + (if (null pi) + ;; if there are no more indentations to the left, just go to column 0 + (haskell-indentation-reindent-to (car (haskell-indentation-first-indentation)) cursor-in-whitespace) + (haskell-indentation-reindent-to pi cursor-in-whitespace)))))) + +;;---------------------------------------- haskell-indentation show indentations UI starts here +(defvar haskell-indentation-dyn-show-indentations + "Whether showing of indentation points is enabled in this buffer.") +(make-variable-buffer-local 'haskell-indentation-dyn-show-indentations) +(defvar haskell-indentation-dyn-overlays nil + "Overlays used by haskell-indentation-enable-show-indentations.") +(make-variable-buffer-local 'haskell-indentation-dyn-overlays) + +(defun haskell-indentation-init-overlays (n) + "Makes sure that haskell-indentation-dyn-overlays contains at least N overlays." + (let* ((clen (length haskell-indentation-dyn-overlays)) + (needed (- n clen))) + (dotimes (n needed haskell-indentation-dyn-overlays) + (setq haskell-indentation-dyn-overlays + (cons (make-overlay 1 1) haskell-indentation-dyn-overlays))))) + +(defun haskell-indentation-unshow-overlays () + "Unshows all the overlays." + (mapc #'delete-overlay haskell-indentation-dyn-overlays)) + +(defun haskell-indentation-show-overlays () + "Put an underscore overlay at all the indentations points in +the current buffer." + (if (and (memq major-mode '(haskell-mode literate-haskell-mode)) + (memq 'haskell-indentation-mode minor-mode-list) + haskell-indentation-dyn-show-indentations) + (save-excursion + (let* ((columns (progn + (end-of-line) + (current-column))) + (ci (haskell-indentation-current-indentation)) + (allinds (save-excursion + (move-to-column ci); XXX: remove when haskell-indentation-find-indentations is fixed + ;; don't freak out on parse-error + (condition-case e + (haskell-indentation-find-indentations-safe) + (parse-error nil)))) + ;; indentations that are easy to show + (inds (cl-remove-if (lambda (i) (>= i columns)) allinds)) + ;; tricky indentations, that are after the current EOL + (overinds (cl-member-if (lambda (i) (>= i columns)) allinds)) + ;; +1: leave space for an extra overlay to show overinds + (overlays (haskell-indentation-init-overlays (+ 1 (length inds))))) + (while inds + (move-to-column (car inds)) + (overlay-put (car overlays) 'face 'haskell-indentation-show-normal-face) + (overlay-put (car overlays) 'after-string nil) + (move-overlay (car overlays) (point) (+ 1 (point))) + (setq inds (cdr inds)) + (setq overlays (cdr overlays))) + (when (and overinds + haskell-indentation-show-indentations-after-eol) + (let ((o (car overlays)) + (s (make-string (+ 1 (- (car (last overinds)) columns)) ? ))) + ;; needed for the cursor to be in the good position, see: + ;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2013-03/msg00079.html + (put-text-property 0 1 'cursor t s) + ;; color the whole line ending overlay with hl-line face if needed + (when (or hl-line-mode global-hl-line-mode) + (put-text-property 0 (length s) 'face 'hl-line s)) + ;; put in the underlines at the correct positions + (dolist (i overinds) + (put-text-property + (- i columns) (+ 1 (- i columns)) + 'face (if (or hl-line-mode global-hl-line-mode) + 'haskell-indentation-show-hl-line-face + 'haskell-indentation-show-normal-face) + s)) + (overlay-put o 'face nil) + (overlay-put o 'after-string s) + (end-of-line) + (move-overlay o (point) (point)))))))) + +(defun haskell-indentation-enable-show-indentations () + "Enable showing of indentation points in the current buffer." + (interactive) + (setq haskell-indentation-dyn-show-indentations t) + (add-hook 'change-major-mode-hook #'haskell-indentation-unshow-overlays nil t) + (add-hook 'pre-command-hook #'haskell-indentation-unshow-overlays nil t) + (add-hook 'post-command-hook #'haskell-indentation-show-overlays nil t)) + +(defun haskell-indentation-disable-show-indentations () + "Disable showing of indentation points in the current buffer." + (interactive) + (setq haskell-indentation-dyn-show-indentations nil) + (remove-hook 'post-command-hook #'haskell-indentation-show-overlays t) + (haskell-indentation-unshow-overlays) + (remove-hook 'change-major-mode-hook #'haskell-indentation-unshow-overlays t) + (remove-hook 'pre-command-hook #'haskell-indentation-unshow-overlays t)) + +;;---------------------------------------- parser starts here + +;; The parser is implemented als a recursive descent parser. Each +;; parser advances the point to after the expression it parses, and +;; sets the dynamic scoped variables containing the information about +;; the indentations. The dynamic scoping allows transparent +;; backtracking to previous states of these variables. A new state +;; can be set using LET. When the scope of this function ends, +;; the variable is automatically reverted to it's old value. + +;; This is basicly a performance hack. It would have been possible +;; to thread this state using a association-list through the parsers, but it +;; would be probably more complicated and slower due to the lack +;; of real closures in ELISP. +;; +;; When finished parsing, the tokenizer returns 'end-token, and +;; following-token is set to the token after point. The parser adds +;; its indentations to possible-indentations and returns to it's +;; parent, or exits non-locally by throwing parse-end, so that the +;; parent will not add new indentations to it. + +;; the parse 'state': +(defvar following-token) ;; the next token after parsing finished +(defvar current-token) ;;; the token at the current parser point or a pseudo-token (see haskell-indentation-read-next-token) +(defvar left-indent) ;; most left possible indentation +(defvar starter-indent) ;; column at a keyword +(defvar current-indent) ;; the most right indentation +(defvar layout-indent) ;; the column of the layout list +(defvar parse-line-number) ;; the number of lines parsed +(defvar possible-indentations) ;; the return value of the indentations +(defvar indentation-point) ;; where to stop parsing (defun haskell-indentation-goto-least-indentation () (beginning-of-line) - (if (eq haskell-literate 'bird) + (if (haskell-indentation-birdp) (catch 'return (while t (when (not (eq (char-after) ?>)) @@ -500,7 +540,7 @@ Preserves indentation and removes extra whitespace" possible-indentations)))) (defun haskell-indentation-first-indentation () - (if (eq haskell-literate 'bird) '(2) '(0))) + (if (haskell-indentation-birdp) '(2) '(0))) (defun haskell-indentation-find-indentations () (let ((ppss (syntax-ppss))) @@ -518,6 +558,11 @@ Preserves indentation and removes extra whitespace" (t (haskell-indentation-parse-to-indentations))))) +;; XXX: this is a hack, the parser shouldn't return nil without parse-error +(defun haskell-indentation-find-indentations-safe () + (or (haskell-indentation-find-indentations) + (haskell-indentation-first-indentation))) + (defconst haskell-indentation-unicode-tokens '(("→" . "->") ;; #x2192 RIGHTWARDS ARROW ("∷" . "::") ;; #x2237 PROPORTION @@ -531,24 +576,24 @@ Preserves indentation and removes extra whitespace" ("★" . "*")) ;; #x2605 BLACK STAR "Translation dictionary from UnicodeSyntax tokens to their ASCII representation.") +;; toplevel keywords (defconst haskell-indentation-toplevel-list '(("module" . haskell-indentation-module) ("data" . (lambda () (haskell-indentation-statement-right #'haskell-indentation-data))) ("type" . (lambda () (haskell-indentation-statement-right #'haskell-indentation-data))) ("newtype" . (lambda () (haskell-indentation-statement-right #'haskell-indentation-data))) ("class" . haskell-indentation-class-declaration) - ("instance" . haskell-indentation-class-declaration))) + ("instance" . haskell-indentation-class-declaration ))) +;; tokens in type declarations (defconst haskell-indentation-type-list '(("::" . (lambda () (haskell-indentation-with-starter - (lambda () (haskell-indentation-separated #'haskell-indentation-type "->" nil)) nil))) - ("(" . (lambda () (haskell-indentation-list #'haskell-indentation-type - ")" "," nil))) - ("[" . (lambda () (haskell-indentation-list #'haskell-indentation-type - "]" "," nil))) - ("{" . (lambda () (haskell-indentation-list #'haskell-indentation-type - "}" "," nil))))) + (lambda () (haskell-indentation-separated #'haskell-indentation-type "->"))))) + ("(" . (lambda () (haskell-indentation-list #'haskell-indentation-type ")" ","))) + ("[" . (lambda () (haskell-indentation-list #'haskell-indentation-type "]" ","))) + ("{" . (lambda () (haskell-indentation-list #'haskell-indentation-type "}" ","))))) +;; keywords in expressions (defconst haskell-indentation-expression-list '(("data" . haskell-indentation-data) ("type" . haskell-indentation-data) @@ -558,38 +603,38 @@ Preserves indentation and removes extra whitespace" '(haskell-indentation-declaration-layout "in" haskell-indentation-expression)))) ("do" . (lambda () (haskell-indentation-with-starter - #'haskell-indentation-expression-layout nil))) + #'haskell-indentation-expression-layout))) ("mdo" . (lambda () (haskell-indentation-with-starter - #'haskell-indentation-expression-layout nil))) + #'haskell-indentation-expression-layout))) ("rec" . (lambda () (haskell-indentation-with-starter - #'haskell-indentation-expression-layout nil))) + #'haskell-indentation-expression-layout))) ("case" . (lambda () (haskell-indentation-phrase '(haskell-indentation-expression "of" haskell-indentation-case-layout)))) ("\\" . (lambda () (haskell-indentation-with-starter - #'haskell-indentation-lambda-maybe-lambdacase nil))) + #'haskell-indentation-lambda-maybe-lambdacase))) ("proc" . (lambda () (haskell-indentation-phrase '(haskell-indentation-expression "->" haskell-indentation-expression)))) ("where" . (lambda () (haskell-indentation-with-starter #'haskell-indentation-declaration-layout nil t))) ("::" . (lambda () (haskell-indentation-with-starter - (lambda () (haskell-indentation-separated #'haskell-indentation-type "->" nil)) nil))) + (lambda () (haskell-indentation-separated #'haskell-indentation-type "->"))))) ("=" . (lambda () (haskell-indentation-statement-right #'haskell-indentation-expression))) ("<-" . (lambda () (haskell-indentation-statement-right #'haskell-indentation-expression))) - ("(" . (lambda () (haskell-indentation-list #'haskell-indentation-expression - ")" '(list "," "->") nil))) - ("[" . (lambda () (haskell-indentation-list #'haskell-indentation-expression - "]" "," "|"))) - ("{" . (lambda () (haskell-indentation-list #'haskell-indentation-expression - "}" "," nil))))) + ("(" . (lambda () (haskell-indentation-list #'haskell-indentation-expression ")" '(list "," "->")))) + ("[" . (lambda () (haskell-indentation-list #'haskell-indentation-expression "]" "," "|"))) + ("{" . (lambda () (haskell-indentation-list #'haskell-indentation-expression "}" ","))))) +;; a layout list with expressions, such as after do (defun haskell-indentation-expression-layout () (haskell-indentation-layout #'haskell-indentation-expression)) +;; a layout list with declarations, such as after where (defun haskell-indentation-declaration-layout () (haskell-indentation-layout #'haskell-indentation-declaration)) +;; a layout list with case expressions (defun haskell-indentation-case-layout () (haskell-indentation-layout #'haskell-indentation-case)) @@ -601,24 +646,24 @@ Preserves indentation and removes extra whitespace" (defun haskell-indentation-lambda-maybe-lambdacase () (if (string= current-token "case") (haskell-indentation-with-starter - #'haskell-indentation-case-layout nil) + #'haskell-indentation-case-layout) (haskell-indentation-phrase-rest '(haskell-indentation-expression "->" haskell-indentation-expression)))) +;; a functional dependency (defun haskell-indentation-fundep () (haskell-indentation-with-starter - (lambda () (haskell-indentation-separated - #'haskell-indentation-fundep1 "," nil)) - nil)) + (lambda () (haskell-indentation-separated #'haskell-indentation-fundep1 ",")))) (defun haskell-indentation-fundep1 () - (let ((current-indent (haskell-current-column))) + (let ((current-indent (current-column))) (while (member current-token '(value "->")) (haskell-indentation-read-next-token)) (when (and (eq current-token 'end-tokens) (member following-token '(value "->"))) (haskell-indentation-add-indentation current-indent)))) +;; the toplevel parser (defun haskell-indentation-toplevel () (haskell-indentation-layout (lambda () @@ -627,8 +672,9 @@ Preserves indentation and removes extra whitespace" (funcall (cdr parser)) (haskell-indentation-declaration)))))) +;; a type declaration (defun haskell-indentation-type () - (let ((current-indent (haskell-current-column))) + (let ((current-indent (current-column))) (catch 'return (while t (cond @@ -641,12 +687,12 @@ Preserves indentation and removes extra whitespace" "->" "(" "[" "{" "::")) (haskell-indentation-add-indentation current-indent)) (throw 'return nil)) - (t (let ((parser (assoc current-token haskell-indentation-type-list))) (if (not parser) (throw 'return nil) (funcall (cdr parser)))))))))) +;; a data or type declaration (defun haskell-indentation-data () (haskell-indentation-with-starter (lambda () @@ -655,13 +701,12 @@ Preserves indentation and removes extra whitespace" (haskell-indentation-type) (cond ((string= current-token "=") (haskell-indentation-with-starter - (lambda () (haskell-indentation-separated #'haskell-indentation-type "|" "deriving")) - nil)) - ((string= current-token "where") + (lambda () (haskell-indentation-separated #'haskell-indentation-type "|" "deriving")))) + ((string= current-token "where") (haskell-indentation-with-starter - #'haskell-indentation-expression-layout nil)))) - nil)) + #'haskell-indentation-expression-layout nil)))))) +;; a class declaration (defun haskell-indentation-class-declaration () (haskell-indentation-with-starter (lambda () @@ -670,18 +715,18 @@ Preserves indentation and removes extra whitespace" (haskell-indentation-fundep)) (when (string= current-token "where") (haskell-indentation-with-starter - #'haskell-indentation-declaration-layout nil))) - nil)) + #'haskell-indentation-declaration-layout nil))))) +;; a module declaration (defun haskell-indentation-module () (haskell-indentation-with-starter (lambda () - (let ((current-indent (haskell-current-column))) + (let ((current-indent (current-column))) (haskell-indentation-read-next-token) (when (string= current-token "(") (haskell-indentation-list #'haskell-indentation-module-export - ")" "," nil)) + ")" ",")) (when (eq current-token 'end-tokens) (haskell-indentation-add-indentation current-indent) (throw 'parse-end nil)) @@ -690,12 +735,12 @@ Preserves indentation and removes extra whitespace" (when (eq current-token 'end-tokens) (haskell-indentation-add-layout-indent) (throw 'parse-end nil)) - (haskell-indentation-layout #'haskell-indentation-toplevel)))) - nil)) + (haskell-indentation-layout #'haskell-indentation-toplevel)))))) +;; an export list (defun haskell-indentation-module-export () (cond ((string= current-token "module") - (let ((current-indent (haskell-current-column))) + (let ((current-indent (current-column))) (haskell-indentation-read-next-token) (cond ((eq current-token 'end-tokens) (haskell-indentation-add-indentation current-indent)) @@ -703,36 +748,41 @@ Preserves indentation and removes extra whitespace" (haskell-indentation-read-next-token))))) (t (haskell-indentation-type)))) -(defun haskell-indentation-list (parser end sep stmt-sep) +;; an list, pair or other expression containing multiple +;; items parsed by parser, separated by sep or stmt-sep, and ending in +;; end. +(defun haskell-indentation-list (parser end sep &optional stmt-sep) (haskell-indentation-with-starter `(lambda () (haskell-indentation-separated #',parser ,sep ,stmt-sep)) end)) -(defun haskell-indentation-with-starter (parser end &optional where-expr?) - (let ((starter-column (haskell-current-column)) +;; An expression starting with a keyword or paren. Skip the keyword +;; or paren. +(defun haskell-indentation-with-starter (parser &optional end where-expr?) + (let ((starter-column (current-column)) (current-indent current-indent) - (left-indent (if (= (haskell-current-column) (haskell-indentation-current-indentation)) - (haskell-current-column) left-indent))) + (left-indent (if (= (current-column) (haskell-indentation-current-indentation)) + (current-column) left-indent))) (haskell-indentation-read-next-token) (when (eq current-token 'end-tokens) - (if (equal following-token end) - (haskell-indentation-add-indentation starter-column) - (if where-expr? - (haskell-indentation-add-where-post-indent left-indent) - (haskell-indentation-add-indentation - (+ left-indent haskell-indentation-left-offset)))) + (cond ((equal following-token end) + (haskell-indentation-add-indentation starter-column)) ; indent before keyword or paren + (where-expr? + (haskell-indentation-add-where-post-indent left-indent)) ;; left indent + where post indent + (t + (haskell-indentation-add-left-indent))) (throw 'parse-end nil)) - (let* ((current-indent (haskell-current-column)) + (let* ((current-indent (current-column)) (starter-indent (min starter-column current-indent)) (left-indent (if end (+ current-indent haskell-indentation-starter-offset) left-indent))) (funcall parser) (cond ((eq current-token 'end-tokens) (when (equal following-token end) - (haskell-indentation-add-indentation starter-indent)) - (when end (throw 'parse-end nil))) ;; add no indentations + (haskell-indentation-add-indentation starter-indent)) ; indent before keyword or paren + (when end (throw 'parse-end nil))) ;; add no more indentations if we expect a closing keyword ((equal current-token end) (haskell-indentation-read-next-token)) ;; continue (end (parse-error "Illegal token: %s" current-token)))))) @@ -761,31 +811,25 @@ Preserves indentation and removes extra whitespace" ;; otherwise fallthrough )) +;; the right side of a statement. Sets current-indent +;; to the current column and cals the given parser. +;; if parsing ends here, set indentation to left-indent. (defun haskell-indentation-statement-right (parser) (haskell-indentation-read-next-token) (when (eq current-token 'end-tokens) - (haskell-indentation-add-indentation - (+ left-indent haskell-indentation-left-offset)) + (haskell-indentation-add-left-indent) + (haskell-indentation-add-indentation current-indent) (throw 'parse-end nil)) - (let ((current-indent (haskell-current-column))) + (let ((current-indent (current-column))) (funcall parser))) -(defun haskell-indentation-simple-declaration () - (haskell-indentation-expression) - (cond ((string= current-token "=") - (haskell-indentation-statement-right #'haskell-indentation-expression)) - ((string= current-token "::") - (haskell-indentation-statement-right #'haskell-indentation-type)) - ((and (eq current-token 'end-tokens) - (string= following-token "=")) - (haskell-indentation-add-indentation current-indent) - (throw 'parse-end nil)))) (defun haskell-indentation-guard () (setq left-indent (current-column)) (haskell-indentation-separated #'haskell-indentation-expression "," nil)) +;; function or type declaration (defun haskell-indentation-declaration () (haskell-indentation-separated #'haskell-indentation-expression "," nil) (cond ((string= current-token "|") @@ -798,17 +842,19 @@ Preserves indentation and removes extra whitespace" (haskell-indentation-add-indentation current-indent) (throw 'parse-end nil))))) +;; enter a layout list, where each layout item is parsed by parser. (defun haskell-indentation-layout (parser) (if (string= current-token "{") - (haskell-indentation-list parser "}" ";" nil) + (haskell-indentation-list parser "}" ";") ;; explicit layout (haskell-indentation-implicit-layout-list parser))) (defun haskell-indentation-expression-token (token) (member token '("if" "let" "do" "case" "\\" "(" "[" "::" value operator no-following-token))) +;; parse an expression until an unknown token is encountered. (defun haskell-indentation-expression () - (let ((current-indent (haskell-current-column))) + (let ((current-indent (current-column))) (catch 'return (while t (cond @@ -817,29 +863,32 @@ Preserves indentation and removes extra whitespace" ((eq current-token 'end-tokens) (cond ((string= following-token "where") - (haskell-indentation-add-where-pre-indent)) + (haskell-indentation-add-where-pre-indent)) ; before a where ((haskell-indentation-expression-token following-token) (haskell-indentation-add-indentation - current-indent))) + current-indent))) ;; a normal expression (throw 'return nil)) (t (let ((parser (assoc current-token haskell-indentation-expression-list))) (when (null parser) - (throw 'return nil)) - (funcall (cdr parser)) + (throw 'return nil)) ; not expression token, so exit + (funcall (cdr parser)) ; run parser (when (and (eq current-token 'end-tokens) (string= (car parser) "let") (= haskell-indentation-layout-offset current-indent) (haskell-indentation-expression-token following-token)) ;; inside a layout, after a let construct + ;; for example: do let a = 20 (haskell-indentation-add-layout-indent) (throw 'parse-end nil)) - (unless (member (car parser) '("(" "[" "{" "do" "case")) + + ;; after an 'open' expression such as 'if', exit + (unless (member (car parser) '("(" "[" "{" "do" "case")) (throw 'return nil))))))))) (defun haskell-indentation-test-indentations () (interactive) - (let ((indentations (save-excursion (haskell-indentation-find-indentations))) + (let ((indentations (save-excursion (haskell-indentation-find-indentations-safe))) (str "") (pos 0)) (while indentations @@ -852,47 +901,70 @@ Preserves indentation and removes extra whitespace" (newline) (insert str))) -(defun haskell-indentation-separated (parser separator stmt-separator) + +;; evaluate parser separated by separator and stmt-separator. +;; if stmt-separator is not nil, it will be used to set a +;; new starter-indent. +;; for example +;; [ i | i <- [1..10] +;; , +(defun haskell-indentation-separated (parser separator &optional stmt-separator) (catch 'return (while t (funcall parser) - (cond ((if (listp separator) (member current-token separator) (equal current-token separator)) + (cond ((if (listp separator) + (member current-token separator) + (equal current-token separator)) (haskell-indentation-at-separator)) ((equal current-token stmt-separator) - (setq starter-indent (haskell-current-column)) + (setq starter-indent (current-column)) (haskell-indentation-at-separator)) ((eq current-token 'end-tokens) (cond ((or (equal following-token separator) (equal following-token stmt-separator)) + ;; set an indentation before a separator, + ;; for example: + ;; [ 1 or [ 1 | a + ;; , 2 , 20 (haskell-indentation-add-indentation starter-indent) (throw 'parse-end nil))) (throw 'return nil)) (t (throw 'return nil)))))) +;; At a separator. +;; If at a new line, set starter-indent at the separator +;; and current-indent after the separator +;; For example: +;; l = [ 1 +;; , 2 +;; , -- start now here + (defun haskell-indentation-at-separator () (let ((separator-column - (and (= (haskell-current-column) (haskell-indentation-current-indentation)) - (haskell-current-column)))) + (and (= (current-column) (haskell-indentation-current-indentation)) + (current-column)))) (haskell-indentation-read-next-token) (cond ((eq current-token 'end-tokens) (haskell-indentation-add-indentation current-indent) (throw 'return nil)) (separator-column ;; on the beginning of the line - (setq current-indent (haskell-current-column)) + (setq current-indent (current-column)) (setq starter-indent separator-column))))) +;; An implicit layout list. This sets the layout-indent +;; variable to the column where the layout starts. (defun haskell-indentation-implicit-layout-list (parser) - (let* ((layout-indent (haskell-current-column)) - (current-indent (haskell-current-column)) - (left-indent (haskell-current-column))) + (let* ((layout-indent (current-column)) + (current-indent (current-column)) + (left-indent (current-column))) (catch 'return (while t (let ((left-indent left-indent)) (funcall parser)) - (cond ((member current-token '(layout-next ";")) + (cond ((member current-token '(layout-item ";")) (haskell-indentation-read-next-token)) ((eq current-token 'end-tokens) (when (or (haskell-indentation-expression-token following-token) @@ -927,7 +999,7 @@ Preserves indentation and removes extra whitespace" (defun haskell-indentation-phrase-rest (phrase) (let ((starter-line parse-line-number)) - (let ((current-indent (haskell-current-column))) + (let ((current-indent (current-column))) (funcall (car phrase))) (cond ((eq current-token 'end-tokens) @@ -944,7 +1016,7 @@ Preserves indentation and removes extra whitespace" ((null (cdr phrase))) ((equal (cadr phrase) current-token) - (let* ((on-new-line (= (haskell-current-column) (haskell-indentation-current-indentation))) + (let* ((on-new-line (= (current-column) (haskell-indentation-current-indentation))) (lines-between (- parse-line-number starter-line)) (left-indent (if (<= lines-between 0) left-indent @@ -1003,6 +1075,10 @@ Preserves indentation and removes extra whitespace" (haskell-indentation-push-indentation (+ indent haskell-indentation-where-post-offset))) +(defun haskell-indentation-add-left-indent () + (haskell-indentation-add-indentation + (+ left-indent haskell-indentation-left-offset))) + (defun haskell-indentation-push-indentation (indent) (when (or (null possible-indentations) (< indent (car possible-indentations))) @@ -1017,19 +1093,33 @@ Preserves indentation and removes extra whitespace" (indentation-point (mark))) (haskell-indentation-read-next-token))) +;; Go to the next token and set current-token to the next token. +;; The following symbols are used as pseudo tokens: +;; +;; 'layout-item: A new item in a layout list. The next token +;; will be the first token from the item. +;; 'layout-end: the end of a layout list. Next token will be +;; the first token after the layout list. +;; 'end-tokens: back at point where we started, following-token +;; will be set to the next token. +;; +;; if we are at a new line, parse-line is increased, and +;; current-indent and left-indent are set to the indentation +;; of the line. + (defun haskell-indentation-read-next-token () (cond ((eq current-token 'end-tokens) 'end-tokens) ((eq current-token 'layout-end) - (cond ((> layout-indent (haskell-current-column)) + (cond ((> layout-indent (current-column)) 'layout-end) - ((= layout-indent (haskell-current-column)) - (setq current-token 'layout-next)) - ((< layout-indent (haskell-current-column)) + ((= layout-indent (current-column)) + (setq current-token 'layout-item)) + ((< layout-indent (current-column)) (setq current-token (haskell-indentation-peek-token))))) - ((eq current-token 'layout-next) + ((eq current-token 'layout-item) (setq current-token (haskell-indentation-peek-token))) - ((> layout-indent (haskell-current-column)) + ((> layout-indent (current-column)) (setq current-token 'layout-end)) (t (haskell-indentation-skip-token) @@ -1040,15 +1130,15 @@ Preserves indentation and removes extra whitespace" (haskell-indentation-peek-token) 'no-following-token)) (setq current-token 'end-tokens)) - (when (= (haskell-current-column) (haskell-indentation-current-indentation)) + (when (= (current-column) (haskell-indentation-current-indentation)) ;; on a new line - (setq current-indent (haskell-current-column)) - (setq left-indent (haskell-current-column)) + (setq current-indent (current-column)) + (setq left-indent (current-column)) (setq parse-line-number (+ parse-line-number 1))) - (cond ((> layout-indent (haskell-current-column)) + (cond ((> layout-indent (current-column)) (setq current-token 'layout-end)) - ((= layout-indent (haskell-current-column)) - (setq current-token 'layout-next)) + ((= layout-indent (current-column)) + (setq current-token 'layout-item)) (t (setq current-token (haskell-indentation-peek-token)))))))) (defun haskell-indentation-peek-token () @@ -1083,7 +1173,7 @@ Preserves indentation and removes extra whitespace" ;; otherwise skip until space found (skip-syntax-forward "^-")) (forward-comment (buffer-size)) - (while (and (eq haskell-literate 'bird) + (while (and (haskell-indentation-birdp) (bolp) (eq (char-after) ?>)) (forward-char)