;;; haskell-cabal.el --- Support for Cabal packages -*- lexical-binding: t -*-
|
|
|
|
;; Copyright © 2007, 2008 Stefan Monnier
|
|
;; 2016 Arthur Fayzrakhmanov
|
|
|
|
;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
|
|
|
|
;; 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 GNU Emacs; see the file COPYING. If not, write to
|
|
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
;; Boston, MA 02110-1301, USA.
|
|
|
|
;;; Commentary:
|
|
|
|
;; Todo:
|
|
|
|
;; - distinguish continued lines from indented lines.
|
|
;; - indent-line-function.
|
|
;; - outline-minor-mode.
|
|
|
|
;;; Code:
|
|
|
|
;; (defun haskell-cabal-extract-fields-from-doc ()
|
|
;; (require 'xml)
|
|
;; (let ((section (completing-read
|
|
;; "Section: "
|
|
;; '("general-fields" "library" "executable" "buildinfo"))))
|
|
;; (goto-char (point-min))
|
|
;; (search-forward (concat "<sect3 id=\"" section "\">")))
|
|
;; (let* ((xml (xml-parse-region
|
|
;; (progn (search-forward "<variablelist>") (match-beginning 0))
|
|
;; (progn (search-forward "</variablelist>") (point))))
|
|
;; (varlist (cl-remove-if-not 'consp (cl-cddar xml)))
|
|
;; (syms (mapcar (lambda (entry) (cl-caddr (assq 'literal (assq 'term entry))))
|
|
;; varlist))
|
|
;; (fields (mapcar (lambda (sym) (substring-no-properties sym 0 -1)) syms)))
|
|
;; fields))
|
|
|
|
(require 'cl-lib)
|
|
(require 'haskell-utils)
|
|
|
|
(defcustom haskell-hasktags-path "hasktags"
|
|
"Path to `hasktags' executable."
|
|
:group 'haskell
|
|
:type 'string)
|
|
|
|
(defcustom haskell-hasktags-arguments '("-e" "-x")
|
|
"Additional arguments for `hasktags' executable.
|
|
By default these are:
|
|
|
|
-e - generate ETAGS file
|
|
-x - generate additional information in CTAGS file."
|
|
:group 'haskell
|
|
:type '(list string))
|
|
|
|
(defconst haskell-cabal-general-fields
|
|
;; Extracted with (haskell-cabal-extract-fields-from-doc "general-fields")
|
|
'("name" "version" "cabal-version" "license" "license-file" "copyright"
|
|
"author" "maintainer" "stability" "homepage" "package-url" "synopsis"
|
|
"description" "category" "tested-with" "build-depends" "data-files"
|
|
"extra-source-files" "extra-tmp-files" "import"))
|
|
|
|
(defconst haskell-cabal-library-fields
|
|
;; Extracted with (haskell-cabal-extract-fields-from-doc "library")
|
|
'("exposed-modules"))
|
|
|
|
(defconst haskell-cabal-executable-fields
|
|
;; Extracted with (haskell-cabal-extract-fields-from-doc "executable")
|
|
'("executable" "main-is"))
|
|
|
|
(defconst haskell-cabal-buildinfo-fields
|
|
;; Extracted with (haskell-cabal-extract-fields-from-doc "buildinfo")
|
|
'("buildable" "other-modules" "hs-source-dirs" "extensions" "ghc-options"
|
|
"ghc-prof-options" "hugs-options" "nhc-options" "includes"
|
|
"install-includes" "include-dirs" "c-sources" "extra-libraries"
|
|
"extra-lib-dirs" "cc-options" "ld-options" "frameworks"))
|
|
|
|
(defvar haskell-cabal-mode-syntax-table
|
|
(let ((st (make-syntax-table)))
|
|
;; The comment syntax can't be described simply in syntax-table.
|
|
;; We could use font-lock-syntactic-keywords, but is it worth it?
|
|
;; (modify-syntax-entry ?- ". 12" st)
|
|
(modify-syntax-entry ?\n ">" st)
|
|
(modify-syntax-entry ?- "w" st)
|
|
st))
|
|
|
|
(defvar haskell-cabal-font-lock-keywords
|
|
;; The comment syntax can't be described simply in syntax-table.
|
|
;; We could use font-lock-syntactic-keywords, but is it worth it?
|
|
'(("^[ \t]*--.*" . font-lock-comment-face)
|
|
("^ *\\([^ \t:]+\\):" (1 font-lock-keyword-face))
|
|
("^\\(Library\\)[ \t]*\\({\\|$\\)" (1 font-lock-keyword-face))
|
|
("^\\(Executable\\|Test-Suite\\|Benchmark\\|Common\\)[ \t]+\\([^\n \t]*\\)"
|
|
(1 font-lock-keyword-face) (2 font-lock-function-name-face))
|
|
("^\\(Flag\\)[ \t]+\\([^\n \t]*\\)"
|
|
(1 font-lock-keyword-face) (2 font-lock-constant-face))
|
|
("^\\(Source-Repository\\)[ \t]+\\(head\\|this\\)"
|
|
(1 font-lock-keyword-face) (2 font-lock-constant-face))
|
|
("^ *\\(if\\)[ \t]+.*\\({\\|$\\)" (1 font-lock-keyword-face))
|
|
("^ *\\(}[ \t]*\\)?\\(else\\)[ \t]*\\({\\|$\\)"
|
|
(2 font-lock-keyword-face))
|
|
("\\<\\(?:True\\|False\\)\\>"
|
|
(0 font-lock-constant-face))))
|
|
|
|
(defvar haskell-cabal-buffers nil
|
|
"List of Cabal buffers.")
|
|
|
|
(defun haskell-cabal-buffers-clean (&optional buffer)
|
|
"Refresh list of known cabal buffers.
|
|
|
|
Check each buffer in variable `haskell-cabal-buffers' and remove
|
|
it from list if one of the following conditions are hold:
|
|
+ buffer is killed;
|
|
+ buffer's mode is not derived from `haskell-cabal-mode';
|
|
+ buffer is a BUFFER (if given)."
|
|
(let ((bufs ()))
|
|
(dolist (buf haskell-cabal-buffers)
|
|
(if (and (buffer-live-p buf)
|
|
(not (eq buf buffer))
|
|
(with-current-buffer buf (derived-mode-p 'haskell-cabal-mode)))
|
|
(push buf bufs)))
|
|
(setq haskell-cabal-buffers bufs)))
|
|
|
|
(defun haskell-cabal-unregister-buffer ()
|
|
"Exclude current buffer from global list of known cabal buffers."
|
|
(haskell-cabal-buffers-clean (current-buffer)))
|
|
|
|
;;;###autoload
|
|
(add-to-list 'auto-mode-alist '("\\.cabal\\'" . haskell-cabal-mode))
|
|
|
|
(defvar haskell-cabal-mode-map
|
|
(let ((map (make-sparse-keymap)))
|
|
(define-key map (kbd "C-c C-s") 'haskell-cabal-subsection-arrange-lines)
|
|
(define-key map (kbd "C-M-n") 'haskell-cabal-next-section)
|
|
(define-key map (kbd "C-M-p") 'haskell-cabal-previous-section)
|
|
(define-key map (kbd "M-n") 'haskell-cabal-next-subsection)
|
|
(define-key map (kbd "M-p") 'haskell-cabal-previous-subsection)
|
|
(define-key map (kbd "C-<down>") 'haskell-cabal-next-subsection)
|
|
(define-key map (kbd "C-<up>") 'haskell-cabal-previous-subsection)
|
|
(define-key map (kbd "C-c C-f") 'haskell-cabal-find-or-create-source-file)
|
|
(define-key map (kbd "M-g l") 'haskell-cabal-goto-library-section)
|
|
(define-key map (kbd "M-g e") 'haskell-cabal-goto-executable-section)
|
|
(define-key map (kbd "M-g b") 'haskell-cabal-goto-benchmark-section)
|
|
(define-key map (kbd "M-g o") 'haskell-cabal-goto-common-section)
|
|
(define-key map (kbd "M-g t") 'haskell-cabal-goto-test-suite-section)
|
|
map))
|
|
|
|
;;;###autoload
|
|
(define-derived-mode haskell-cabal-mode fundamental-mode "Haskell-Cabal"
|
|
"Major mode for Cabal package description files."
|
|
(setq-local font-lock-defaults
|
|
'(haskell-cabal-font-lock-keywords t t nil nil))
|
|
(add-to-list 'haskell-cabal-buffers (current-buffer))
|
|
(add-hook 'change-major-mode-hook 'haskell-cabal-unregister-buffer nil 'local)
|
|
(add-hook 'kill-buffer-hook 'haskell-cabal-unregister-buffer nil 'local)
|
|
(setq-local comment-start "-- ")
|
|
(setq-local comment-start-skip "\\(^[ \t]*\\)--[ \t]*")
|
|
(setq-local comment-end "")
|
|
(setq-local comment-end-skip "[ \t]*\\(\\s>\\|\n\\)")
|
|
(setq-local indent-line-function 'haskell-cabal-indent-line)
|
|
(setq indent-tabs-mode nil)
|
|
)
|
|
|
|
(make-obsolete 'haskell-cabal-get-setting
|
|
'haskell-cabal--get-field
|
|
"March 14, 2016")
|
|
(defalias 'haskell-cabal-get-setting 'haskell-cabal--get-field
|
|
"Try to read value of field with NAME from current buffer.
|
|
Obsolete function. Defined for backward compatibility. Use
|
|
`haskell-cabal--get-field' instead.")
|
|
|
|
(defun haskell-cabal--get-field (name)
|
|
"Try to read value of field with NAME from current buffer."
|
|
(save-excursion
|
|
(let ((case-fold-search t))
|
|
(goto-char (point-min))
|
|
(when (re-search-forward
|
|
(concat "^[ \t]*" (regexp-quote name)
|
|
":[ \t]*\\(.*\\(\n[ \t]+[ \t\n].*\\)*\\)")
|
|
nil t)
|
|
(let ((val (match-string 1))
|
|
(start 1))
|
|
(when (match-end 2) ;Multiple lines.
|
|
;; The documentation is not very precise about what to do about
|
|
;; the \n and the indentation: are they part of the value or
|
|
;; the encoding? I take the point of view that \n is part of
|
|
;; the value (so that values can span multiple lines as well),
|
|
;; and that only the first char in the indentation is part of
|
|
;; the encoding, the rest is part of the value (otherwise, lines
|
|
;; in the value cannot start with spaces or tabs).
|
|
(while (string-match "^[ \t]\\(?:\\.$\\)?" val start)
|
|
(setq start (1+ (match-beginning 0)))
|
|
(setq val (replace-match "" t t val))))
|
|
val)))))
|
|
|
|
|
|
(make-obsolete 'haskell-cabal-guess-setting
|
|
'haskell-cabal-get-field
|
|
"March 14, 2016")
|
|
(defalias 'haskell-cabal-guess-setting 'haskell-cabal-get-field
|
|
"Read the value of field with NAME from project's cabal file.
|
|
Obsolete function. Defined for backward compatibility. Use
|
|
`haskell-cabal-get-field' instead.")
|
|
|
|
;;;###autoload
|
|
(defun haskell-cabal-get-field (name)
|
|
"Read the value of field with NAME from project's cabal file.
|
|
If there is no valid .cabal file to get the setting from (or
|
|
there is no corresponding setting with that name in the .cabal
|
|
file), then this function returns nil."
|
|
(interactive)
|
|
(when (and name buffer-file-name)
|
|
(let ((cabal-file (haskell-cabal-find-file)))
|
|
(when (and cabal-file (file-readable-p cabal-file))
|
|
(with-temp-buffer
|
|
(insert-file-contents cabal-file)
|
|
(haskell-cabal--get-field name))))))
|
|
|
|
;;;###autoload
|
|
(defun haskell-cabal-get-dir (&optional use-defaults)
|
|
"Get the Cabal dir for a new project. Various ways of figuring this out,
|
|
and indeed just prompting the user. Do them all."
|
|
(let* ((file (haskell-cabal-find-file))
|
|
(dir (if file (file-name-directory file) default-directory)))
|
|
(if use-defaults
|
|
dir
|
|
(haskell-utils-read-directory-name
|
|
(format "Cabal dir%s: " (if file (format " (guessed from %s)" (file-relative-name file)) ""))
|
|
dir))))
|
|
|
|
(defun haskell-cabal-compute-checksum (dir)
|
|
"Compute MD5 checksum of package description file in DIR.
|
|
Return nil if no Cabal description file could be located via
|
|
`haskell-cabal-find-pkg-desc'."
|
|
(let ((cabal-file (haskell-cabal-find-pkg-desc dir)))
|
|
(when cabal-file
|
|
(with-temp-buffer
|
|
(insert-file-contents cabal-file)
|
|
(md5 (buffer-string))))))
|
|
|
|
(defun haskell-cabal-find-file (&optional dir)
|
|
"Search for package description file upwards starting from DIR.
|
|
If DIR is nil, `default-directory' is used as starting point for
|
|
directory traversal. Upward traversal is aborted if file owner
|
|
changes. Uses `haskell-cabal-find-pkg-desc' internally."
|
|
(let ((use-dir (or dir default-directory)))
|
|
(while (and use-dir (not (file-directory-p use-dir)))
|
|
(setq use-dir (file-name-directory (directory-file-name use-dir))))
|
|
(when use-dir
|
|
(catch 'found
|
|
(let ((user (nth 2 (file-attributes use-dir)))
|
|
;; Abbreviate, so as to stop when we cross ~/.
|
|
(root (abbreviate-file-name use-dir)))
|
|
;; traverse current dir up to root as long as file owner doesn't change
|
|
(while (and root (equal user (nth 2 (file-attributes root))))
|
|
(let ((cabal-file (haskell-cabal-find-pkg-desc root)))
|
|
(when cabal-file
|
|
(throw 'found cabal-file)))
|
|
|
|
(let ((proot (file-name-directory (directory-file-name root))))
|
|
(if (equal proot root) ;; fix-point reached?
|
|
(throw 'found nil)
|
|
(setq root proot))))
|
|
nil)))))
|
|
|
|
(defun haskell-cabal-find-pkg-desc (dir &optional allow-multiple)
|
|
"Find a package description file in the directory DIR.
|
|
Returns nil if none or multiple \".cabal\" files were found. If
|
|
ALLOW-MULTIPLE is non nil, in case of multiple \".cabal\" files,
|
|
a list is returned instead of failing with a nil result."
|
|
;; This is basically a port of Cabal's
|
|
;; Distribution.Simple.Utils.findPackageDesc function
|
|
;; http://hackage.haskell.org/packages/archive/Cabal/1.16.0.3/doc/html/Distribution-Simple-Utils.html
|
|
;; but without the exception throwing.
|
|
(let* ((cabal-files
|
|
(cl-remove-if 'file-directory-p
|
|
(cl-remove-if-not 'file-exists-p
|
|
(directory-files dir t ".\\.cabal\\'")))))
|
|
(cond
|
|
((= (length cabal-files) 1) (car cabal-files)) ;; exactly one candidate found
|
|
(allow-multiple cabal-files) ;; pass-thru multiple candidates
|
|
(t nil))))
|
|
|
|
(defun haskell-cabal-find-dir (&optional dir)
|
|
"Like `haskell-cabal-find-file' but returns directory instead.
|
|
See `haskell-cabal-find-file' for meaning of DIR argument."
|
|
(let ((cabal-file (haskell-cabal-find-file dir)))
|
|
(when cabal-file
|
|
(file-name-directory cabal-file))))
|
|
|
|
;;;###autoload
|
|
(defun haskell-cabal-visit-file (other-window)
|
|
"Locate and visit package description file for file visited by current buffer.
|
|
This uses `haskell-cabal-find-file' to locate the closest
|
|
\".cabal\" file and open it. This command assumes a common Cabal
|
|
project structure where the \".cabal\" file is in the top-folder
|
|
of the project, and all files related to the project are in or
|
|
below the top-folder. If called with non-nil prefix argument
|
|
OTHER-WINDOW use `find-file-other-window'."
|
|
(interactive "P")
|
|
;; Note: We aren't allowed to rely on haskell-session here (which,
|
|
;; in pathological cases, can have a different .cabal file
|
|
;; associated with the current buffer)
|
|
(if buffer-file-name
|
|
(let ((cabal-file (haskell-cabal-find-file (file-name-directory buffer-file-name))))
|
|
(if cabal-file
|
|
(if other-window
|
|
(find-file-other-window cabal-file)
|
|
(find-file cabal-file))
|
|
(error "Could not locate \".cabal\" file for %S" buffer-file-name)))
|
|
(error "Cannot locate \".cabal\" file for buffers not visiting any file")))
|
|
|
|
(defvar haskell-cabal-commands
|
|
'("install"
|
|
"update"
|
|
"list"
|
|
"info"
|
|
"upgrade"
|
|
"fetch"
|
|
"unpack"
|
|
"check"
|
|
"sdist"
|
|
"upload"
|
|
"report"
|
|
"init"
|
|
"configure"
|
|
"build"
|
|
"copy"
|
|
"haddock"
|
|
"clean"
|
|
"hscolour"
|
|
"register"
|
|
"test"
|
|
"help"
|
|
"run"))
|
|
|
|
;;;###autoload
|
|
(defgroup haskell-cabal nil
|
|
"Haskell cabal files"
|
|
:group 'haskell
|
|
)
|
|
|
|
(defconst haskell-cabal-section-header-regexp "^[[:alnum:]]" )
|
|
(defconst haskell-cabal-subsection-header-regexp "^[ \t]*[[:alnum:]]\\w*:")
|
|
(defconst haskell-cabal-comment-regexp "^[ \t]*--")
|
|
(defconst haskell-cabal-empty-regexp "^[ \t]*$")
|
|
(defconst haskell-cabal-conditional-regexp "^[ \t]*\\(\\if\\|else\\|}\\)")
|
|
|
|
(defun haskell-cabal-classify-line ()
|
|
"Classify the current line into 'section-header 'subsection-header 'section-data 'comment and 'empty '"
|
|
(save-excursion
|
|
(beginning-of-line)
|
|
(cond
|
|
((looking-at haskell-cabal-subsection-header-regexp ) 'subsection-header)
|
|
((looking-at haskell-cabal-section-header-regexp) 'section-header)
|
|
((looking-at haskell-cabal-comment-regexp) 'comment)
|
|
((looking-at haskell-cabal-empty-regexp ) 'empty)
|
|
((looking-at haskell-cabal-conditional-regexp ) 'conditional)
|
|
(t 'section-data))))
|
|
|
|
(defun haskell-cabal-header-p ()
|
|
"Is the current line a section or subsection header?"
|
|
(cl-case (haskell-cabal-classify-line)
|
|
((section-header subsection-header) t)))
|
|
|
|
(defun haskell-cabal-section-header-p ()
|
|
"Is the current line a section or subsection header?"
|
|
(cl-case (haskell-cabal-classify-line)
|
|
((section-header) t)))
|
|
|
|
|
|
(defun haskell-cabal-section-beginning ()
|
|
"Find the beginning of the current section"
|
|
(save-excursion
|
|
(while (not (or (bobp) (haskell-cabal-section-header-p)))
|
|
(forward-line -1))
|
|
(point)))
|
|
|
|
(defun haskell-cabal-beginning-of-section ()
|
|
"go to the beginning of the section"
|
|
(interactive)
|
|
(goto-char (haskell-cabal-section-beginning))
|
|
)
|
|
|
|
(defun haskell-cabal-section-end ()
|
|
"Find the end of the current section"
|
|
(interactive)
|
|
(save-excursion
|
|
(if (re-search-forward "\n\\([ \t]*\n\\)*[[:alnum:]]" nil t)
|
|
(match-beginning 0)
|
|
(point-max))))
|
|
|
|
(defun haskell-cabal-end-of-section ()
|
|
"go to the end of the section"
|
|
(interactive)
|
|
(goto-char (haskell-cabal-section-end)))
|
|
|
|
(defun haskell-cabal-next-section ()
|
|
"Go to the next section"
|
|
(interactive)
|
|
(when (haskell-cabal-section-header-p) (forward-line))
|
|
(while (not (or (eobp) (haskell-cabal-section-header-p)))
|
|
(forward-line)))
|
|
|
|
(defun haskell-cabal-previous-section ()
|
|
"Go to the next section"
|
|
(interactive)
|
|
(when (haskell-cabal-section-header-p) (forward-line -1))
|
|
(while (not (or (bobp) (haskell-cabal-section-header-p)))
|
|
(forward-line -1)))
|
|
|
|
(defun haskell-cabal-subsection-end ()
|
|
"find the end of the current subsection"
|
|
(save-excursion
|
|
(haskell-cabal-beginning-of-subsection)
|
|
(forward-line)
|
|
(while (and (not (eobp))
|
|
(member (haskell-cabal-classify-line) '(empty section-data)))
|
|
(forward-line))
|
|
(unless (eobp) (forward-line -1))
|
|
(while (and (equal (haskell-cabal-classify-line) 'empty)
|
|
(not (bobp)))
|
|
(forward-line -1))
|
|
(end-of-line)
|
|
(point)))
|
|
|
|
(defun haskell-cabal-end-of-subsection ()
|
|
"go to the end of the current subsection"
|
|
(interactive)
|
|
(goto-char (haskell-cabal-subsection-end)))
|
|
|
|
(defun haskell-cabal-section ()
|
|
"Get the name and data of the associated section"
|
|
(save-excursion
|
|
(haskell-cabal-beginning-of-section)
|
|
(when (and (haskell-cabal-section-header-p)
|
|
(looking-at "^\\(\\w+\\)[ \t]*\\(.*\\)$"))
|
|
(list :name (match-string-no-properties 1)
|
|
:value (match-string-no-properties 2)
|
|
:beginning (match-beginning 0)
|
|
:end (haskell-cabal-section-end)))))
|
|
|
|
|
|
(defun haskell-cabal-subsection ()
|
|
"Get the name and bounds of of the current subsection"
|
|
(save-excursion
|
|
(haskell-cabal-beginning-of-subsection)
|
|
(when (looking-at "\\([ \t]*\\(\\w*\\):\\)[ \t]*")
|
|
(list :name (match-string-no-properties 2)
|
|
:beginning (match-end 0)
|
|
:end (save-match-data (haskell-cabal-subsection-end))
|
|
:data-start-column (save-excursion (goto-char (match-end 0))
|
|
(current-column))
|
|
:data-indent-column (save-excursion (goto-char (match-end 0))
|
|
(when (looking-at "\n +\\(\\w*\\)") (goto-char (match-beginning 1)))
|
|
(current-column)
|
|
)))))
|
|
|
|
|
|
(defun haskell-cabal-section-name (section)
|
|
(plist-get section :name))
|
|
|
|
(defun haskell-cabal-section-value (section)
|
|
(plist-get section :value))
|
|
|
|
(defun haskell-cabal-section-start (section)
|
|
(plist-get section :beginning))
|
|
|
|
(defun haskell-cabal-section-data-start-column (section)
|
|
(plist-get section :data-start-column))
|
|
|
|
(defun haskell-cabal-section-data-indent-column (section)
|
|
(plist-get section :data-indent-column))
|
|
|
|
(defun haskell-cabal-map-component-type (component-type)
|
|
"Map from cabal file COMPONENT-TYPE to build command component-type."
|
|
(let ((component-type (downcase component-type)))
|
|
(cond ((equal component-type "executable") "exe")
|
|
((equal component-type "test-suite") "test")
|
|
((equal component-type "benchmark") "bench"))))
|
|
|
|
(defun haskell-cabal-enum-targets (&optional process-type)
|
|
"Enumerate .cabal targets. PROCESS-TYPE determines the format of the returned target."
|
|
(let ((cabal-file (haskell-cabal-find-file))
|
|
(process-type (if process-type process-type 'ghci)))
|
|
(when (and cabal-file (file-readable-p cabal-file))
|
|
(with-temp-buffer
|
|
(insert-file-contents cabal-file)
|
|
(haskell-cabal-mode)
|
|
(goto-char (point-min))
|
|
(let ((matches)
|
|
(package-name (haskell-cabal--get-field "name")))
|
|
(haskell-cabal-next-section)
|
|
(while (not (eobp))
|
|
(if (haskell-cabal-source-section-p (haskell-cabal-section))
|
|
(let* ((section (haskell-cabal-section))
|
|
(component-type (haskell-cabal-section-name section))
|
|
(val (car (split-string
|
|
(haskell-cabal-section-value section)))))
|
|
(if (equal (downcase component-type) "library")
|
|
(let ((lib-target (if (eq 'stack-ghci process-type)
|
|
(concat package-name ":lib")
|
|
(concat "lib:" package-name))))
|
|
(push lib-target matches))
|
|
(push (concat (when (eq 'stack-ghci process-type)
|
|
(concat package-name ":"))
|
|
(haskell-cabal-map-component-type component-type)
|
|
":"
|
|
val)
|
|
matches))))
|
|
(haskell-cabal-next-section))
|
|
(reverse matches))))))
|
|
|
|
(defmacro haskell-cabal-with-subsection (subsection replace &rest funs)
|
|
"Copy subsection data into a temporary buffer, save indentation
|
|
and execute FORMS
|
|
|
|
If REPLACE is non-nil the subsection data is replaced with the
|
|
resulting buffer-content"
|
|
(let ((section (make-symbol "section"))
|
|
(beg (make-symbol "beg"))
|
|
(end (make-symbol "end"))
|
|
(start-col (make-symbol "start-col"))
|
|
(section-data (make-symbol "section-data")))
|
|
`(let* ((,section ,subsection)
|
|
(,beg (plist-get ,section :beginning))
|
|
(,end (plist-get ,section :end))
|
|
(,start-col (plist-get ,section :data-start-column))
|
|
(,section-data (buffer-substring ,beg ,end)))
|
|
(save-excursion
|
|
(prog1
|
|
(with-temp-buffer
|
|
(setq indent-tabs-mode nil)
|
|
(indent-to ,start-col)
|
|
(insert ,section-data)
|
|
(goto-char (point-min))
|
|
(prog1
|
|
(progn (haskell-cabal-save-indentation ,@funs))
|
|
(goto-char (point-min))
|
|
(when (looking-at (format "[ ]\\{0,%d\\}" (1+ ,start-col)))
|
|
(replace-match ""))
|
|
|
|
(setq ,section-data (buffer-substring (point-min) (point-max)))))
|
|
,@(when replace
|
|
`((delete-region ,beg ,end)
|
|
(goto-char ,beg)
|
|
(insert ,section-data))))))))
|
|
|
|
(defmacro haskell-cabal-each-line (&rest fun)
|
|
"Execute FORMS on each line"
|
|
`(save-excursion
|
|
(while (< (point) (point-max))
|
|
,@fun
|
|
(forward-line))))
|
|
|
|
(defun haskell-cabal-chomp-line ()
|
|
"Remove leading and trailing whitespaces from current line"
|
|
(beginning-of-line)
|
|
(when (looking-at "^[ \t]*\\([^ \t]\\|\\(?:[^ \t].*[^ \t]\\)\\)[ \t]*$")
|
|
(replace-match (match-string 1) nil t)
|
|
t))
|
|
|
|
|
|
(defun haskell-cabal-min-indentation (&optional beg end)
|
|
"Compute largest common whitespace prefix of each line in between BEG and END"
|
|
(save-excursion
|
|
(goto-char (or beg (point-min)))
|
|
(let ((min-indent nil))
|
|
(while (< (point) (or end (point-max)))
|
|
(let ((indent (current-indentation)))
|
|
(if (and (not (haskell-cabal-ignore-line-p))
|
|
(or (not min-indent)
|
|
(< indent min-indent)))
|
|
(setq min-indent indent)))
|
|
(forward-line))
|
|
min-indent)))
|
|
|
|
(defun haskell-cabal-ignore-line-p ()
|
|
"Does line only contain whitespaces and comments?"
|
|
(save-excursion
|
|
(beginning-of-line)
|
|
(looking-at "^[ \t]*\\(?:--.*\\)?$")))
|
|
|
|
(defun haskell-cabal-kill-indentation ()
|
|
"Remove longest common whitespace prefix from each line"
|
|
(goto-char (point-min))
|
|
(let ((indent (haskell-cabal-min-indentation)))
|
|
(haskell-cabal-each-line (unless (haskell-cabal-ignore-line-p)
|
|
(delete-char indent)) )
|
|
indent))
|
|
|
|
(defun haskell-cabal-add-indentation (indent)
|
|
(goto-char (point-min))
|
|
(haskell-cabal-each-line
|
|
(unless (haskell-cabal-ignore-line-p)
|
|
(indent-to indent))))
|
|
|
|
|
|
(defmacro haskell-cabal-save-indentation (&rest funs)
|
|
"Strip indentation from each line, execute FORMS and reinstate indentation
|
|
so that the indentation of the FIRST LINE matches"
|
|
(let ((old-l1-indent (make-symbol "new-l1-indent"))
|
|
(new-l1-indent (make-symbol "old-l1-indent")))
|
|
`(let ( (,old-l1-indent (save-excursion
|
|
(goto-char (point-min))
|
|
(current-indentation))))
|
|
(unwind-protect
|
|
(progn
|
|
(haskell-cabal-kill-indentation)
|
|
,@funs)
|
|
(progn
|
|
(goto-char (point-min))
|
|
(let ((,new-l1-indent (current-indentation)))
|
|
(haskell-cabal-add-indentation (- ,old-l1-indent
|
|
,new-l1-indent))))))))
|
|
|
|
(defun haskell-cabal-comma-separatorp (pos)
|
|
"Return non-nil when the char at POS is a comma separator.
|
|
Characters that are not a comma, or commas inside a commment or
|
|
string, are not comma separators."
|
|
(when (eq (char-after pos) ?,)
|
|
(let ((ss (syntax-ppss pos)))
|
|
(not
|
|
(or
|
|
;; inside a string
|
|
(nth 3 ss)
|
|
;; inside a comment
|
|
(nth 4 ss))))))
|
|
|
|
(defun haskell-cabal-strip-list-and-detect-style ()
|
|
"Strip commas from a comma-separated list.
|
|
Detect and return the comma style. The possible options are:
|
|
|
|
before: a comma at the start of each line (except the first), e.g.
|
|
Foo
|
|
, Bar
|
|
|
|
after: a comma at the end of each line (except the last), e.g.
|
|
Foo,
|
|
Bar
|
|
|
|
single: everything on a single line, but comma-separated, e.g.
|
|
Foo, Bar
|
|
|
|
nil: no commas, e.g.
|
|
Foo Bar
|
|
|
|
If the styles are mixed, the position of the first comma
|
|
determines the style. If there is only one element then `after'
|
|
style is assumed."
|
|
(let (comma-style)
|
|
;; split list items on single line
|
|
(goto-char (point-min))
|
|
(while (re-search-forward
|
|
"\\([^ \t,\n]\\)[ \t]*\\(,\\)[ \t]*\\([^ \t,\n]\\)" nil t)
|
|
(when (haskell-cabal-comma-separatorp (match-beginning 2))
|
|
(setq comma-style 'single)
|
|
(replace-match "\\1\n\\3" nil nil)))
|
|
;; remove commas before
|
|
(goto-char (point-min))
|
|
(while (re-search-forward "^\\([ \t]*\\),\\([ \t]*\\)" nil t)
|
|
(setq comma-style 'before)
|
|
(replace-match "" nil nil))
|
|
;; remove trailing commas
|
|
(goto-char (point-min))
|
|
(while (re-search-forward ",[ \t]*$" nil t)
|
|
(unless (eq comma-style 'before)
|
|
(setq comma-style 'after))
|
|
(replace-match "" nil nil))
|
|
|
|
;; if there is just one line then set default as 'after
|
|
(unless comma-style
|
|
(goto-char (point-min))
|
|
(forward-line)
|
|
(when (eobp)
|
|
(setq comma-style 'after)))
|
|
(goto-char (point-min))
|
|
|
|
(haskell-cabal-each-line (haskell-cabal-chomp-line))
|
|
comma-style))
|
|
|
|
(defun haskell-cabal-listify (comma-style)
|
|
"Add commas so that the buffer contains a comma-separated list.
|
|
Respect the COMMA-STYLE, see
|
|
`haskell-cabal-strip-list-and-detect-style' for the possible
|
|
styles."
|
|
(cl-case comma-style
|
|
('before
|
|
(goto-char (point-min))
|
|
(while (haskell-cabal-ignore-line-p) (forward-line))
|
|
(indent-to 2)
|
|
(forward-line)
|
|
(haskell-cabal-each-line
|
|
(unless (haskell-cabal-ignore-line-p)
|
|
(insert ", "))))
|
|
('after
|
|
(goto-char (point-max))
|
|
(while (equal 0 (forward-line -1))
|
|
(unless (haskell-cabal-ignore-line-p)
|
|
(end-of-line)
|
|
(insert ",")
|
|
(beginning-of-line))))
|
|
('single
|
|
(goto-char (point-min))
|
|
(while (not (eobp))
|
|
(end-of-line)
|
|
(unless (eobp)
|
|
(insert ", ")
|
|
(delete-char 1)
|
|
(just-one-space))))))
|
|
|
|
(defmacro haskell-cabal-with-cs-list (&rest funs)
|
|
"Format the buffer so that each line contains a list element.
|
|
Respect the comma style."
|
|
(let ((comma-style (make-symbol "comma-style")))
|
|
`(let ((,comma-style
|
|
(save-excursion
|
|
(haskell-cabal-strip-list-and-detect-style))))
|
|
(unwind-protect (progn ,@funs)
|
|
(haskell-cabal-listify ,comma-style)))))
|
|
|
|
|
|
(defun haskell-cabal-sort-lines-key-fun ()
|
|
(when (looking-at "[ \t]*--[ \t,]*")
|
|
(goto-char (match-end 0)))
|
|
nil)
|
|
|
|
(defmacro haskell-cabal-save-position (&rest forms)
|
|
"Save position as mark, execute FORMs and go back to mark"
|
|
`(prog2
|
|
(haskell-cabal-mark)
|
|
(progn ,@forms)
|
|
(haskell-cabal-goto-mark)
|
|
(haskell-cabal-remove-mark)))
|
|
|
|
(defun haskell-cabal-sort-lines-depends-compare (key1 key2)
|
|
(let* ((key1str (buffer-substring (car key1) (cdr key1)))
|
|
(key2str (buffer-substring (car key2) (cdr key2)))
|
|
(base-regex "^[ \t]*base\\($\\|[^[:alnum:]-]\\)"))
|
|
(cond
|
|
((string-match base-regex key1str) t)
|
|
((string-match base-regex key2str) nil)
|
|
(t (string< key1str key2str)))))
|
|
|
|
(defun haskell-cabal-subsection-arrange-lines ()
|
|
"Sort lines of current subsection"
|
|
(interactive)
|
|
(haskell-cabal-save-position
|
|
(let* ((subsection (haskell-cabal-section-name (haskell-cabal-subsection)))
|
|
(compare-lines (if (string= (downcase subsection) "build-depends")
|
|
'haskell-cabal-sort-lines-depends-compare
|
|
nil)))
|
|
(haskell-cabal-with-subsection
|
|
(haskell-cabal-subsection) t
|
|
(haskell-cabal-with-cs-list
|
|
(sort-subr nil 'forward-line 'end-of-line
|
|
'haskell-cabal-sort-lines-key-fun
|
|
'end-of-line
|
|
compare-lines
|
|
))))))
|
|
|
|
(defun haskell-cabal-subsection-beginning ()
|
|
"find the beginning of the current subsection"
|
|
(save-excursion
|
|
(while (and (not (bobp))
|
|
(not (haskell-cabal-header-p)))
|
|
(forward-line -1))
|
|
(back-to-indentation)
|
|
(point)))
|
|
|
|
(defun haskell-cabal-beginning-of-subsection ()
|
|
"go to the beginning of the current subsection"
|
|
(interactive)
|
|
(goto-char (haskell-cabal-subsection-beginning)))
|
|
|
|
(defun haskell-cabal-next-subsection ()
|
|
"go to the next subsection"
|
|
(interactive)
|
|
(if (haskell-cabal-header-p) (forward-line))
|
|
(while (and (not (eobp))
|
|
(not (haskell-cabal-header-p)))
|
|
(forward-line))
|
|
(haskell-cabal-forward-to-line-entry))
|
|
|
|
(defun haskell-cabal-previous-subsection ()
|
|
"go to the previous subsection"
|
|
(interactive)
|
|
(if (haskell-cabal-header-p) (forward-line -1))
|
|
(while (and (not (bobp))
|
|
(not (haskell-cabal-header-p)))
|
|
(forward-line -1))
|
|
(haskell-cabal-forward-to-line-entry)
|
|
)
|
|
|
|
|
|
(defun haskell-cabal-find-subsection-by (section pred)
|
|
"Find subsection with name NAME"
|
|
(save-excursion
|
|
(when section (goto-char (haskell-cabal-section-start section)))
|
|
(let* ((end (if section (haskell-cabal-section-end) (point-max)))
|
|
(found nil))
|
|
(while (and (< (point) end)
|
|
(not found))
|
|
(let ((subsection (haskell-cabal-subsection)))
|
|
(when (and subsection (funcall pred subsection))
|
|
(setq found subsection)))
|
|
(haskell-cabal-next-subsection))
|
|
found)))
|
|
|
|
(defun haskell-cabal-find-subsection (section name)
|
|
"Find subsection with name NAME"
|
|
(let ((downcase-name (downcase name)))
|
|
(haskell-cabal-find-subsection-by
|
|
section
|
|
`(lambda (subsection)
|
|
(string= (downcase (haskell-cabal-section-name subsection))
|
|
,downcase-name)))))
|
|
|
|
(defun haskell-cabal-goto-subsection (name)
|
|
(let ((subsection (haskell-cabal-find-subsection (haskell-cabal-section) name)))
|
|
(when subsection
|
|
(goto-char (haskell-cabal-section-start subsection)))))
|
|
|
|
(defun haskell-cabal-goto-exposed-modules ()
|
|
(interactive)
|
|
(haskell-cabal-goto-subsection "exposed-modules"))
|
|
|
|
(defun haskell-cabal-subsection-entry-list (section name)
|
|
"Get the data of a subsection as a list"
|
|
(let ((subsection (haskell-cabal-find-subsection section name)))
|
|
(when subsection
|
|
(haskell-cabal-with-subsection
|
|
subsection nil
|
|
(haskell-cabal-with-cs-list
|
|
(delete-matching-lines
|
|
(format "\\(?:%s\\)\\|\\(?:%s\\)"
|
|
haskell-cabal-comment-regexp
|
|
haskell-cabal-empty-regexp)
|
|
(point-min) (point-max))
|
|
(split-string (buffer-substring-no-properties (point-min) (point-max))
|
|
"\n" t))))))
|
|
|
|
(defun haskell-cabal-remove-mark ()
|
|
(remove-list-of-text-properties (point-min) (point-max)
|
|
'(haskell-cabal-marker)))
|
|
|
|
|
|
(defun haskell-cabal-mark ()
|
|
"Mark the current position with the text property haskell-cabal-marker"
|
|
(haskell-cabal-remove-mark)
|
|
(put-text-property (line-beginning-position) (line-end-position)
|
|
'haskell-cabal-marker 'marked-line)
|
|
(put-text-property (point) (1+ (point))
|
|
'haskell-cabal-marker 'marked))
|
|
|
|
|
|
(defun haskell-cabal-goto-mark ()
|
|
"Go to marked line"
|
|
(let ((marked-pos (text-property-any (point-min) (point-max)
|
|
'haskell-cabal-marker
|
|
'marked))
|
|
(marked-line (text-property-any (point-min) (point-max)
|
|
'haskell-cabal-marker
|
|
'marked-line) )
|
|
)
|
|
(cond (marked-pos (goto-char marked-pos))
|
|
(marked-line (goto-char marked-line)))))
|
|
|
|
(defmacro haskell-cabal-with-subsection-line (replace &rest forms)
|
|
"Mark line, copy subsection data into a temporary buffer, save indentation
|
|
and execute FORMS at the marked line.
|
|
|
|
If REPLACE is non-nil the subsection data is replaced with the
|
|
resulting buffer-content. Unmark line at the end."
|
|
`(progn
|
|
(haskell-cabal-mark)
|
|
(unwind-protect
|
|
(haskell-cabal-with-subsection (haskell-cabal-subsection) ,replace
|
|
(haskell-cabal-goto-mark)
|
|
,@forms)
|
|
(haskell-cabal-remove-mark))))
|
|
|
|
|
|
(defun haskell-cabal-get-line-content ()
|
|
(haskell-cabal-with-subsection-line
|
|
nil
|
|
(haskell-cabal-with-cs-list
|
|
(haskell-cabal-goto-mark)
|
|
(buffer-substring-no-properties (line-beginning-position)
|
|
(line-end-position)))))
|
|
|
|
(defun haskell-cabal-module-to-filename (module)
|
|
(concat (replace-regexp-in-string "[.]" "/" module ) ".hs"))
|
|
|
|
(defconst haskell-cabal-module-sections '("exposed-modules" "other-modules")
|
|
"List of sections that contain module names"
|
|
)
|
|
|
|
(defconst haskell-cabal-file-sections
|
|
'("main-is" "c-sources" "data-files" "extra-source-files"
|
|
"extra-doc-files" "extra-tmp-files" )
|
|
"List of subsections that contain filenames"
|
|
)
|
|
|
|
(defconst haskell-cabal-source-bearing-sections
|
|
'("library" "executable" "test-suite" "benchmark"))
|
|
|
|
(defun haskell-cabal-source-section-p (section)
|
|
(not (not (member (downcase (haskell-cabal-section-name section))
|
|
haskell-cabal-source-bearing-sections))))
|
|
|
|
(defun haskell-cabal-line-filename ()
|
|
"Expand filename in current line according to the subsection type
|
|
|
|
Module names in exposed-modules and other-modules are expanded by replacing each dot (.) in the module name with a foward slash (/) and appending \".hs\"
|
|
|
|
Example: Foo.Bar.Quux ==> Foo/Bar/Quux.hs
|
|
|
|
Source names from main-is and c-sources sections are left untouched
|
|
|
|
"
|
|
(let ((entry (haskell-cabal-get-line-content))
|
|
(subsection (downcase (haskell-cabal-section-name
|
|
(haskell-cabal-subsection)))))
|
|
(cond ((member subsection haskell-cabal-module-sections)
|
|
(haskell-cabal-module-to-filename entry))
|
|
((member subsection haskell-cabal-file-sections) entry))))
|
|
|
|
(defun haskell-cabal-join-paths (&rest args)
|
|
"Crude hack to replace f-join"
|
|
(mapconcat 'identity args "/")
|
|
)
|
|
|
|
(defun haskell-cabal-find-or-create-source-file ()
|
|
"Open the source file this line refers to."
|
|
(interactive)
|
|
(let* ((src-dirs (append (haskell-cabal-subsection-entry-list
|
|
(haskell-cabal-section) "hs-source-dirs")
|
|
'("")))
|
|
(base-dir (file-name-directory (buffer-file-name)))
|
|
(filename (haskell-cabal-line-filename)))
|
|
(when filename
|
|
(let ((candidates
|
|
(delq nil (mapcar
|
|
(lambda (dir)
|
|
(let ((file (haskell-cabal-join-paths base-dir
|
|
dir
|
|
filename)))
|
|
(when (and (file-readable-p file)
|
|
(not (file-directory-p file)))
|
|
file)))
|
|
src-dirs))))
|
|
(if (null candidates)
|
|
(unwind-protect
|
|
(progn
|
|
(haskell-mode-toggle-interactive-prompt-state)
|
|
(let* ((src-dir
|
|
(haskell-cabal-join-paths base-dir
|
|
(or (car src-dirs) "")))
|
|
(newfile (haskell-cabal-join-paths src-dir filename))
|
|
(do-create-p (y-or-n-p (format "Create file %s ?" newfile))))
|
|
(when do-create-p
|
|
(find-file-other-window newfile ))))
|
|
(haskell-mode-toggle-interactive-prompt-state t))
|
|
(find-file-other-window (car candidates)))))))
|
|
|
|
|
|
(defun haskell-cabal-find-section-type (type &optional wrap)
|
|
(save-excursion
|
|
(haskell-cabal-next-section)
|
|
(while
|
|
(not
|
|
(or
|
|
(eobp)
|
|
(string=
|
|
(downcase type)
|
|
(downcase (haskell-cabal-section-name (haskell-cabal-section))))))
|
|
(haskell-cabal-next-section))
|
|
(if (eobp)
|
|
(if wrap (progn
|
|
(goto-char (point-min))
|
|
(haskell-cabal-find-section-type type nil) )
|
|
nil)
|
|
(point))))
|
|
|
|
(defun haskell-cabal-goto-section-type (type)
|
|
(let ((section (haskell-cabal-find-section-type type t)))
|
|
(if section (goto-char section)
|
|
(message "No %s section found" type))))
|
|
|
|
(defun haskell-cabal-goto-library-section ()
|
|
(interactive)
|
|
(haskell-cabal-goto-section-type "library"))
|
|
|
|
(defun haskell-cabal-goto-test-suite-section ()
|
|
(interactive)
|
|
(haskell-cabal-goto-section-type "test-suite"))
|
|
|
|
(defun haskell-cabal-goto-executable-section ()
|
|
(interactive)
|
|
(haskell-cabal-goto-section-type "executable"))
|
|
|
|
(defun haskell-cabal-goto-benchmark-section ()
|
|
(interactive)
|
|
(haskell-cabal-goto-section-type "benchmark"))
|
|
|
|
(defun haskell-cabal-goto-common-section ()
|
|
(interactive)
|
|
(haskell-cabal-goto-section-type "common"))
|
|
|
|
|
|
(defun haskell-cabal-line-entry-column ()
|
|
"Column at which the line entry starts"
|
|
(save-excursion
|
|
(cl-case (haskell-cabal-classify-line)
|
|
(section-data (beginning-of-line)
|
|
(when (looking-at "[ ]*\\(?:,[ ]*\\)?")
|
|
(goto-char (match-end 0))
|
|
(current-column)))
|
|
(subsection-header
|
|
(haskell-cabal-section-data-start-column (haskell-cabal-subsection))))))
|
|
|
|
(defun haskell-cabal-forward-to-line-entry ()
|
|
"go forward to the beginning of the line entry (but never move backwards)"
|
|
(let ((col (haskell-cabal-line-entry-column)))
|
|
(when (and col (< (current-column) col))
|
|
(beginning-of-line)
|
|
(forward-char col))))
|
|
|
|
(defun haskell-cabal-indent-line ()
|
|
"Indent current line according to subsection"
|
|
(interactive)
|
|
(cl-case (haskell-cabal-classify-line)
|
|
(section-data
|
|
(save-excursion
|
|
(let ((indent (haskell-cabal-section-data-indent-column
|
|
(haskell-cabal-subsection))))
|
|
(indent-line-to indent)
|
|
(beginning-of-line)
|
|
(when (looking-at "[ ]*\\([ ]\\{2\\},[ ]*\\)")
|
|
(replace-match ", " t t nil 1)))))
|
|
(empty
|
|
(indent-relative)))
|
|
(haskell-cabal-forward-to-line-entry))
|
|
|
|
(defun haskell-cabal-map-sections (fun)
|
|
"Execute fun over each section, collecting the result"
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(let ((results nil))
|
|
(while (not (eobp))
|
|
(let* ((section (haskell-cabal-section))
|
|
(result (and section (funcall fun (haskell-cabal-section)))))
|
|
(when section (setq results (cons result results))))
|
|
(haskell-cabal-next-section))
|
|
(nreverse results))))
|
|
|
|
(defun haskell-cabal-section-add-build-dependency (dependency &optional sort sec)
|
|
"Add a build dependency to the build-depends section"
|
|
(let* ((section (or sec (haskell-cabal-section)))
|
|
(subsection (and section
|
|
(haskell-cabal-find-subsection section "build-depends"))))
|
|
(when subsection
|
|
(haskell-cabal-with-subsection
|
|
subsection t
|
|
(haskell-cabal-with-cs-list
|
|
(insert dependency)
|
|
(insert "\n")
|
|
(when sort
|
|
(goto-char (point-min))
|
|
(sort-subr nil 'forward-line 'end-of-line
|
|
'haskell-cabal-sort-lines-key-fun)))))))
|
|
|
|
(defun haskell-cabal-add-build-dependency (dependency &optional sort silent)
|
|
"Add the given DEPENDENCY to every section in cabal file.
|
|
If SORT argument is given sort dependencies in section after update.
|
|
Pass SILENT argument to update all sections without asking user."
|
|
(haskell-cabal-map-sections
|
|
(lambda (section)
|
|
(when (haskell-cabal-source-section-p section)
|
|
(unwind-protect
|
|
(progn
|
|
(when
|
|
(or silent
|
|
(y-or-n-p (format "Add dependency %s to %s section %s?"
|
|
dependency
|
|
(haskell-cabal-section-name section)
|
|
(haskell-cabal-section-value section))))
|
|
(haskell-cabal-section-add-build-dependency dependency
|
|
sort
|
|
section))
|
|
nil)
|
|
(haskell-mode-toggle-interactive-prompt-state t))))))
|
|
|
|
(defun haskell-cabal-add-dependency
|
|
(package &optional version no-prompt sort silent)
|
|
"Add PACKAGE to the cabal file.
|
|
If VERSION is non-nil it will be appended as a minimum version.
|
|
If NO-PROMPT is nil the minimum package version is read from the
|
|
minibuffer. When SORT is non-nil the package entries are sorted
|
|
afterwards. If SILENT is non-nil the user is prompted for each
|
|
source-section."
|
|
(interactive
|
|
(list (read-from-minibuffer "Package entry: ") nil t t nil))
|
|
(haskell-mode-toggle-interactive-prompt-state)
|
|
(unwind-protect
|
|
(save-window-excursion
|
|
(find-file-other-window (haskell-cabal-find-file))
|
|
(let ((entry (if no-prompt package
|
|
(read-from-minibuffer
|
|
"Package entry: "
|
|
(concat package
|
|
(if version (concat " >= " version) ""))))))
|
|
(haskell-cabal-add-build-dependency entry sort silent)
|
|
(when (or silent (y-or-n-p "Save cabal file? "))
|
|
(save-buffer))))
|
|
;; unwind
|
|
(haskell-mode-toggle-interactive-prompt-state t)))
|
|
|
|
|
|
(defun haskell-cabal--find-tags-dir ()
|
|
"Return a directory where TAGS file will be generated.
|
|
Tries to find cabal file first and if succeeds uses its location.
|
|
If cabal file not found uses current file directory. If current
|
|
buffer not visiting a file returns nil."
|
|
(or (haskell-cabal-find-dir)
|
|
(when buffer-file-name
|
|
(file-name-directory buffer-file-name))))
|
|
|
|
(defun haskell-cabal--compose-hasktags-command (dir)
|
|
"Prepare command to execute `hasktags` command in DIR folder.
|
|
|
|
To customise the command executed, see `haskell-hasktags-path'
|
|
and `haskell-hasktags-arguments'.
|
|
|
|
This function takes into account the user's operating system: in case
|
|
of Windows it generates a simple command, relying on Hasktags
|
|
itself to find source files:
|
|
|
|
hasktags --output=DIR\TAGS -x -e DIR
|
|
|
|
In other cases it uses `find` command to find all source files
|
|
recursively avoiding visiting unnecessary heavy directories like
|
|
.git, .svn, _darcs and build directories created by
|
|
cabal-install, stack, etc and passes list of found files to Hasktags."
|
|
(if (eq system-type 'windows-nt)
|
|
(format "%s --output=%s %s %s"
|
|
haskell-hasktags-path
|
|
(shell-quote-argument (expand-file-name "TAGS" dir))
|
|
(mapconcat #'identity haskell-hasktags-arguments " ")
|
|
(shell-quote-argument dir))
|
|
(format "cd %s && %s | %s"
|
|
(shell-quote-argument dir)
|
|
(concat "find . "
|
|
"-type d \\( "
|
|
"-name .git "
|
|
"-o -name .svn "
|
|
"-o -name _darcs "
|
|
"-o -name .stack-work "
|
|
"-o -name dist "
|
|
"-o -name dist-newstyle "
|
|
"-o -name .cabal-sandbox "
|
|
"\\) -prune "
|
|
"-o -type f \\( "
|
|
"-name '*.hs' "
|
|
"-or -name '*.lhs' "
|
|
"-or -name '*.hsc' "
|
|
"\\) -not \\( "
|
|
"-name '#*' "
|
|
"-or -name '.*' "
|
|
"\\) -print0")
|
|
(format "xargs -0 %s %s"
|
|
(shell-quote-argument haskell-hasktags-path)
|
|
(mapconcat #'identity haskell-hasktags-arguments " ")))))
|
|
|
|
(provide 'haskell-cabal)
|
|
;;; haskell-cabal.el ends here
|