|
|
- ;;; haskell-align-imports.el --- Align the import lines in a Haskell file -*- lexical-binding: t -*-
-
- ;; Copyright (C) 2010 Chris Done
-
- ;; Author: Chris Done <chrisdone@gmail.com>
-
- ;; This file is not part of GNU Emacs.
-
- ;; This program 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 of
- ;; the License, or (at your option) any later version.
-
- ;; This program 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:
-
- ;; Consider the following imports list:
- ;;
- ;; import One
- ;; import Two as A
- ;; import qualified Three
- ;; import qualified Four as PRELUDE
- ;; import Five (A)
- ;; import Six (A,B)
- ;; import qualified Seven (A,B)
- ;; import "abc" Eight
- ;; import "abc" Nine as TWO
- ;; import qualified "abc" Ten
- ;; import qualified "defg" Eleven as PRELUDE
- ;; import "barmu" Twelve (A)
- ;; import "zotconpop" Thirteen (A,B)
- ;; import qualified "z" Fourteen (A,B)
- ;; import Fifteen hiding (A)
- ;; import Sixteen as TWO hiding (A)
- ;; import qualified Seventeen hiding (A)
- ;; import qualified Eighteen as PRELUDE hiding (A)
- ;; import "abc" Nineteen hiding (A)
- ;; import "abc" Twenty as TWO hiding (A)
- ;;
- ;; When haskell-align-imports is run within the same buffer, the
- ;; import list is transformed to:
- ;;
- ;; import "abc" Eight
- ;; import qualified Eighteen as PRELUDE hiding (A)
- ;; import qualified "defg" Eleven as PRELUDE
- ;; import Fifteen hiding (A)
- ;; import Five (A)
- ;; import qualified Four as PRELUDE
- ;; import qualified "z" Fourteen (A,B)
- ;; import "abc" Nine as TWO
- ;; import "abc" Nineteen hiding (A)
- ;; import One
- ;; import qualified Seven (A,B)
- ;; import qualified Seventeen hiding (A)
- ;; import Six (A,B)
- ;; import Sixteen as TWO hiding (A)
- ;; import qualified "abc" Ten
- ;; import "zotconpop" Thirteen (A,B)
- ;; import qualified Three
- ;; import "barmu" Twelve (A)
- ;; import "abc" Twenty as TWO hiding (A)
- ;; import Two as A
- ;;
- ;; If you want everything after module names to be padded out, too,
- ;; customize `haskell-align-imports-pad-after-name', and you'll get:
- ;;
- ;; import One
- ;; import Two as A
- ;; import qualified Three
- ;; import qualified Four as PRELUDE
- ;; import Five (A)
- ;; import Six (A,B)
- ;; import qualified Seven (A,B)
- ;; import "abc" Eight
- ;; import "abc" Nine as TWO
- ;; import qualified "abc" Ten
- ;; import qualified "defg" Eleven as PRELUDE
- ;; import "barmu" Twelve (A)
- ;; import "zotconpop" Thirteen (A,B)
- ;; import qualified "z" Fourteen (A,B)
- ;; import Fifteen hiding (A)
- ;; import Sixteen as TWO hiding (A)
- ;; import qualified Seventeen hiding (A)
- ;; import qualified Eighteen as PRELUDE hiding (A)
- ;; import "abc" Nineteen hiding (A)
- ;; import "abc" Twenty as TWO hiding (A)
-
- ;;; Code:
-
- (require 'cl-lib)
-
- (defvar haskell-align-imports-regexp
- (concat "^\\(import[ ]+\\)"
- "\\(qualified \\)?"
- "[ ]*\\(\"[^\"]*\" \\)?"
- "[ ]*\\([A-Za-z0-9_.']+\\)"
- "[ ]*\\([ ]*as [A-Z][^ ]*\\)?"
- "[ ]*\\((.*)\\)?"
- "\\([ ]*hiding (.*)\\)?"
- "\\( -- .*\\)?[ ]*$")
- "Regex used for matching components of an import.")
-
- (defcustom haskell-align-imports-pad-after-name
- nil
- "Pad layout after the module name also."
- :type 'boolean
- :group 'haskell-interactive)
-
- ;;;###autoload
- (defun haskell-align-imports ()
- "Align all the imports in the buffer."
- (interactive)
- (when (haskell-align-imports-line-match)
- (save-excursion
- (goto-char (point-min))
- (let* ((imports (haskell-align-imports-collect))
- (padding (haskell-align-imports-padding imports)))
- (mapc (lambda (x)
- (goto-char (cdr x))
- (delete-region (point) (line-end-position))
- (insert (haskell-align-imports-chomp
- (haskell-align-imports-fill padding (car x)))))
- imports))))
- nil)
-
- (defun haskell-align-imports-line-match ()
- "Try to match the current line as a regexp."
- (let ((line (buffer-substring-no-properties (line-beginning-position)
- (line-end-position))))
- (if (string-match "^import " line)
- line
- nil)))
-
- (defun haskell-align-imports-collect ()
- "Collect a list of mark / import statement pairs."
- (let ((imports '()))
- (while (not (or (equal (point) (point-max)) (haskell-align-imports-after-imports-p)))
- (let ((line (haskell-align-imports-line-match-it)))
- (when line
- (let ((match
- (haskell-align-imports-merge-parts
- (cl-loop for i from 1 to 8
- collect (haskell-align-imports-chomp (match-string i line))))))
- (setq imports (cons (cons match (line-beginning-position))
- imports)))))
- (forward-line))
- imports))
-
- (defun haskell-align-imports-merge-parts (l)
- "Merge together parts of an import statement that shouldn't be separated."
- (let ((parts (apply #'vector l))
- (join (lambda (ls)
- (cl-reduce (lambda (a b)
- (concat a
- (if (and (> (length a) 0)
- (> (length b) 0))
- " "
- "")
- b))
- ls))))
- (if haskell-align-imports-pad-after-name
- (list (funcall join (list (aref parts 0)
- (aref parts 1)
- (aref parts 2)))
- (aref parts 3)
- (funcall join (list (aref parts 4)
- (aref parts 5)
- (aref parts 6)))
- (aref parts 7))
- (list (funcall join (list (aref parts 0)
- (aref parts 1)
- (aref parts 2)))
- (funcall join (list (aref parts 3)
- (aref parts 4)
- (aref parts 5)
- (aref parts 6)
- (aref parts 7)))))))
-
- (defun haskell-align-imports-chomp (str)
- "Chomp leading and tailing whitespace from STR."
- (if str
- (replace-regexp-in-string "\\(^[[:space:]\n]*\\|[[:space:]\n]*$\\)" ""
- str)
- ""))
-
- (defun haskell-align-imports-padding (imports)
- "Find the padding for each part of the import statements."
- (if (null imports)
- imports
- (cl-reduce (lambda (a b) (cl-mapcar #'max a b))
- (mapcar (lambda (x) (mapcar #'length (car x)))
- imports))))
-
- (defun haskell-align-imports-fill (padding line)
- "Fill an import line using the padding worked out from all statements."
- (mapconcat #'identity
- (cl-mapcar (lambda (pad part)
- (if (> (length part) 0)
- (concat part (make-string (- pad (length part)) ? ))
- (make-string pad ? )))
- padding
- line)
- " "))
-
- (defun haskell-align-imports-line-match-it ()
- "Try to match the current line as a regexp."
- (let ((line (buffer-substring-no-properties (line-beginning-position)
- (line-end-position))))
- (if (string-match haskell-align-imports-regexp line)
- line
- nil)))
-
- (defun haskell-align-imports-after-imports-p ()
- "Are we after the imports list?"
- (save-excursion
- (goto-char (line-beginning-position))
- (let ((case-fold-search nil))
- (not (not (search-forward-regexp "\\( = \\|\\<instance\\>\\| :: \\| ∷ \\)"
- (line-end-position) t 1))))))
-
- (provide 'haskell-align-imports)
-
- ;;; haskell-align-imports.el ends here
|