Taylor Bockman
9 years ago
158 changed files with 64 additions and 59367 deletions
@ -1 +1 @@
|
||||
Good signature from 474F05837FBDEF9B GNU ELPA Signing Agent <elpasign@elpa.gnu.org> (trust undefined) created at 2015-10-23T02:05:01-0700 using DSA |
||||
Good signature from 474F05837FBDEF9B GNU ELPA Signing Agent <elpasign@elpa.gnu.org> (trust undefined) created at 2015-10-24T02:05:02-0700 using DSA |
File diff suppressed because one or more lines are too long
@ -1,130 +0,0 @@
|
||||
;;; cider-apropos.el --- Apropos functionality for Clojure -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright © 2014-2015 Jeff Valk, Bozhidar Batsov |
||||
;; |
||||
;; Author: Jeff Valk <jv@jeffvalk.com> |
||||
|
||||
;; 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/>. |
||||
|
||||
;; This file is not part of GNU Emacs. |
||||
|
||||
;;; Commentary: |
||||
|
||||
;; Apropos functionality for Clojure. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cider-doc) |
||||
(require 'cider-util) |
||||
(require 'cider-compat) |
||||
|
||||
(require 'cider-client) |
||||
(require 'cider-popup) |
||||
(require 'nrepl-client) |
||||
|
||||
(require 'clojure-mode) |
||||
(require 'apropos) |
||||
(require 'button) |
||||
|
||||
(defconst cider-apropos-buffer "*cider-apropos*") |
||||
|
||||
(push cider-apropos-buffer cider-ancillary-buffers) |
||||
|
||||
(defun cider-apropos-doc (button) |
||||
"Display documentation for the symbol represented at BUTTON." |
||||
(cider-doc-lookup (button-get button 'apropos-symbol))) |
||||
|
||||
(defun cider-apropos-summary (query ns docs-p include-private-p case-sensitive-p) |
||||
"Return a short description for the performed apropos search." |
||||
(concat (if case-sensitive-p "Case-sensitive " "") |
||||
(if docs-p "Documentation " "") |
||||
(format "Apropos for %S" query) |
||||
(if ns (format " in namespace %S" ns) "") |
||||
(if include-private-p |
||||
" (public and private symbols)" |
||||
" (public symbols only)"))) |
||||
|
||||
(defun cider-apropos-highlight (doc query) |
||||
"Return the DOC string propertized to highlight QUERY matches." |
||||
(let ((pos 0)) |
||||
(while (string-match query doc pos) |
||||
(setq pos (match-end 0)) |
||||
(put-text-property (match-beginning 0) |
||||
(match-end 0) |
||||
'font-lock-face apropos-match-face doc))) |
||||
doc) |
||||
|
||||
(defun cider-apropos-result (result query docs-p) |
||||
"Emit a RESULT matching QUERY into current buffer, formatted for DOCS-P." |
||||
(nrepl-dbind-response result (name type doc) |
||||
(let* ((label (capitalize (if (string= type "variable") "var" type))) |
||||
(help (concat "Display doc for this " (downcase label)))) |
||||
(cider-propertize-region (list 'apropos-symbol name |
||||
'action 'cider-apropos-doc |
||||
'help-echo help) |
||||
(insert-text-button name 'type 'apropos-symbol) |
||||
(insert "\n ") |
||||
(insert-text-button label 'type (intern (concat "apropos-" type))) |
||||
(insert ": ") |
||||
(let ((beg (point))) |
||||
(if docs-p |
||||
(insert (cider-apropos-highlight doc query) "\n") |
||||
(insert doc) |
||||
(fill-region beg (point)))) |
||||
(insert "\n"))))) |
||||
|
||||
(declare-function cider-mode "cider-mode") |
||||
|
||||
(defun cider-show-apropos (summary results query docs-p) |
||||
"Show SUMMARY and RESULTS for QUERY in a pop-up buffer, formatted for DOCS-P." |
||||
(with-current-buffer (cider-popup-buffer cider-apropos-buffer t) |
||||
(let ((inhibit-read-only t)) |
||||
(set-syntax-table clojure-mode-syntax-table) |
||||
(apropos-mode) |
||||
(cider-mode) |
||||
(if (boundp 'header-line-format) |
||||
(setq-local header-line-format summary) |
||||
(insert summary "\n\n")) |
||||
(dolist (result results) |
||||
(cider-apropos-result result query docs-p)) |
||||
(goto-char (point-min))))) |
||||
|
||||
;;;###autoload |
||||
(defun cider-apropos (query &optional ns docs-p privates-p case-sensitive-p) |
||||
"Show all symbols whose names match QUERY, a regular expression. |
||||
The search may be limited to the namespace NS, and may optionally search doc |
||||
strings, include private vars, and be case sensitive." |
||||
(interactive |
||||
(if current-prefix-arg |
||||
(list (read-string "Clojure Apropos: ") |
||||
(let ((ns (read-string "Namespace: "))) |
||||
(if (string= ns "") nil ns)) |
||||
(y-or-n-p "Search doc strings? ") |
||||
(y-or-n-p "Include private symbols? ") |
||||
(y-or-n-p "Case-sensitive? ")) |
||||
(list (read-string "Clojure Apropos: ")))) |
||||
(cider-ensure-op-supported "apropos") |
||||
(if-let ((summary (cider-apropos-summary |
||||
query ns docs-p privates-p case-sensitive-p)) |
||||
(results (cider-sync-request:apropos query ns docs-p privates-p case-sensitive-p))) |
||||
(cider-show-apropos summary results query docs-p) |
||||
(message "No apropos matches for %S" query))) |
||||
|
||||
;;;###autoload |
||||
(defun cider-apropos-documentation () |
||||
"Shortcut for (cider-apropos <query> nil t)." |
||||
(interactive) |
||||
(cider-apropos (read-string "Clojure documentation Apropos: ") nil t)) |
||||
|
||||
(provide 'cider-apropos) |
@ -1,150 +0,0 @@
|
||||
;;; cider-browse-ns.el --- CIDER namespace browser |
||||
|
||||
;; Copyright © 2014-2015 John Andrews |
||||
|
||||
;; Author: John Andrews <john.m.andrews@gmail.com> |
||||
|
||||
;; 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/>. |
||||
|
||||
;; This file is not part of GNU Emacs. |
||||
|
||||
;;; Commentary: |
||||
|
||||
;; (cider-browse-ns) |
||||
;; Display a list of all vars in a namespace. |
||||
;; Pressing <enter> will take you to the cider-doc buffer for that var. |
||||
;; Pressing ^ will take you to a list of all namespaces (akin to dired mode) |
||||
|
||||
;; (cider-browse-ns-all) |
||||
;; Explore clojure namespaces by browsing a list of all namespaces. |
||||
;; Pressing enter expands into a list of that namespace's vars as if by |
||||
;; executing the command (cider-browse-ns "my.ns") |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cider-repl) |
||||
(require 'cider-client) |
||||
(require 'cider-compat) |
||||
|
||||
(defconst cider-browse-ns-buffer "*Browse NS*") |
||||
(defvar-local cider-browse-ns-current-ns nil) |
||||
|
||||
;; Mode Definition |
||||
|
||||
(defvar cider-browse-ns-mode-map |
||||
(let ((map (make-sparse-keymap))) |
||||
(set-keymap-parent map cider-popup-buffer-mode-map) |
||||
(define-key map "d" #'cider-browse-ns--doc-at-point) |
||||
(define-key map "s" #'cider-browse-ns--find-at-point) |
||||
(define-key map [return] #'cider-browse-ns--doc-at-point) |
||||
(define-key map "^" #'cider-browse-ns-all) |
||||
(define-key map "n" #'next-line) |
||||
(define-key map "p" #'previous-line) |
||||
map)) |
||||
|
||||
(defvar cider-browse-ns-mouse-map |
||||
(let ((map (make-sparse-keymap))) |
||||
(define-key map [mouse-1] #'cider-browse-ns--handle-mouse) |
||||
map)) |
||||
|
||||
(define-derived-mode cider-browse-ns-mode special-mode "browse-ns" |
||||
"Major mode for browsing Clojure namespaces. |
||||
|
||||
\\{cider-browse-ns-mode-map}" |
||||
(set-syntax-table clojure-mode-syntax-table) |
||||
(setq buffer-read-only t) |
||||
(setq-local electric-indent-chars nil) |
||||
(setq-local truncate-lines t) |
||||
(setq-local cider-browse-ns-current-ns nil)) |
||||
|
||||
(defun cider-browse-ns--properties (text) |
||||
"Decorate TEXT with a clickable keymap and function face." |
||||
(propertize text |
||||
'font-lock-face 'font-lock-function-name-face |
||||
'mouse-face 'highlight |
||||
'keymap cider-browse-ns-mouse-map)) |
||||
|
||||
(defun cider-browse-ns--list (buffer title items &optional ns noerase) |
||||
"Reset contents of BUFFER. Then display TITLE at the top and ITEMS are indented underneath. |
||||
If NS is non-nil, it is added to each item as the |
||||
`cider-browse-ns-current-ns' text property. If NOERASE is non-nil, the |
||||
contents of the buffer are not reset before inserting TITLE and ITEMS." |
||||
(with-current-buffer buffer |
||||
(cider-browse-ns-mode) |
||||
(let ((inhibit-read-only t)) |
||||
(unless noerase (erase-buffer)) |
||||
(goto-char (point-max)) |
||||
(insert (propertize title 'font-lock-face 'font-lock-type-face) |
||||
"\n") |
||||
(dolist (item items) |
||||
(insert (propertize (concat " " item "\n") |
||||
'cider-browse-ns-current-ns ns))) |
||||
(goto-char (point-min))))) |
||||
|
||||
;; Interactive Functions |
||||
|
||||
;;;###autoload |
||||
(defun cider-browse-ns (namespace) |
||||
"List all NAMESPACE's vars in BUFFER." |
||||
(interactive (list (completing-read "Browse namespace: " (cider-sync-request:ns-list)))) |
||||
(with-current-buffer (cider-popup-buffer cider-browse-ns-buffer t) |
||||
(let ((vars (cider-sync-request:ns-vars namespace))) |
||||
(cider-browse-ns--list (current-buffer) |
||||
namespace |
||||
(mapcar (lambda (var) |
||||
(format "%s" |
||||
(cider-browse-ns--properties var))) |
||||
vars)) |
||||
(setq-local cider-browse-ns-current-ns namespace)))) |
||||
|
||||
;;;###autoload |
||||
(defun cider-browse-ns-all () |
||||
"List all loaded namespaces in BUFFER." |
||||
(interactive) |
||||
(with-current-buffer (cider-popup-buffer cider-browse-ns-buffer t) |
||||
(let ((names (cider-sync-request:ns-list))) |
||||
(cider-browse-ns--list (current-buffer) |
||||
"All loaded namespaces" |
||||
(mapcar (lambda (name) |
||||
(cider-browse-ns--properties name)) |
||||
names)) |
||||
(setq-local cider-browse-ns-current-ns nil)))) |
||||
|
||||
(defun cider-browse-ns--var-at-point () |
||||
(let ((line (thing-at-point 'line))) |
||||
(when (string-match " +\\(.+\\)\n?" line) |
||||
(format "%s/%s" |
||||
(or (get-text-property (point) 'cider-browse-ns-current-ns) |
||||
cider-browse-ns-current-ns) |
||||
(match-string 1 line))))) |
||||
|
||||
(defun cider-browse-ns--doc-at-point () |
||||
"Expand browser according to thing at current point." |
||||
(interactive) |
||||
(when-let ((var (cider-browse-ns--var-at-point))) |
||||
(cider-doc-lookup var))) |
||||
|
||||
(defun cider-browse-ns--find-at-point () |
||||
(interactive) |
||||
(when-let ((var (cider-browse-ns--var-at-point))) |
||||
(cider-find-var current-prefix-arg var))) |
||||
|
||||
(defun cider-browse-ns--handle-mouse (event) |
||||
"Handle mouse click EVENT." |
||||
(interactive "e") |
||||
(cider-browse-ns--doc-at-point)) |
||||
|
||||
(provide 'cider-browse-ns) |
||||
|
||||
;;; cider-browse-ns.el ends here |
@ -1,106 +0,0 @@
|
||||
;;; cider-classpath.el --- Basic Java classpath browser |
||||
|
||||
;; Copyright © 2014-2015 Bozhidar Batsov |
||||
|
||||
;; 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/>. |
||||
|
||||
;; This file is not part of GNU Emacs. |
||||
|
||||
;;; Commentary: |
||||
|
||||
;; Basic Java classpath browser for CIDER. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cider-client) |
||||
(require 'cider-popup) |
||||
(require 'cider-compat) |
||||
|
||||
(defvar cider-classpath-buffer "*Classpath*") |
||||
|
||||
(defvar cider-classpath-mode-map |
||||
(let ((map (make-sparse-keymap))) |
||||
(set-keymap-parent map cider-popup-buffer-mode-map) |
||||
(define-key map [return] #'cider-classpath-operate-on-point) |
||||
(define-key map "n" #'next-line) |
||||
(define-key map "p" #'previous-line) |
||||
map)) |
||||
|
||||
(defvar cider-classpath-mouse-map |
||||
(let ((map (make-sparse-keymap))) |
||||
(define-key map [mouse-1] #'cider-classpath-handle-mouse) |
||||
map)) |
||||
|
||||
(define-derived-mode cider-classpath-mode special-mode "classpath" |
||||
"Major mode for browsing the entries in Java's classpath. |
||||
|
||||
\\{cider-classpath-mode-map}" |
||||
(setq buffer-read-only t) |
||||
(setq-local electric-indent-chars nil) |
||||
(setq-local truncate-lines t)) |
||||
|
||||
(defun cider-classpath-list (buffer items) |
||||
"Populate BUFFER with ITEMS." |
||||
(with-current-buffer buffer |
||||
(cider-classpath-mode) |
||||
(let ((inhibit-read-only t)) |
||||
(erase-buffer) |
||||
(dolist (item items) |
||||
(insert item "\n")) |
||||
(goto-char (point-min))))) |
||||
|
||||
(defun cider-classpath-properties (text) |
||||
"Decorate TEXT with a clickable keymap and function face." |
||||
(let ((face (cond |
||||
((not (file-exists-p text)) 'font-lock-warning-face) |
||||
((file-directory-p text) 'dired-directory) |
||||
(t 'default)))) |
||||
(propertize text |
||||
'font-lock-face face |
||||
'mouse-face 'highlight |
||||
'keymap cider-classpath-mouse-map))) |
||||
|
||||
(defun cider-classpath-operate-on-point () |
||||
"Expand browser according to thing at current point." |
||||
(interactive) |
||||
(let* ((bol (line-beginning-position)) |
||||
(eol (line-end-position)) |
||||
(line (buffer-substring-no-properties bol eol))) |
||||
(find-file-other-window line))) |
||||
|
||||
(defun cider-classpath-handle-mouse (event) |
||||
"Handle mouse click EVENT." |
||||
(interactive "e") |
||||
(cider-classpath-operate-on-point)) |
||||
|
||||
;;;###autoload |
||||
(defun cider-classpath () |
||||
"List all classpath entries." |
||||
(interactive) |
||||
(with-current-buffer (cider-popup-buffer cider-classpath-buffer t) |
||||
(cider-classpath-list (current-buffer) |
||||
(mapcar (lambda (name) |
||||
(cider-classpath-properties name)) |
||||
(cider-sync-request:classpath))))) |
||||
|
||||
;;;###autoload |
||||
(defun cider-open-classpath-entry () |
||||
"Open a classpath entry." |
||||
(interactive) |
||||
(when-let ((entry (completing-read "Classpath entries: " (cider-sync-request:classpath)))) |
||||
(find-file-other-window entry))) |
||||
|
||||
(provide 'cider-classpath) |
||||
|
||||
;;; cider-classpath.el ends here |
@ -1,225 +0,0 @@
|
||||
;;; cider-common.el --- Common use functions -*- lexical-binding: t; -*- |
||||
|
||||
;; Copyright (C) 2015 Artur Malabarba |
||||
|
||||
;; Author: Artur Malabarba <bruce.connor.am@gmail.com> |
||||
|
||||
;; 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: |
||||
|
||||
;; Common functions that are useful in both Clojure buffers and REPL |
||||
;; buffers. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cider-compat) |
||||
(require 'nrepl-client) |
||||
(require 'cider-util) |
||||
|
||||
(defcustom cider-prompt-for-symbol t |
||||
"Controls when to prompt for symbol when a command requires one. |
||||
|
||||
When non-nil, always prompt, and use the symbol at point as the default |
||||
value at the prompt. |
||||
|
||||
When nil, attempt to use the symbol at point for the command, and only |
||||
prompt if that throws an error." |
||||
:type '(choice (const :tag "always" t) |
||||
(const :tag "dwim" nil)) |
||||
:group 'cider |
||||
:package-version '(cider . "0.9.0")) |
||||
|
||||
(defun cider--should-prompt-for-symbol (&optional invert) |
||||
(if invert (not cider-prompt-for-symbol) cider-prompt-for-symbol)) |
||||
|
||||
(defun cider-prompt-for-symbol-function (&optional invert) |
||||
(if (cider--should-prompt-for-symbol invert) |
||||
#'cider-read-symbol-name |
||||
#'cider-try-symbol-at-point)) |
||||
|
||||
(defun cider--kw-to-symbol (kw) |
||||
"Convert the keyword KW to a symbol." |
||||
(when kw |
||||
(replace-regexp-in-string "\\`:+" "" kw))) |
||||
|
||||
(declare-function cider-read-from-minibuffer "cider-interaction") |
||||
|
||||
(defun cider-read-symbol-name (prompt callback) |
||||
"Read a symbol name using PROMPT with a default of the one at point. |
||||
Use CALLBACK as the completing read var callback." |
||||
(funcall callback (cider-read-from-minibuffer |
||||
prompt |
||||
;; if the thing at point is a keyword we treat it as symbol |
||||
(cider--kw-to-symbol (cider-symbol-at-point))))) |
||||
|
||||
(defun cider-try-symbol-at-point (prompt callback) |
||||
"Call CALLBACK with symbol at point. |
||||
On failure, read a symbol name using PROMPT and call CALLBACK with that." |
||||
(condition-case nil (funcall callback (cider--kw-to-symbol (cider-symbol-at-point))) |
||||
('error (funcall callback (cider-read-from-minibuffer prompt))))) |
||||
|
||||
(declare-function cider-jump-to "cider-interaction") |
||||
|
||||
(defun cider--jump-to-loc-from-info (info &optional other-window) |
||||
"Jump to location give by INFO. |
||||
INFO object is returned by `cider-var-info' or `cider-member-info'. |
||||
OTHER-WINDOW is passed to `cider-jamp-to'." |
||||
(let* ((line (nrepl-dict-get info "line")) |
||||
(file (nrepl-dict-get info "file")) |
||||
(name (nrepl-dict-get info "name")) |
||||
(buffer (and file |
||||
(not (cider--tooling-file-p file)) |
||||
(cider-find-file file)))) |
||||
(if buffer |
||||
(cider-jump-to buffer (if line (cons line nil) name) other-window) |
||||
(error "No source location")))) |
||||
|
||||
(declare-function url-filename "url-parse" (cl-x) t) |
||||
|
||||
(defun cider--url-to-file (url) |
||||
"Return the filename from the resource URL. |
||||
Uses `url-generic-parse-url' to parse the url. The filename is extracted and |
||||
then url decoded. If the decoded filename has a Windows device letter followed |
||||
by a colon immediately after the leading '/' then the leading '/' is dropped to |
||||
create a valid path." |
||||
(let ((filename (url-unhex-string (url-filename (url-generic-parse-url url))))) |
||||
(if (string-match "^/\\([a-zA-Z]:/.*\\)" filename) |
||||
(match-string 1 filename) |
||||
filename))) |
||||
|
||||
(defun cider-tramp-prefix (&optional buffer) |
||||
"Use the filename for BUFFER to determine a tramp prefix. |
||||
Defaults to the current buffer. |
||||
Return the tramp prefix, or nil if BUFFER is local." |
||||
(let* ((buffer (or buffer (current-buffer))) |
||||
(name (or (buffer-file-name buffer) |
||||
(with-current-buffer buffer |
||||
default-directory)))) |
||||
(when (tramp-tramp-file-p name) |
||||
(let ((vec (tramp-dissect-file-name name))) |
||||
(tramp-make-tramp-file-name (tramp-file-name-method vec) |
||||
(tramp-file-name-user vec) |
||||
(tramp-file-name-host vec) |
||||
nil))))) |
||||
|
||||
(defun cider--client-tramp-filename (name &optional buffer) |
||||
"Return the tramp filename for path NAME relative to BUFFER. |
||||
If BUFFER has a tramp prefix, it will be added as a prefix to NAME. |
||||
If the resulting path is an existing tramp file, it returns the path, |
||||
otherwise, nil." |
||||
(let* ((buffer (or buffer (current-buffer))) |
||||
(name (concat (cider-tramp-prefix buffer) name))) |
||||
(if (tramp-handle-file-exists-p name) |
||||
name))) |
||||
|
||||
(defun cider--server-filename (name) |
||||
"Return the nREPL server-relative filename for NAME." |
||||
(if (tramp-tramp-file-p name) |
||||
(with-parsed-tramp-file-name name nil |
||||
localname) |
||||
name)) |
||||
|
||||
(defvar cider-from-nrepl-filename-function |
||||
(with-no-warnings |
||||
(if (eq system-type 'cygwin) |
||||
#'cygwin-convert-file-name-from-windows |
||||
#'identity)) |
||||
"Function to translate nREPL namestrings to Emacs filenames.") |
||||
|
||||
(defcustom cider-prefer-local-resources nil |
||||
"Prefer local resources to remote (tramp) ones when both are available." |
||||
:type 'boolean |
||||
:group 'cider) |
||||
|
||||
(defun cider--file-path (path) |
||||
"Return PATH's local or tramp path using `cider-prefer-local-resources'. |
||||
If no local or remote file exists, return nil." |
||||
(let* ((local-path (funcall cider-from-nrepl-filename-function path)) |
||||
(tramp-path (and local-path (cider--client-tramp-filename local-path)))) |
||||
(cond ((equal local-path "") "") |
||||
((and cider-prefer-local-resources (file-exists-p local-path)) |
||||
local-path) |
||||
((and tramp-path (file-exists-p tramp-path)) |
||||
tramp-path) |
||||
((and local-path (file-exists-p local-path)) |
||||
local-path)))) |
||||
|
||||
(declare-function archive-extract "arc-mode") |
||||
(declare-function archive-zip-extract "arc-mode") |
||||
|
||||
(defun cider-find-file (url) |
||||
"Return a buffer visiting the file URL if it exists, or nil otherwise. |
||||
If URL has a scheme prefix, it must represent a fully-qualified file path |
||||
or an entry within a zip/jar archive. If URL doesn't contain a scheme |
||||
prefix and is an absolute path, it is treated as such. Finally, if URL is |
||||
relative, it is expanded within each of the open Clojure buffers till an |
||||
existing file ending with URL has been found." |
||||
(require 'arc-mode) |
||||
(cond ((string-match "^file:\\(.+\\)" url) |
||||
(when-let ((file (cider--url-to-file (match-string 1 url))) |
||||
(path (cider--file-path file))) |
||||
(find-file-noselect path))) |
||||
((string-match "^\\(jar\\|zip\\):\\(file:.+\\)!/\\(.+\\)" url) |
||||
(when-let ((entry (match-string 3 url)) |
||||
(file (cider--url-to-file (match-string 2 url))) |
||||
(path (cider--file-path file)) |
||||
(name (format "%s:%s" path entry))) |
||||
(or (find-buffer-visiting name) |
||||
(if (tramp-tramp-file-p path) |
||||
(progn |
||||
;; Use emacs built in archiving |
||||
(find-file path) |
||||
(goto-char (point-min)) |
||||
;; Make sure the file path is followed by a newline to |
||||
;; prevent eg. clj matching cljs. |
||||
(search-forward (concat entry "\n")) |
||||
;; moves up to matching line |
||||
(forward-line -1) |
||||
(archive-extract) |
||||
(current-buffer)) |
||||
;; Use external zip program to just extract the single file |
||||
(with-current-buffer (generate-new-buffer |
||||
(file-name-nondirectory entry)) |
||||
(archive-zip-extract path entry) |
||||
(set-visited-file-name name) |
||||
(setq-local default-directory (file-name-directory path)) |
||||
(setq-local buffer-read-only t) |
||||
(set-buffer-modified-p nil) |
||||
(set-auto-mode) |
||||
(current-buffer)))))) |
||||
(t (if-let ((path (cider--file-path url))) |
||||
(find-file-noselect path) |
||||
(unless (file-name-absolute-p url) |
||||
(let ((cider-buffers (cider-util--clojure-buffers)) |
||||
(url (file-name-nondirectory url))) |
||||
(or (cl-loop for bf in cider-buffers |
||||
for path = (with-current-buffer bf |
||||
(expand-file-name url)) |
||||
if (and path (file-exists-p path)) |
||||
return (find-file-noselect path)) |
||||
(cl-loop for bf in cider-buffers |
||||
if (string= (buffer-name bf) url) |
||||
return bf)))))))) |
||||
|
||||
(defun cider--open-other-window-p (arg) |
||||
"Test prefix value ARG to see if it indicates displaying results in other window." |
||||
(let ((narg (prefix-numeric-value arg))) |
||||
(pcase narg |
||||
(-1 t) ; - |
||||
(16 t) ; empty empty |
||||
(_ nil)))) |
||||
|
||||
(provide 'cider-common) |
||||
;;; cider-common.el ends here |
@ -1,157 +0,0 @@
|
||||
;;; cider-compat.el --- Functions from newer Emacs versions for compatibility -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright © 2012-2015 Tim King, Phil Hagelberg |
||||
;; Copyright © 2013-2015 Bozhidar Batsov, Hugo Duncan, Steve Purcell |
||||
;; |
||||
|
||||
;; 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/>. |
||||
|
||||
;; This file is not part of GNU Emacs. |
||||
|
||||
;;; Commentary: |
||||
|
||||
;; Pretty much everything here's copied from subr-x for compatibility with |
||||
;; Emacs 24.3 and 24.4. |
||||
|
||||
;;; Code: |
||||
|
||||
(eval-and-compile |
||||
|
||||
(unless (fboundp 'thread-first ) |
||||
|
||||
(defmacro internal--thread-argument (first? &rest forms) |
||||
"Internal implementation for `thread-first' and `thread-last'. |
||||
When Argument FIRST? is non-nil argument is threaded first, else |
||||
last. FORMS are the expressions to be threaded." |
||||
(pcase forms |
||||
(`(,x (,f . ,args) . ,rest) |
||||
`(internal--thread-argument |
||||
,first? ,(if first? `(,f ,x ,@args) `(,f ,@args ,x)) ,@rest)) |
||||
(`(,x ,f . ,rest) `(internal--thread-argument ,first? (,f ,x) ,@rest)) |
||||
(_ (car forms)))) |
||||
|
||||
(defmacro thread-first (&rest forms) |
||||
"Thread FORMS elements as the first argument of their successor. |
||||
Example: |
||||
(thread-first |
||||
5 |
||||
(+ 20) |
||||
(/ 25) |
||||
- |
||||
(+ 40)) |
||||
Is equivalent to: |
||||
(+ (- (/ (+ 5 20) 25)) 40) |
||||
Note how the single `-' got converted into a list before |
||||
threading." |
||||
(declare (indent 1) |
||||
(debug (form &rest [&or symbolp (sexp &rest form)]))) |
||||
`(internal--thread-argument t ,@forms))) |
||||
|
||||
(unless (fboundp 'thread-last ) |
||||
|
||||
(defmacro thread-last (&rest forms) |
||||
"Thread FORMS elements as the last argument of their successor. |
||||
Example: |
||||
(thread-last |
||||
5 |
||||
(+ 20) |
||||
(/ 25) |
||||
- |
||||
(+ 40)) |
||||
Is equivalent to: |
||||
(+ 40 (- (/ 25 (+ 20 5)))) |
||||
Note how the single `-' got converted into a list before |
||||
threading." |
||||
(declare (indent 1) (debug thread-first)) |
||||
`(internal--thread-argument nil ,@forms)))) |
||||
|
||||
|
||||
(eval-and-compile |
||||
|
||||
(unless (fboundp 'internal--listify) |
||||
|
||||
(defsubst internal--listify (elt) |
||||
"Wrap ELT in a list if it is not one." |
||||
(if (not (listp elt)) |
||||
(list elt) |
||||
elt))) |
||||
|
||||
(unless (fboundp 'internal--check-binding) |
||||
|
||||
(defsubst internal--check-binding (binding) |
||||
"Check BINDING is properly formed." |
||||
(when (> (length binding) 2) |
||||
(signal |
||||
'error |
||||
(cons "`let' bindings can have only one value-form" binding))) |
||||
binding)) |
||||
|
||||
(unless (fboundp 'internal--build-binding-value-form) |
||||
|
||||
(defsubst internal--build-binding-value-form (binding prev-var) |
||||
"Build the conditional value form for BINDING using PREV-VAR." |
||||
`(,(car binding) (and ,prev-var ,(cadr binding))))) |
||||
|
||||
(unless (fboundp ' internal--build-binding) |
||||
|
||||
(defun internal--build-binding (binding prev-var) |
||||
"Check and build a single BINDING with PREV-VAR." |
||||
(thread-first |
||||
binding |
||||
internal--listify |
||||
internal--check-binding |
||||
(internal--build-binding-value-form prev-var)))) |
||||
|
||||
(unless (fboundp ' internal--build-bindings) |
||||
|
||||
(defun internal--build-bindings (bindings) |
||||
"Check and build conditional value forms for BINDINGS." |
||||
(let ((prev-var t)) |
||||
(mapcar (lambda (binding) |
||||
(let ((binding (internal--build-binding binding prev-var))) |
||||
(setq prev-var (car binding)) |
||||
binding)) |
||||
bindings))))) |
||||
|
||||
(eval-and-compile |
||||
|
||||
(unless (fboundp 'if-let) |
||||
(defmacro if-let (bindings then &rest else) |
||||
"Process BINDINGS and if all values are non-nil eval THEN, else ELSE. |
||||
Argument BINDINGS is a list of tuples whose car is a symbol to be |
||||
bound and (optionally) used in THEN, and its cadr is a sexp to be |
||||
evalled to set symbol's value. In the special case you only want |
||||
to bind a single value, BINDINGS can just be a plain tuple." |
||||
(declare (indent 2) |
||||
(debug ([&or (&rest (symbolp form)) (symbolp form)] form body))) |
||||
(when (and (<= (length bindings) 2) |
||||
(not (listp (car bindings)))) |
||||
;; Adjust the single binding case |
||||
(setq bindings (list bindings))) |
||||
`(let* ,(internal--build-bindings bindings) |
||||
(if ,(car (internal--listify (car (last bindings)))) |
||||
,then |
||||
,@else))) |
||||
|
||||
(defmacro when-let (bindings &rest body) |
||||
"Process BINDINGS and if all values are non-nil eval BODY. |
||||
Argument BINDINGS is a list of tuples whose car is a symbol to be |
||||
bound and (optionally) used in BODY, and its cadr is a sexp to be |
||||
evalled to set symbol's value. In the special case you only want |
||||
to bind a single value, BINDINGS can just be a plain tuple." |
||||
(declare (indent 1) (debug if-let)) |
||||
(list 'if-let bindings (macroexp-progn body))))) |
||||
|
||||
(provide 'cider-compat) |
||||
;;; cider-compat.el ends here |
@ -1,164 +0,0 @@
|
||||
;;; cider-eldoc.el --- eldoc support for Clojure -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright © 2012-2015 Tim King, Phil Hagelberg |
||||
;; Copyright © 2013-2015 Bozhidar Batsov, Hugo Duncan, Steve Purcell |
||||
;; |
||||
;; Author: Tim King <kingtim@gmail.com> |
||||
;; Phil Hagelberg <technomancy@gmail.com> |
||||
;; Bozhidar Batsov <bozhidar@batsov.com> |
||||
;; Hugo Duncan <hugo@hugoduncan.org> |
||||
;; Steve Purcell <steve@sanityinc.com> |
||||
|
||||
;; 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/>. |
||||
|
||||
;; This file is not part of GNU Emacs. |
||||
|
||||
;;; Commentary: |
||||
|
||||
;; eldoc support for Clojure. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cider-client) |
||||
(require 'cider-common) ; for cider-symbol-at-point |
||||
(require 'cider-compat) |
||||
(require 'cider-util) |
||||
|
||||
(require 'cl-lib) |
||||
|
||||
(require 'eldoc) |
||||
|
||||
(defvar cider-extra-eldoc-commands '("yas-expand") |
||||
"Extra commands to be added to eldoc's safe commands list.") |
||||
|
||||
(defvar-local cider-eldoc-last-symbol nil |
||||
"The eldoc information for the last symbol we checked.") |
||||
|
||||
(defun cider-eldoc-format-thing (thing) |
||||
"Format the eldoc THING." |
||||
(propertize thing 'face 'font-lock-function-name-face)) |
||||
|
||||
(defun cider-highlight-args (arglist pos) |
||||
"Format the the function ARGLIST for eldoc. |
||||
POS is the index of the currently highlighted argument." |
||||
(let* ((rest-pos (cider--find-rest-args-position arglist)) |
||||
(i 0)) |
||||
(mapconcat |
||||
(lambda (arg) |
||||
(let ((argstr (format "%s" arg))) |
||||
(if (eq arg '&) |
||||
argstr |
||||
(prog1 |
||||
(if (or (= (1+ i) pos) |
||||
(and rest-pos (> (+ 1 i) rest-pos) |
||||
(> pos rest-pos))) |
||||
(propertize argstr 'face |
||||
'eldoc-highlight-function-argument) |
||||
argstr) |
||||
(setq i (1+ i)))))) arglist " "))) |
||||
|
||||
(defun cider--find-rest-args-position (arglist) |
||||
"Find the position of & in the ARGLIST vector." |
||||
(cl-position '& (append arglist ()))) |
||||
|
||||
(defun cider-highlight-arglist (arglist pos) |
||||
"Format the ARGLIST for eldoc. |
||||
POS is the index of the argument to highlight." |
||||
(concat "[" (cider-highlight-args arglist pos) "]")) |
||||
|
||||
(defun cider-eldoc-format-arglist (arglist pos) |
||||
"Format all the ARGLIST for eldoc. |
||||
POS is the index of current argument." |
||||
(concat "(" |
||||
(mapconcat (lambda (args) (cider-highlight-arglist args pos)) |
||||
arglist |
||||
" ") |
||||
")")) |
||||
|
||||
(defun cider-eldoc-beginning-of-sexp () |
||||
"Move to the beginning of current sexp. |
||||
|
||||
Return the number of nested sexp the point was over or after. " |
||||
(let ((parse-sexp-ignore-comments t) |
||||
(num-skipped-sexps 0)) |
||||
(condition-case _ |
||||
(progn |
||||
;; First account for the case the point is directly over a |
||||
;; beginning of a nested sexp. |
||||
(condition-case _ |
||||
(let ((p (point))) |
||||
(forward-sexp -1) |
||||
(forward-sexp 1) |
||||
(when (< (point) p) |
||||
(setq num-skipped-sexps 1))) |
||||
(error)) |
||||
(while |
||||
(let ((p (point))) |
||||
(forward-sexp -1) |
||||
(when (< (point) p) |
||||
(setq num-skipped-sexps (1+ num-skipped-sexps)))))) |
||||
(error)) |
||||
num-skipped-sexps)) |
||||
|
||||
(defun cider-eldoc-info-in-current-sexp () |
||||
"Return a list of the current sexp and the current argument index." |
||||
(save-excursion |
||||
(let ((argument-index (1- (cider-eldoc-beginning-of-sexp)))) |
||||
;; If we are at the beginning of function name, this will be -1. |
||||
(when (< argument-index 0) |
||||
(setq argument-index 0)) |
||||
;; Don't do anything if current word is inside a string, vector, |
||||
;; hash or set literal. |
||||
(if (member (or (char-after (1- (point))) 0) '(?\" ?\{ ?\[)) |
||||
nil |
||||
(list (cider-symbol-at-point) argument-index))))) |
||||
|
||||
(defun cider-eldoc-arglist (thing) |
||||
"Return the arglist for THING." |
||||
(when (and (cider-nrepl-op-supported-p "eldoc") |
||||
thing |
||||
(not (string= thing "")) |
||||
(not (string-prefix-p ":" thing))) |
||||
;; check if we can used the cached eldoc info |
||||
(if (string= thing (car cider-eldoc-last-symbol)) |
||||
(cdr cider-eldoc-last-symbol) |
||||
(when-let ((eldoc-info (cider-sync-request:eldoc (substring-no-properties thing)))) |
||||
(let ((arglist (nrepl-dict-get eldoc-info "eldoc"))) |
||||
(setq cider-eldoc-last-symbol (cons thing arglist)) |
||||
arglist))))) |
||||
|
||||
(defun cider-eldoc () |
||||
"Backend function for eldoc to show argument list in the echo area." |
||||
(when (and (cider-connected-p) |
||||
;; don't clobber an error message in the minibuffer |
||||
(not (member last-command '(next-error previous-error)))) |
||||
(let* ((info (cider-eldoc-info-in-current-sexp)) |
||||
(thing (car info)) |
||||
(pos (cadr info)) |
||||
(value (cider-eldoc-arglist thing))) |
||||
(when value |
||||
(format "%s: %s" |
||||
(cider-eldoc-format-thing thing) |
||||
(cider-eldoc-format-arglist value pos)))))) |
||||
|
||||
(defun cider-eldoc-setup () |
||||
"Turn on eldoc mode in the current buffer." |
||||
(setq-local eldoc-documentation-function #'cider-eldoc) |
||||
(apply #'eldoc-add-command cider-extra-eldoc-commands)) |
||||
|
||||
(define-obsolete-function-alias 'cider-turn-on-eldoc-mode 'eldoc-mode) |
||||
|
||||
(provide 'cider-eldoc) |
||||
|
||||
;;; cider-eldoc.el ends here |
@ -1,112 +0,0 @@
|
||||
;;; cider-grimoire.el --- Grimoire integration -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright © 2014-2015 Bozhidar Batsov |
||||
;; |
||||
;; Author: Bozhidar Batsov <bozhidar@batsov.com> |
||||
|
||||
;; 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/>. |
||||
|
||||
;; This file is not part of GNU Emacs. |
||||
|
||||
;;; Commentary: |
||||
|
||||
;; A few commands for Grimoire documentation lookup. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cider-client) |
||||
(require 'cider-common) |
||||
(require 'cider-compat) |
||||
(require 'cider-popup) |
||||
|
||||
(require 'nrepl-client) |
||||
|
||||
(require 'url-vars) |
||||
|
||||
(defconst cider-grimoire-url "http://conj.io/") |
||||
|
||||
(defun cider-grimoire-replace-special (name) |
||||
"Convert the dashes in NAME to a grimoire friendly format." |
||||
(thread-last name |
||||
(replace-regexp-in-string "\\?" "_QMARK_") |
||||
(replace-regexp-in-string "\\." "_DOT_") |
||||
(replace-regexp-in-string "\\/" "_SLASH_") |
||||
(replace-regexp-in-string "\\(\\`_\\)\\|\\(_\\'\\)" ""))) |
||||
|
||||
(defun cider-grimoire-url (name ns) |
||||
"Generate a grimoire search v0 url from NAME, NS." |
||||
(let ((base-url cider-grimoire-url)) |
||||
(when (and name ns) |
||||
(concat base-url "search/v0/" ns "/" (cider-grimoire-replace-special name) "/")))) |
||||
|
||||
(defun cider-grimoire-web-lookup (symbol) |
||||
"Look up the grimoire documentation for SYMBOL." |
||||
(if-let ((var-info (cider-var-info symbol))) |
||||
(let ((name (nrepl-dict-get var-info "name")) |
||||
(ns (nrepl-dict-get var-info "ns"))) |
||||
(browse-url (cider-grimoire-url name ns))) |
||||
(error "Symbol %s not resolved" symbol))) |
||||
|
||||
;;;###autoload |
||||
(defun cider-grimoire-web (&optional arg) |
||||
"Open grimoire documentation in the default web browser. |
||||
|
||||
Prompts for the symbol to use, or uses the symbol at point, depending on |
||||
the value of `cider-prompt-for-symbol'. With prefix arg ARG, does the |
||||
opposite of what that option dictates." |
||||
(interactive "P") |
||||
(funcall (cider-prompt-for-symbol-function arg) |
||||
"Grimoire doc for" |
||||
#'cider-grimoire-web-lookup)) |
||||
|
||||
(defun cider-create-grimoire-buffer (content) |
||||
"Create a new grimoire buffer with CONTENT." |
||||
(with-current-buffer (cider-popup-buffer "*cider grimoire*" t) |
||||
(read-only-mode -1) |
||||
(insert content) |
||||
(read-only-mode +1) |
||||
(goto-char (point-min)) |
||||
(current-buffer))) |
||||
|
||||
(defun cider-grimoire-lookup (symbol) |
||||
"Look up the grimoire documentation for SYMBOL." |
||||
(if-let ((var-info (cider-var-info symbol))) |
||||
(let ((name (nrepl-dict-get var-info "name")) |
||||
(ns (nrepl-dict-get var-info "ns")) |
||||
(url-request-method "GET") |
||||
(url-request-extra-headers `(("Content-Type" . "text/plain")))) |
||||
(url-retrieve (cider-grimoire-url name ns) |
||||
(lambda (_status) |
||||
;; we need to strip the http header |
||||
(goto-char (point-min)) |
||||
(re-search-forward "^$") |
||||
(delete-region (point-min) (point)) |
||||
(delete-blank-lines) |
||||
;; and create a new buffer with whatever is left |
||||
(pop-to-buffer (cider-create-grimoire-buffer (buffer-string)))))) |
||||
(error "Symbol %s not resolved" symbol))) |
||||
|
||||
;;;###autoload |
||||
(defun cider-grimoire (&optional arg) |
||||
"Open grimoire documentation in a popup buffer. |
||||
|
||||
Prompts for the symbol to use, or uses the symbol at point, depending on |
||||
the value of `cider-prompt-for-symbol'. With prefix arg ARG, does the |
||||
opposite of what that option dictates." |
||||
(interactive "P") |
||||
(funcall (cider-prompt-for-symbol-function arg) |
||||
"Grimoire doc for" |
||||
#'cider-grimoire-lookup)) |
||||
|
||||
(provide 'cider-grimoire) |
@ -1,309 +0,0 @@
|
||||
;;; cider-inspector.el --- Object inspector -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright © 2013-2015 Vital Reactor, LLC |
||||
;; Copyright © 2014-2015 Bozhidar Batsov |
||||
|
||||
;; Author: Ian Eslick <ian@vitalreactor.com> |
||||
;; Bozhidar Batsov <bozhidar@batsov.com> |
||||
|
||||
;; 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/>. |
||||
|
||||
;; This file is not part of GNU Emacs. |
||||
|
||||
;;; Commentary: |
||||
|
||||
;; Clojure object inspector inspired by SLIME. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cl-lib) |
||||
(require 'cider-interaction) |
||||
|
||||
;; =================================== |
||||
;; Inspector Key Map and Derived Mode |
||||
;; =================================== |
||||
|
||||
(defconst cider-inspector-buffer "*cider inspect*") |
||||
|
||||
;;; Customization |
||||
(defgroup cider-inspector nil |
||||
"Presentation and behaviour of the cider value inspector." |
||||
:prefix "cider-inspector-" |
||||
:group 'cider |
||||
:package-version '(cider . "0.10.0")) |
||||
|
||||
(defcustom cider-inspector-page-size 32 |
||||
"Default page size in paginated inspector view. |
||||
The page size can be also changed interactively within the inspector." |
||||
:type '(integer :tag "Page size" 32) |
||||
:group 'cider-inspector |
||||
:package-version '(cider . "0.10.0")) |
||||
|
||||
(push cider-inspector-buffer cider-ancillary-buffers) |
||||
|
||||
(defvar cider-inspector-mode-map |
||||
(let ((map (make-sparse-keymap))) |
||||
(set-keymap-parent map cider-popup-buffer-mode-map) |
||||
(define-key map [return] #'cider-inspector-operate-on-point) |
||||
(define-key map "\C-m" #'cider-inspector-operate-on-point) |
||||
(define-key map [mouse-1] #'cider-inspector-operate-on-click) |
||||
(define-key map "l" #'cider-inspector-pop) |
||||
(define-key map "g" #'cider-inspector-refresh) |
||||
;; Page-up/down |
||||
(define-key map [next] #'cider-inspector-next-page) |
||||
(define-key map [prior] #'cider-inspector-prev-page) |
||||
(define-key map " " #'cider-inspector-next-page) |
||||
(define-key map (kbd "M-SPC") #'cider-inspector-prev-page) |
||||
(define-key map (kbd "S-SPC") #'cider-inspector-prev-page) |
||||
(define-key map "s" #'cider-inspector-set-page-size) |
||||
(define-key map [tab] #'cider-inspector-next-inspectable-object) |
||||
(define-key map "\C-i" #'cider-inspector-next-inspectable-object) |
||||
(define-key map [(shift tab)] #'cider-inspector-previous-inspectable-object) ; Emacs translates S-TAB |
||||
(define-key map [backtab] #'cider-inspector-previous-inspectable-object) ; to BACKTAB on X. |
||||
map)) |
||||
|
||||
(define-derived-mode cider-inspector-mode fundamental-mode "Inspector" |
||||
"Major mode for inspecting Clojure data structures. |
||||
|
||||
\\{cider-inspector-mode-map}" |
||||
(set-syntax-table clojure-mode-syntax-table) |
||||
(setq buffer-read-only t) |
||||
(setq-local electric-indent-chars nil) |
||||
(setq-local truncate-lines t)) |
||||
|
||||
;;;###autoload |
||||
(defun cider-inspect (expression) |
||||
"Eval the string EXPRESSION and inspect the result." |
||||
(interactive |
||||
(list (cider-read-from-minibuffer "Inspect value: " |
||||
(cider-sexp-at-point)))) |
||||
(cider-inspect-expr expression (cider-current-ns))) |
||||
|
||||
;; Operations |
||||
(defun cider-inspector--value-handler (_buffer value) |
||||
(cider-make-popup-buffer cider-inspector-buffer 'cider-inspector-mode) |
||||
(cider-irender cider-inspector-buffer value)) |
||||
|
||||
(defun cider-inspector--out-handler (_buffer value) |
||||
(cider-emit-interactive-eval-output value)) |
||||
|
||||
(defun cider-inspector--err-handler (_buffer err) |
||||
(cider-emit-interactive-eval-err-output err)) |
||||
|
||||
(defun cider-inspector--done-handler (buffer) |
||||
(when (get-buffer cider-inspector-buffer) |
||||
(with-current-buffer buffer |
||||
(cider-popup-buffer-display cider-inspector-buffer t)))) |
||||
|
||||
(defun cider-inspector-response-handler (buffer) |
||||
"Create an inspector response handler for BUFFER. |
||||
|
||||
The \"value\" slot of each successive response (if it exists) will be |
||||
rendered into `cider-inspector-buffer'. Once a response is received with a |
||||
\"status\" slot containing \"done\", `cider-inspector-buffer' will be |
||||
displayed. |
||||
|
||||
Used for all inspector nREPL ops." |
||||
(nrepl-make-response-handler buffer |
||||
#'cider-inspector--value-handler |
||||
#'cider-inspector--out-handler |
||||
#'cider-inspector--err-handler |
||||
#'cider-inspector--done-handler)) |
||||
|
||||
(defun cider-inspect-expr (expr ns) |
||||
(cider--prep-interactive-eval expr) |
||||
(cider-nrepl-send-request (append (nrepl--eval-request expr (cider-current-session) ns) |
||||
(list "inspect" "true" |
||||
"page-size" (or cider-inspector-page-size 32))) |
||||
(cider-inspector-response-handler (current-buffer)))) |
||||
|
||||
(defun cider-inspector-pop () |
||||
(interactive) |
||||
(cider-nrepl-send-request (list "op" "inspect-pop" |
||||
"session" (cider-current-session)) |
||||
(cider-inspector-response-handler (current-buffer)))) |
||||
|
||||
(defun cider-inspector-push (idx) |
||||
(cider-nrepl-send-request (list "op" "inspect-push" |
||||
"idx" (number-to-string idx) |
||||
"session" (cider-current-session)) |
||||
(cider-inspector-response-handler (current-buffer)))) |
||||
|
||||
(defun cider-inspector-refresh () |
||||
(interactive) |
||||
(cider-nrepl-send-request (list "op" "inspect-refresh" |
||||
"session" (cider-current-session)) |
||||
(cider-inspector-response-handler (current-buffer)))) |
||||
|
||||
(defun cider-inspector-next-page () |
||||
"Jump to the next page when inspecting a paginated sequence/map. |
||||
|
||||
Does nothing if already on the last page." |
||||
(interactive) |
||||
(cider-nrepl-send-request (list "op" "inspect-next-page" |
||||
"session" (cider-current-session)) |
||||
(cider-inspector-response-handler (current-buffer)))) |
||||
|
||||
(defun cider-inspector-prev-page () |
||||
"Jump to the previous page when expecting a paginated sequence/map. |
||||
|
||||
Does nothing if already on the first page." |
||||
(interactive) |
||||
(cider-nrepl-send-request (list "op" "inspect-prev-page" |
||||
"session" (cider-current-session)) |
||||
(cider-inspector-response-handler (current-buffer)))) |
||||
|
||||
(defun cider-inspector-set-page-size (page-size) |
||||
"Set the page size in pagination mode to the specified value. |
||||
|
||||
Current page will be reset to zero." |
||||
(interactive "nPage size:") |
||||
(cider-nrepl-send-request (list "op" "inspect-set-page-size" |
||||
"session" (cider-current-session) |
||||
"page-size" page-size) |
||||
(cider-inspector-response-handler (current-buffer)))) |
||||
|
||||
;; Render Inspector from Structured Values |
||||
(defun cider-irender (buffer str) |
||||
(with-current-buffer buffer |
||||
(cider-inspector-mode) |
||||
(let ((inhibit-read-only t)) |
||||
(condition-case nil |
||||
(cider-irender* (car (read-from-string str))) |
||||
(error (insert "\nInspector error for: " str)))) |
||||
(goto-char (point-min)))) |
||||
|
||||
(defun cider-irender* (elements) |
||||
(dolist (el elements) |
||||
(cider-irender-el* el))) |
||||
|
||||
(defun cider-irender-el* (el) |
||||
(cond ((symbolp el) (insert (symbol-name el))) |
||||
((stringp el) (insert (propertize el 'font-lock-face 'font-lock-keyword-face))) |
||||
((and (consp el) (eq (car el) :newline)) |
||||
(insert "\n")) |
||||
((and (consp el) (eq (car el) :value)) |
||||
(cider-irender-value (cadr el) (cl-caddr el))) |
||||
(t (message "Unrecognized inspector object: %s" el)))) |
||||
|
||||
(defun cider-irender-value (value idx) |
||||
(cider-propertize-region |
||||
(list 'cider-value-idx idx |
||||
'mouse-face 'highlight) |
||||
(cider-irender-el* (cider-font-lock-as-clojure value)))) |
||||
|
||||
|
||||
;; =================================================== |
||||
;; Inspector Navigation (lifted from SLIME inspector) |
||||
;; =================================================== |
||||
|
||||
(defun cider-find-inspectable-object (direction limit) |
||||
"Find the next/previous inspectable object. |
||||
DIRECTION can be either 'next or 'prev. |
||||
LIMIT is the maximum or minimum position in the current buffer. |
||||
|
||||
Return a list of two values: If an object could be found, the |
||||
starting position of the found object and T is returned; |
||||
otherwise LIMIT and NIL is returned." |
||||
(let ((finder (cl-ecase direction |
||||
(next 'next-single-property-change) |
||||
(prev 'previous-single-property-change)))) |
||||
(let ((prop nil) (curpos (point))) |
||||
(while (and (not prop) (not (= curpos limit))) |
||||
(let ((newpos (funcall finder curpos 'cider-value-idx nil limit))) |
||||
(setq prop (get-text-property newpos 'cider-value-idx)) |
||||
(setq curpos newpos))) |
||||
(list curpos (and prop t))))) |
||||
|
||||
(defun cider-inspector-next-inspectable-object (arg) |
||||
"Move point to the next inspectable object. |
||||
With optional ARG, move across that many objects. |
||||
If ARG is negative, move backwards." |
||||
(interactive "p") |
||||
(let ((maxpos (point-max)) (minpos (point-min)) |
||||
(previously-wrapped-p nil)) |
||||
;; Forward. |
||||
(while (> arg 0) |
||||
(cl-destructuring-bind (pos foundp) |
||||
(cider-find-inspectable-object 'next maxpos) |
||||
(if foundp |
||||
(progn (goto-char pos) (setq arg (1- arg)) |
||||
(setq previously-wrapped-p nil)) |
||||
(if (not previously-wrapped-p) ; cycle detection |
||||
(progn (goto-char minpos) (setq previously-wrapped-p t)) |
||||
(error "No inspectable objects"))))) |
||||
;; Backward. |
||||
(while (< arg 0) |
||||
(cl-destructuring-bind (pos foundp) |
||||
(cider-find-inspectable-object 'prev minpos) |
||||
;; CIDER-OPEN-INSPECTOR inserts the title of an inspector page |
||||
;; as a presentation at the beginning of the buffer; skip |
||||
;; that. (Notice how this problem can not arise in ``Forward.'') |
||||
(if (and foundp (/= pos minpos)) |
||||
(progn (goto-char pos) (setq arg (1+ arg)) |
||||
(setq previously-wrapped-p nil)) |
||||
(if (not previously-wrapped-p) ; cycle detection |
||||
(progn (goto-char maxpos) (setq previously-wrapped-p t)) |
||||
(error "No inspectable objects"))))))) |
||||
|
||||
(defun cider-inspector-previous-inspectable-object (arg) |
||||
"Move point to the previous inspectable object. |
||||
With optional ARG, move across that many objects. |
||||
If ARG is negative, move forwards." |
||||
(interactive "p") |
||||
(cider-inspector-next-inspectable-object (- arg))) |
||||
|
||||
(defun cider-inspector-property-at-point () |
||||
(let* ((properties '(cider-value-idx cider-range-button |
||||
cider-action-number)) |
||||
(find-property |
||||
(lambda (point) |
||||
(cl-loop for property in properties |
||||
for value = (get-text-property point property) |
||||
when value |
||||
return (list property value))))) |
||||
(or (funcall find-property (point)) |
||||
(funcall find-property (1- (point)))))) |
||||
|
||||
(defun cider-inspector-operate-on-point () |
||||
"Invoke the command for the text at point. |
||||
1. If point is on a value then recursivly call the inspector on |
||||
that value. |
||||
2. If point is on an action then call that action. |
||||
3. If point is on a range-button fetch and insert the range." |
||||
(interactive) |
||||
(cl-destructuring-bind (property value) |
||||
(cider-inspector-property-at-point) |
||||
(cl-case property |
||||
(cider-value-idx |
||||
(cider-inspector-push value)) |
||||
;; TODO: range and action handlers |
||||
(t (error "No object at point"))))) |
||||
|
||||
(defun cider-inspector-operate-on-click (event) |
||||
"Move to EVENT's position and operate the part." |
||||
(interactive "@e") |
||||
(let ((point (posn-point (event-end event)))) |
||||
(cond ((and point |
||||
(or (get-text-property point 'cider-value-idx))) |
||||
;; (get-text-property point 'cider-range-button) |
||||
;; (get-text-property point 'cider-action-number))) |
||||
(goto-char point) |
||||
(cider-inspector-operate-on-point)) |
||||
(t |
||||
(error "No clickable part here"))))) |
||||
|
||||
(provide 'cider-inspector) |
||||
|
||||
;;; cider-inspector.el ends here |
File diff suppressed because it is too large
Load Diff
@ -1,208 +0,0 @@
|
||||
;;; cider-macroexpansion.el --- Macro expansion support -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright © 2012-2015 Tim King, Phil Hagelberg |
||||
;; Copyright © 2013-2015 Bozhidar Batsov, Hugo Duncan, Steve Purcell |
||||
;; |
||||
;; Author: Tim King <kingtim@gmail.com> |
||||
;; Phil Hagelberg <technomancy@gmail.com> |
||||
;; Bozhidar Batsov <bozhidar@batsov.com> |
||||
;; Hugo Duncan <hugo@hugoduncan.org> |
||||
;; Steve Purcell <steve@sanityinc.com> |
||||
|
||||
;; 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/>. |
||||
|
||||
;; This file is not part of GNU Emacs. |
||||
|
||||
;;; Commentary: |
||||
|
||||
;; Macro expansion support. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cider-mode) |
||||
(require 'cider-compat) |
||||
|
||||
(defconst cider-macroexpansion-buffer "*cider-macroexpansion*") |
||||
|
||||
(push cider-macroexpansion-buffer cider-ancillary-buffers) |
||||
|
||||
(defcustom cider-macroexpansion-display-namespaces 'tidy |
||||
"Determines if namespaces are displayed in the macroexpansion buffer. |
||||
Possible values are: |
||||
|
||||
'qualified ;=> Vars are fully-qualified in the expansion |
||||
'none ;=> Vars are displayed without namespace qualification |
||||
'tidy ;=> Vars that are :refer-ed or defined in the current namespace are |
||||
displayed with their simple name, non-refered vars from other |
||||
namespaces are refered using the alias for that namespace (if |
||||
defined), other vars are displayed fully qualified." |
||||
:type '(choice (const :tag "Suppress namespaces" none) |
||||
(const :tag "Show fully-qualified namespaces" qualified) |
||||
(const :tag "Show namespace aliases" tidy)) |
||||
:group 'cider |
||||
:package-version '(cider . "0.7.0")) |
||||
|
||||
(defcustom cider-macroexpansion-print-metadata nil |
||||
"Determines if metadata is included in macroexpansion results." |
||||
:type 'boolean |
||||
:group 'cider |
||||
:package-version '(cider . "0.9.0")) |
||||
|
||||
(defun cider-sync-request:macroexpand (expander expr &optional display-namespaces) |
||||
"Macroexpand, using EXPANDER, the given EXPR. |
||||
The default for DISPLAY-NAMESPACES is taken from |
||||
`cider-macroexpansion-display-namespaces'." |
||||
(cider-ensure-op-supported "macroexpand") |
||||
(thread-first (list "op" "macroexpand" |
||||
"expander" expander |
||||
"code" expr |
||||
"ns" (cider-current-ns) |
||||
"display-namespaces" |
||||
(or display-namespaces |
||||
(symbol-name cider-macroexpansion-display-namespaces))) |
||||
(append (when cider-macroexpansion-print-metadata |
||||
(list "print-meta" "true"))) |
||||
(cider-nrepl-send-sync-request) |
||||
(nrepl-dict-get "expansion"))) |
||||
|
||||
(defun cider-macroexpand-undo (&optional arg) |
||||
"Undo the last macroexpansion, using `undo-only'. |
||||
ARG is passed along to `undo-only'." |
||||
(interactive) |
||||
(let ((inhibit-read-only t)) |
||||
(undo-only arg))) |
||||
|
||||
(defvar cider-last-macroexpand-expression nil |
||||
"Specify the last macroexpansion preformed. |
||||
This variable specifies both what was expanded and the expander.") |
||||
|
||||
(defun cider-macroexpand-expr (expander expr) |
||||
"Macroexpand, use EXPANDER, the given EXPR." |
||||
(let* ((expansion (cider-sync-request:macroexpand expander expr))) |
||||
(setq cider-last-macroexpand-expression expr) |
||||
(cider-initialize-macroexpansion-buffer expansion (cider-current-ns)))) |
||||
|
||||
(defun cider-macroexpand-expr-inplace (expander) |
||||
"Substitute the form preceding point with its macroexpansion using EXPANDER." |
||||
(interactive) |
||||
(let* ((expansion (cider-sync-request:macroexpand expander (cider-last-sexp))) |
||||
(bounds (cons (save-excursion (clojure-backward-logical-sexp 1) (point)) (point)))) |
||||
(cider-redraw-macroexpansion-buffer |
||||
expansion (current-buffer) (car bounds) (cdr bounds)))) |
||||
|
||||
(defun cider-macroexpand-again () |
||||
"Repeat the last macroexpansion." |
||||
(interactive) |
||||
(cider-initialize-macroexpansion-buffer cider-last-macroexpand-expression (cider-current-ns))) |
||||
|
||||
;;;###autoload |
||||
(defun cider-macroexpand-1 (&optional prefix) |
||||
"Invoke 'macroexpand-1' on the expression preceding point. |
||||
If invoked with a PREFIX argument, use 'macroexpand' instead of |
||||
'macroexpand-1'." |
||||
(interactive "P") |
||||
(let ((expander (if prefix "macroexpand" "macroexpand-1"))) |
||||
(cider-macroexpand-expr expander (cider-last-sexp)))) |
||||
|
||||
(defun cider-macroexpand-1-inplace (&optional prefix) |
||||
"Perform inplace 'macroexpand-1' on the expression preceding point. |
||||
If invoked with a PREFIX argument, use 'macroexpand' instead of |
||||
'macroexpand-1'." |
||||
(interactive "P") |
||||
(let ((expander (if prefix "macroexpand" "macroexpand-1"))) |
||||
(cider-macroexpand-expr-inplace expander))) |
||||
|
||||
;;;###autoload |
||||
(defun cider-macroexpand-all () |
||||
"Invoke 'clojure.walk/macroexpand-all' on the expression preceding point." |
||||
(interactive) |
||||
(cider-macroexpand-expr "macroexpand-all" (cider-last-sexp))) |
||||
|
||||
(defun cider-macroexpand-all-inplace () |
||||
"Perform inplace 'clojure.walk/macroexpand-all' on the expression preceding point." |
||||
(interactive) |
||||
(cider-macroexpand-expr-inplace "macroexpand-all")) |
||||
|
||||
(defun cider-initialize-macroexpansion-buffer (expansion ns) |
||||
"Create a new Macroexpansion buffer with EXPANSION and namespace NS." |
||||
(pop-to-buffer (cider-create-macroexpansion-buffer)) |
||||
(setq cider-buffer-ns ns) |
||||
(setq buffer-undo-list nil) |
||||
(let ((inhibit-read-only t) |
||||
(buffer-undo-list t)) |
||||
(erase-buffer) |
||||
(insert (format "%s" expansion)) |
||||
(goto-char (point-max)) |
||||
(cider--font-lock-ensure))) |
||||
|
||||
(defun cider-redraw-macroexpansion-buffer (expansion buffer start end) |
||||
"Redraw the macroexpansion with new EXPANSION. |
||||
Text in BUFFER from START to END is replaced with new expansion, |
||||
and point is placed after the expanded form." |
||||
(with-current-buffer buffer |
||||
(let ((buffer-read-only nil)) |
||||
(goto-char start) |
||||
(delete-region start end) |
||||
(insert (format "%s" expansion)) |
||||
(goto-char start) |
||||
(indent-sexp) |
||||
(forward-sexp)))) |
||||
|
||||
(declare-function cider-mode "cider-mode") |
||||
|
||||
(defun cider-create-macroexpansion-buffer () |
||||
"Create a new macroexpansion buffer." |
||||
(with-current-buffer (cider-popup-buffer cider-macroexpansion-buffer t) |
||||
(clojure-mode) |
||||
(cider-mode -1) |
||||
(cider-macroexpansion-mode 1) |
||||
(current-buffer))) |
||||
|
||||
(defvar cider-macroexpansion-mode-map |
||||
(let ((map (make-sparse-keymap))) |
||||
(define-key map (kbd "g") #'cider-macroexpand-again) |
||||
(define-key map (kbd "q") #'cider-popup-buffer-quit-function) |
||||
(define-key map (kbd "d") #'cider-doc) |
||||
(define-key map (kbd "j") #'cider-javadoc) |
||||
(define-key map (kbd ".") #'cider-find-var) |
||||
(easy-menu-define cider-macroexpansion-mode-menu map |
||||
"Menu for CIDER's doc mode" |
||||
'("Macroexpansion" |
||||
["Restart expansion" cider-macroexpand-again] |
||||
["Macroexpand-1" cider-macroexpand-1-inplace] |
||||
["Macroexpand-all" cider-macroexpand-all-inplace] |
||||
["Go to source" cider-find-var] |
||||
["Go to doc" cider-doc] |
||||
["Go to Javadoc" cider-docview-javadoc] |
||||
["Quit" cider-popup-buffer-quit-function])) |
||||
(cl-labels ((redefine-key (from to) |
||||
(dolist (mapping (where-is-internal from cider-mode-map)) |
||||
(define-key map mapping to)))) |
||||
(redefine-key 'cider-macroexpand-1 #'cider-macroexpand-1-inplace) |
||||
(redefine-key 'cider-macroexpand-all #'cider-macroexpand-all-inplace) |
||||
(redefine-key 'advertised-undo #'cider-macroexpand-undo) |
||||
(redefine-key 'undo #'cider-macroexpand-undo)) |
||||
map)) |
||||
|
||||
(define-minor-mode cider-macroexpansion-mode |
||||
"Minor mode for CIDER macroexpansion. |
||||
|
||||
\\{cider-macroexpansion-mode-map}" |
||||
nil |
||||
" Macroexpand" |
||||
cider-macroexpansion-mode-map) |
||||
|
||||
(provide 'cider-macroexpansion) |
||||
|
||||
;;; cider-macroexpansion.el ends here |
@ -1,12 +0,0 @@
|
||||
(define-package "cider" "20151022.28" "Clojure Interactive Development Environment that Rocks" |
||||
'((clojure-mode "5.0.0") |
||||
(pkg-info "0.4") |
||||
(emacs "24.3") |
||||
(queue "0.1.1") |
||||
(spinner "1.4") |
||||
(seq "1.9")) |
||||
:url "http://www.github.com/clojure-emacs/cider" :keywords |
||||
'("languages" "clojure" "cider")) |
||||
;; Local Variables: |
||||
;; no-byte-compile: t |
||||
;; End: |
@ -1,122 +0,0 @@
|
||||
;;; cider-popup.el --- Creating and quitting popup buffers -*- lexical-binding: t; -*- |
||||
|
||||
;; Copyright (C) 2015 Artur Malabarba |
||||
|
||||
;; Author: Artur Malabarba <bruce.connor.am@gmail.com> |
||||
|
||||
;; 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/>. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'nrepl-client) |
||||
(require 'cider-compat) |
||||
|
||||
(define-minor-mode cider-popup-buffer-mode |
||||
"Mode for CIDER popup buffers" |
||||
nil |
||||
(" cider-tmp") |
||||
'(("q" . cider-popup-buffer-quit-function))) |
||||
|
||||
(defvar-local cider-popup-buffer-quit-function #'cider-popup-buffer-quit |
||||
"The function that is used to quit a temporary popup buffer.") |
||||
|
||||
(defun cider-popup-buffer-quit-function (&optional kill-buffer-p) |
||||
"Wrapper to invoke the function `cider-popup-buffer-quit-function'. |
||||
KILL-BUFFER-P is passed along." |
||||
(interactive) |
||||
(funcall cider-popup-buffer-quit-function kill-buffer-p)) |
||||
|
||||
(defun cider-popup-buffer (name &optional select mode ancillary) |
||||
"Create new popup buffer called NAME. |
||||
If SELECT is non-nil, select the newly created window. |
||||
If major MODE is non-nil, enable it for the popup buffer. |
||||
If ANCILLARY is non-nil, the buffer is added to `cider-ancillary-buffers' |
||||
and automatically removed when killed." |
||||
(thread-first (cider-make-popup-buffer name mode ancillary) |
||||
(cider-popup-buffer-display select))) |
||||
|
||||
(defun cider-popup-buffer-display (buffer &optional select) |
||||
"Display BUFFER. |
||||
If SELECT is non-nil, select the BUFFER." |
||||
(let ((window (get-buffer-window buffer))) |
||||
(when window |
||||
(with-current-buffer buffer |
||||
(set-window-point window (point)))) |
||||
;; If the buffer we are popping up is already displayed in the selected |
||||
;; window, the below `inhibit-same-window' logic will cause it to be |
||||
;; displayed twice - so we early out in this case. Note that we must check |
||||
;; `selected-window', as async request handlers are executed in the context |
||||
;; of the current connection buffer (i.e. `current-buffer' is dynamically |
||||
;; bound to that). |
||||
(unless (eq window (selected-window)) |
||||
;; Non nil `inhibit-same-window' ensures that current window is not covered |
||||
(if select |
||||
(pop-to-buffer buffer `(nil . ((inhibit-same-window . ,pop-up-windows)))) |
||||
(display-buffer buffer `(nil . ((inhibit-same-window . ,pop-up-windows))))))) |
||||
buffer) |
||||
|
||||
(defun cider-popup-buffer-quit (&optional kill) |
||||
"Quit the current (temp) window and bury its buffer using `quit-restore-window'. |
||||
If prefix argument KILL is non-nil, kill the buffer instead of burying it." |
||||
(interactive) |
||||
(quit-restore-window (selected-window) (if kill 'kill 'append))) |
||||
|
||||
(defvar-local cider-popup-output-marker nil) |
||||
|
||||
(defvar cider-ancillary-buffers (list nrepl-message-buffer-name)) |
||||
|
||||
(defun cider-make-popup-buffer (name &optional mode ancillary) |
||||
"Create a temporary buffer called NAME using major MODE (if specified). |
||||
If ANCILLARY is non-nil, the buffer is added to `cider-ancillary-buffers' |
||||
and automatically removed when killed." |
||||
(with-current-buffer (get-buffer-create name) |
||||
(kill-all-local-variables) |
||||
(setq buffer-read-only nil) |
||||
(erase-buffer) |
||||
(when mode |
||||
(funcall mode)) |
||||
(cider-popup-buffer-mode 1) |
||||
(setq cider-popup-output-marker (point-marker)) |
||||
(setq buffer-read-only t) |
||||
(when ancillary |
||||
(add-to-list 'cider-ancillary-buffers name) |
||||
(add-hook 'kill-buffer-hook |
||||
(lambda () (setq cider-ancillary-buffers (remove name cider-ancillary-buffers))) |
||||
nil 'local)) |
||||
(current-buffer))) |
||||
|
||||
(defun cider-emit-into-popup-buffer (buffer value &optional face) |
||||
"Emit into BUFFER the provided VALUE." |
||||
;; Long string output renders emacs unresponsive and users might intentionally |
||||
;; kill the frozen popup buffer. Therefore, we don't re-create the buffer and |
||||
;; silently ignore the output. |
||||
(when (buffer-live-p buffer) |
||||
(with-current-buffer buffer |
||||
(let ((inhibit-read-only t) |
||||
(buffer-undo-list t) |
||||
(moving (= (point) cider-popup-output-marker))) |
||||
(save-excursion |
||||
(goto-char cider-popup-output-marker) |
||||
(let ((value-str (format "%s" value))) |
||||
(when face |
||||
(if (fboundp 'add-face-text-property) |
||||
(add-face-text-property 0 (length value-str) face nil value-str) |
||||
(add-text-properties 0 (length value-str) (list 'face face) value-str))) |
||||
(insert value-str)) |
||||
(indent-sexp) |
||||
(set-marker cider-popup-output-marker (point))) |
||||
(when moving (goto-char cider-popup-output-marker)))))) |
||||
|
||||
(provide 'cider-popup) |
||||
;;; cider-popup.el ends here |
File diff suppressed because it is too large
Load Diff
@ -1,129 +0,0 @@
|
||||
;;; cider-resolve.el --- Resolve clojure symbols according to current nREPL connection |
||||
|
||||
;; Copyright © 2015 Artur Malabarba |
||||
|
||||
;; Author: Artur Malabarba <bruce.connor.am@gmail.com> |
||||
|
||||
;; 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: |
||||
|
||||
;; The ns cache is a dict of namespaces stored in the connection buffer. This |
||||
;; file offers functions to easily get information about variables from this |
||||
;; cache, given the variable's name and the file's namespace. This |
||||
;; functionality is similar to that offered by the `cider-var-info' function |
||||
;; (and others). The difference is that all functions in this file operate |
||||
;; without contacting the server (they still rely on an active connection |
||||
;; buffer, but no messages are actually exchanged). |
||||
|
||||
;; For this reason, the functions here are well suited for very |
||||
;; performance-sentitive operations, such as font-locking or |
||||
;; indentation. Meanwhile, operations like code-jumping are better off |
||||
;; communicating with the middleware, just in the off chance that the cache is |
||||
;; outdated. |
||||
|
||||
;; Below is a typical entry on this cache dict. Note that clojure.core symbols |
||||
;; are excluded from the refers to save space. |
||||
|
||||
;; "cider.nrepl.middleware.track-state" |
||||
;; (dict "aliases" |
||||
;; (dict "cljs" "cider.nrepl.middleware.util.cljs" |
||||
;; "misc" "cider.nrepl.middleware.util.misc" |
||||
;; "set" "clojure.set") |
||||
;; "interns" (dict a |
||||
;; "assoc-state" (dict "arglists" |
||||
;; (("response" |
||||
;; (dict "as" "msg" "keys" |
||||
;; ("session"))))) |
||||
;; "filter-core" (dict "arglists" |
||||
;; (("refers"))) |
||||
;; "make-transport" (dict "arglists" |
||||
;; (((dict "as" "msg" "keys" |
||||
;; ("transport"))))) |
||||
;; "ns-as-map" (dict "arglists" |
||||
;; (("ns"))) |
||||
;; "ns-cache" (dict) |
||||
;; "relevant-meta" (dict "arglists" |
||||
;; (("var"))) |
||||
;; "update-vals" (dict "arglists" |
||||
;; (("m" "f"))) |
||||
;; "wrap-tracker" (dict "arglists" |
||||
;; (("handler")))) |
||||
;; "refers" (dict "set-descriptor!" "#'clojure.tools.nrepl.middleware/set-descriptor!")) |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cider-client) |
||||
(require 'nrepl-client) |
||||
(require 'cider-util) |
||||
|
||||
(defvar cider-repl-ns-cache) |
||||
|
||||
(defun cider-resolve--get-in (&rest keys) |
||||
"Return (nrepl-dict-get-in cider-repl-ns-cache KEYS)." |
||||
(when cider-connections |
||||
(with-current-buffer (cider-current-connection) |
||||
(nrepl-dict-get-in cider-repl-ns-cache keys)))) |
||||
|
||||
(defun cider-resolve-alias (ns alias) |
||||
"Return the namespace that ALIAS refers to in namespace NS. |
||||
If it doesn't point anywhere, returns ALIAS." |
||||
(or (cider-resolve--get-in ns "aliases" alias) |
||||
alias)) |
||||
|
||||
(defconst cider-resolve--prefix-regexp "\\`\\(?:#'\\)?\\([^/]+\\)/") |
||||
|
||||
(defun cider-resolve-var (ns var) |
||||
"Return a dict of the metadata of a clojure var VAR in namespace NS. |
||||
VAR is a string. |
||||
Return nil only if VAR cannot be resolved." |
||||
(let* ((var-ns (when (string-match cider-resolve--prefix-regexp var) |
||||
(cider-resolve-alias ns (match-string 1 var)))) |
||||
(name (replace-regexp-in-string cider-resolve--prefix-regexp "" var))) |
||||
(or |
||||
(cider-resolve--get-in (or var-ns ns) "interns" name) |
||||
(unless var-ns |
||||
;; If the var had no prefix, it might be referred. |
||||
(if-let ((referal (cider-resolve--get-in ns "refers" name))) |
||||
(cider-resolve-var ns referal) |
||||
;; Or it might be from core. |
||||
(unless (equal ns "clojure.core") |
||||
(cider-resolve-var "clojure.core" name))))))) |
||||
|
||||
(defun cider-resolve-core-ns () |
||||
"Return a dict of the core namespace for current connection. |
||||
This will be clojure.core or cljs.core depending on `cider-repl-type'." |
||||
(when (cider-connected-p) |
||||
(with-current-buffer (cider-current-connection) |
||||
(cider-resolve--get-in (if (equal cider-repl-type "cljs") |
||||
"cljs.core" |
||||
"clojure.core"))))) |
||||
|
||||
(defun cider-resolve-ns-symbols (ns) |
||||
"Return a plist of all valid symbols in NS. |
||||
Each entry's value is the metadata of the var that the symbol refers to. |
||||
NS can be the namespace name, or a dict of the namespace itself." |
||||
(when-let ((dict (if (stringp ns) |
||||
(cider-resolve--get-in ns) |
||||
ns))) |
||||
(nrepl-dbind-response dict (interns refers aliases) |
||||
(append (cdr interns) |
||||
(nrepl-dict-flat-map (lambda (alias namespace) |
||||
(nrepl-dict-flat-map (lambda (sym meta) |
||||
(list (concat alias "/" sym) meta)) |
||||
(cider-resolve--get-in namespace "interns"))) |
||||
aliases))))) |
||||
|
||||
(provide 'cider-resolve) |
||||
;;; cider-resolve.el ends here |
@ -1,71 +0,0 @@
|
||||
;;; cider-scratch.el --- *scratch* buffer for Clojure -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright © 2014-2015 Bozhidar Batsov |
||||
;; |
||||
;; Author: Tim King <kingtim@gmail.com> |
||||
;; Phil Hagelberg <technomancy@gmail.com> |
||||
;; Bozhidar Batsov <bozhidar@batsov.com> |
||||
;; Hugo Duncan <hugo@hugoduncan.org> |
||||
;; Steve Purcell <steve@sanityinc.com> |
||||
|
||||
;; 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/>. |
||||
|
||||
;; This file is not part of GNU Emacs. |
||||
|
||||
;;; Commentary: |
||||
|
||||
;; Imitate Emacs's *scratch* buffer. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cider-interaction) |
||||
(require 'clojure-mode) |
||||
|
||||
(defvar cider-clojure-interaction-mode-map |
||||
(let ((map (make-sparse-keymap))) |
||||
(set-keymap-parent map clojure-mode-map) |
||||
(define-key map (kbd "C-j") #'cider-eval-print-last-sexp) |
||||
map)) |
||||
|
||||
(defconst cider-scratch-buffer-name "*cider-scratch*") |
||||
|
||||
;;;###autoload |
||||
(defun cider-scratch () |
||||
"Create a scratch buffer." |
||||
(interactive) |
||||
(pop-to-buffer (cider-find-or-create-scratch-buffer))) |
||||
|
||||
(defun cider-find-or-create-scratch-buffer () |
||||
"Find or create the scratch buffer." |
||||
(or (get-buffer cider-scratch-buffer-name) |
||||
(cider-create-scratch-buffer))) |
||||
|
||||
(define-derived-mode cider-clojure-interaction-mode clojure-mode "Clojure Interaction" |
||||
"Major mode for typing and evaluating Clojure forms. |
||||
Like Lisp mode except that \\[cider-eval-print-last-sexp] evals the Lisp expression |
||||
before point, and prints its value into the buffer, advancing point. |
||||
|
||||
\\{cider-clojure-interaction-mode-map}") |
||||
|
||||
(defun cider-create-scratch-buffer () |
||||
"Create a new scratch buffer." |
||||
(with-current-buffer (get-buffer-create cider-scratch-buffer-name) |
||||
(cider-clojure-interaction-mode) |
||||
(insert ";; This buffer is for Clojure experiments and evaluation.\n" |
||||
";; Press C-j to evaluate the last expression.\n\n") |
||||
(current-buffer))) |
||||
|
||||
(provide 'cider-scratch) |
||||
|
||||
;;; cider-scratch.el ends here |
@ -1,154 +0,0 @@
|
||||
;;; cider-selector.el --- Buffer selection command inspired by SLIME's selector -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright © 2012-2015 Tim King, Phil Hagelberg |
||||
;; Copyright © 2013-2015 Bozhidar Batsov, Hugo Duncan, Steve Purcell |
||||
;; |
||||
;; Author: Tim King <kingtim@gmail.com> |
||||
;; Phil Hagelberg <technomancy@gmail.com> |
||||
;; Bozhidar Batsov <bozhidar@batsov.com> |
||||
;; Hugo Duncan <hugo@hugoduncan.org> |
||||
;; Steve Purcell <steve@sanityinc.com> |
||||
|
||||
;; 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/>. |
||||
|
||||
;; This file is not part of GNU Emacs. |
||||
|
||||
;;; Commentary: |
||||
|
||||
;; Buffer selection command inspired by SLIME's selector. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cider-client) |
||||
(require 'cider-interaction) |
||||
|
||||
(defconst cider-selector-help-buffer "*Selector Help*" |
||||
"The name of the selector's help buffer.") |
||||
|
||||
(defvar cider-selector-methods nil |
||||
"List of buffer-selection methods for the `cider-selector' command. |
||||
Each element is a list (KEY DESCRIPTION FUNCTION). |
||||
DESCRIPTION is a one-line description of what the key selects.") |
||||
|
||||
(defvar cider-selector-other-window nil |
||||
"If non-nil use `switch-to-buffer-other-window'.") |
||||
|
||||
(defun cider--recently-visited-buffer (mode) |
||||
"Return the most recently visited buffer whose `major-mode' is MODE. |
||||
Only considers buffers that are not already visible." |
||||
(cl-loop for buffer in (buffer-list) |
||||
when (and (with-current-buffer buffer (eq major-mode mode)) |
||||
(not (string-match-p "^ " (buffer-name buffer))) |
||||
(null (get-buffer-window buffer 'visible))) |
||||
return buffer |
||||
finally (error "Can't find unshown buffer in %S" mode))) |
||||
|
||||
;;;###autoload |
||||
(defun cider-selector (&optional other-window) |
||||
"Select a new buffer by type, indicated by a single character. |
||||
The user is prompted for a single character indicating the method by |
||||
which to choose a new buffer. The `?' character describes then |
||||
available methods. OTHER-WINDOW provides an optional target. |
||||
|
||||
See `def-cider-selector-method' for defining new methods." |
||||
(interactive) |
||||
(message "Select [%s]: " |
||||
(apply #'string (mapcar #'car cider-selector-methods))) |
||||
(let* ((cider-selector-other-window other-window) |
||||
(ch (save-window-excursion |
||||
(select-window (minibuffer-window)) |
||||
(read-char))) |
||||
(method (cl-find ch cider-selector-methods :key #'car))) |
||||
(cond (method |
||||
(funcall (cl-caddr method))) |
||||
(t |
||||
(message "No method for character: ?\\%c" ch) |
||||
(ding) |
||||
(sleep-for 1) |
||||
(discard-input) |
||||
(cider-selector))))) |
||||
|
||||
(defmacro def-cider-selector-method (key description &rest body) |
||||
"Define a new `cider-select' buffer selection method. |
||||
|
||||
KEY is the key the user will enter to choose this method. |
||||
|
||||
DESCRIPTION is a one-line sentence describing how the method |
||||
selects a buffer. |
||||
|
||||
BODY is a series of forms which are evaluated when the selector |
||||
is chosen. The returned buffer is selected with |
||||
`switch-to-buffer'." |
||||
(let ((method `(lambda () |
||||
(let ((buffer (progn ,@body))) |
||||
(cond ((not (get-buffer buffer)) |
||||
(message "No such buffer: %S" buffer) |
||||
(ding)) |
||||
((get-buffer-window buffer) |
||||
(select-window (get-buffer-window buffer))) |
||||
(cider-selector-other-window |
||||
(switch-to-buffer-other-window buffer)) |
||||
(t |
||||
(switch-to-buffer buffer))))))) |
||||
`(setq cider-selector-methods |
||||
(cl-sort (cons (list ,key ,description ,method) |
||||
(cl-remove ,key cider-selector-methods :key #'car)) |
||||
#'< :key #'car)))) |
||||
|
||||
(def-cider-selector-method ?? "Selector help buffer." |
||||
(ignore-errors (kill-buffer cider-selector-help-buffer)) |
||||
(with-current-buffer (get-buffer-create cider-selector-help-buffer) |
||||
(insert "CIDER Selector Methods:\n\n") |
||||
(cl-loop for (key line nil) in cider-selector-methods |
||||
do (insert (format "%c:\t%s\n" key line))) |
||||
(goto-char (point-min)) |
||||
(help-mode) |
||||
(display-buffer (current-buffer) t)) |
||||
(cider-selector) |
||||
(current-buffer)) |
||||
|
||||
(cl-pushnew (list ?4 "Select in other window" (lambda () (cider-selector t))) |
||||
cider-selector-methods :key #'car) |
||||
|
||||
(def-cider-selector-method ?c |
||||
"Most recently visited clojure-mode buffer." |
||||
(cider--recently-visited-buffer 'clojure-mode)) |
||||
|
||||
(def-cider-selector-method ?e |
||||
"Most recently visited emacs-lisp-mode buffer." |
||||
(cider--recently-visited-buffer 'emacs-lisp-mode)) |
||||
|
||||
(def-cider-selector-method ?q "Abort." |
||||
(top-level)) |
||||
|
||||
(def-cider-selector-method ?r |
||||
"Current REPL buffer." |
||||
(cider-current-repl-buffer)) |
||||
|
||||
(def-cider-selector-method ?n |
||||
"Connections browser buffer." |
||||
(cider-connection-browser) |
||||
cider--connection-browser-buffer-name) |
||||
|
||||
(def-cider-selector-method ?m |
||||
"*nrepl-messages* buffer." |
||||
nrepl-message-buffer-name) |
||||
|
||||
(def-cider-selector-method ?x |
||||
"*cider-error* buffer." |
||||
cider-error-buffer) |
||||
|
||||
(provide 'cider-selector) |
||||
|
||||
;;; cider-selector.el ends here |
@ -1,610 +0,0 @@
|
||||
;;; cider-stacktrace.el --- Stacktrace navigator -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright © 2014-2015 Jeff Valk |
||||
|
||||
;; Author: Jeff Valk <jv@jeffvalk.com> |
||||
|
||||
;; 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/>. |
||||
|
||||
;; This file is not part of GNU Emacs. |
||||
|
||||
;;; Commentary: |
||||
|
||||
;; Stacktrace filtering and stack frame source navigation |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cl-lib) |
||||
(require 'cider-popup) |
||||
(require 'button) |
||||
(require 'easymenu) |
||||
(require 'cider-common) |
||||
(require 'cider-compat) |
||||
(require 'cider-client) |
||||
(require 'cider-util) |
||||
|
||||
(require 'seq) |
||||
|
||||
;; Variables |
||||
|
||||
(defgroup cider-stacktrace nil |
||||
"Stacktrace filtering and navigation." |
||||
:prefix "cider-stacktrace-" |
||||
:group 'cider) |
||||
|
||||
(defcustom cider-stacktrace-fill-column t |
||||
"Fill column for error messages in stacktrace display. |
||||
If nil, messages will not be wrapped. If truthy but non-numeric, |
||||
`fill-column' will be used." |
||||
:type 'list |
||||
:group 'cider-stacktrace |
||||
:package-version '(cider . "0.7.0")) |
||||
|
||||
(defcustom cider-stacktrace-default-filters '(tooling dup) |
||||
"Frame types to omit from initial stacktrace display." |
||||
:type 'list |
||||
:group 'cider-stacktrace |
||||
:package-version '(cider . "0.6.0")) |
||||
|
||||
(defcustom cider-stacktrace-print-length 50 |
||||
"Set the maximum length of sequences in displayed cause data. |
||||
|
||||
This sets the value of Clojure's `*print-length*` when pretty printing the |
||||
`ex-data` map for exception causes in the stacktrace that are instances of |
||||
`IExceptionInfo`. |
||||
|
||||
Be advised that setting this to `nil` will cause the attempted printing of |
||||
infinite data structures." |
||||
:type '(choice integer (const nil)) |
||||
:group 'cider-stacktrace |
||||
:package-version '(cider . "0.9.0")) |
||||
|
||||
(defcustom cider-stacktrace-print-level 50 |
||||
"Set the maximum level of nesting in displayed cause data. |
||||
|
||||
This sets the value of Clojure's `*print-level*` when pretty printing the |
||||
`ex-data` map for exception causes in the stacktrace that are instances of |
||||
`IExceptionInfo`. |
||||
|
||||
Be advised that setting this to `nil` will cause the attempted printing of |
||||
cyclical data structures." |
||||
:type '(choice integer (const nil)) |
||||
:group 'cider-stacktrace |
||||
:package-version '(cider . "0.8.0")) |
||||
|
||||
(defvar cider-stacktrace-detail-max 2 |
||||
"The maximum detail level for causes.") |
||||
|
||||
(defvar-local cider-stacktrace-hidden-frame-count 0) |
||||
(defvar-local cider-stacktrace-filters nil) |
||||
(defvar-local cider-stacktrace-prior-filters nil) |
||||
(defvar-local cider-stacktrace-cause-visibility nil) |
||||
|
||||
(defconst cider-error-buffer "*cider-error*") |
||||
(add-to-list 'cider-ancillary-buffers cider-error-buffer) |
||||
|
||||
;; Faces |
||||
|
||||
(defface cider-stacktrace-error-class-face |
||||
'((t (:inherit font-lock-warning-face))) |
||||
"Face for exception class names" |
||||
:group 'cider-stacktrace |
||||
:package-version '(cider . "0.6.0")) |
||||
|
||||
(defface cider-stacktrace-error-message-face |
||||
'((t (:inherit font-lock-doc-face))) |
||||
"Face for exception messages" |
||||
:group 'cider-stacktrace |
||||
:package-version '(cider . "0.7.0")) |
||||
|
||||
(defface cider-stacktrace-filter-shown-face |
||||
'((t (:inherit button :underline t :weight normal))) |
||||
"Face for filter buttons representing frames currently visible" |
||||
:group 'cider-stacktrace |
||||
:package-version '(cider . "0.6.0")) |
||||
|
||||
(defface cider-stacktrace-filter-hidden-face |
||||
'((t (:inherit button :underline nil :weight normal))) |
||||
"Face for filter buttons representing frames currently filtered out" |
||||
:group 'cider-stacktrace |
||||
:package-version '(cider . "0.6.0")) |
||||
|
||||
(defface cider-stacktrace-face |
||||
'((t (:inherit default))) |
||||
"Face for stack frame text" |
||||
:group 'cider-stacktrace |
||||
:package-version '(cider . "0.6.0")) |
||||
|
||||
(defface cider-stacktrace-ns-face |
||||
'((t (:inherit font-lock-comment-face))) |
||||
"Face for stack frame namespace name" |
||||
:group 'cider-stacktrace |
||||
:package-version '(cider . "0.6.0")) |
||||
|
||||
(defface cider-stacktrace-fn-face |
||||
'((t (:inherit default :weight bold))) |
||||
"Face for stack frame function name" |
||||
:group 'cider-stacktrace |
||||
:package-version '(cider . "0.6.0")) |
||||
|
||||
|
||||
;; Colors & Theme Support |
||||
|
||||
(defvar cider-stacktrace-frames-background-color |
||||
(cider-scale-background-color) |
||||
"Background color for stacktrace frames.") |
||||
|
||||
(defadvice enable-theme (after cider-stacktrace-adapt-to-theme activate) |
||||
"When theme is changed, update `cider-stacktrace-frames-background-color'." |
||||
(setq cider-stacktrace-frames-background-color (cider-scale-background-color))) |
||||
|
||||
|
||||
;; Mode & key bindings |
||||
|
||||
(defvar cider-stacktrace-mode-map |
||||
(let ((map (make-sparse-keymap))) |
||||
(define-key map (kbd "M-p") #'cider-stacktrace-previous-cause) |
||||
(define-key map (kbd "M-n") #'cider-stacktrace-next-cause) |
||||
(define-key map (kbd "M-.") #'cider-stacktrace-jump) |
||||
(define-key map "q" #'cider-popup-buffer-quit-function) |
||||
(define-key map "j" #'cider-stacktrace-toggle-java) |
||||
(define-key map "c" #'cider-stacktrace-toggle-clj) |
||||
(define-key map "r" #'cider-stacktrace-toggle-repl) |
||||
(define-key map "t" #'cider-stacktrace-toggle-tooling) |
||||
(define-key map "d" #'cider-stacktrace-toggle-duplicates) |
||||
(define-key map "a" #'cider-stacktrace-toggle-all) |
||||
(define-key map "1" #'cider-stacktrace-cycle-cause-1) |
||||
(define-key map "2" #'cider-stacktrace-cycle-cause-2) |
||||
(define-key map "3" #'cider-stacktrace-cycle-cause-3) |
||||
(define-key map "4" #'cider-stacktrace-cycle-cause-4) |
||||
(define-key map "5" #'cider-stacktrace-cycle-cause-5) |
||||
(define-key map "0" #'cider-stacktrace-cycle-all-causes) |
||||
(define-key map [tab] #'cider-stacktrace-cycle-current-cause) |
||||
(define-key map [backtab] #'cider-stacktrace-cycle-all-causes) |
||||
(easy-menu-define cider-stacktrace-mode-menu map |
||||
"Menu for CIDER's stacktrace mode" |
||||
'("Stacktrace" |
||||
["Previous cause" cider-stacktrace-previous-cause] |
||||
["Next cause" cider-stacktrace-next-cause] |
||||
"--" |
||||
["Jump to frame source" cider-stacktrace-jump] |
||||
"--" |
||||
["Cycle current cause detail" cider-stacktrace-cycle-current-cause] |
||||
["Cycle cause #1 detail" cider-stacktrace-cycle-cause-1] |
||||
["Cycle cause #2 detail" cider-stacktrace-cycle-cause-2] |
||||
["Cycle cause #3 detail" cider-stacktrace-cycle-cause-3] |
||||
["Cycle cause #4 detail" cider-stacktrace-cycle-cause-4] |
||||
["Cycle cause #5 detail" cider-stacktrace-cycle-cause-5] |
||||
["Cycle all cause detail" cider-stacktrace-cycle-all-causes] |
||||
"--" |
||||
["Show/hide Java frames" cider-stacktrace-toggle-java] |
||||
["Show/hide Clojure frames" cider-stacktrace-toggle-clj] |
||||
["Show/hide REPL frames" cider-stacktrace-toggle-repl] |
||||
["Show/hide tooling frames" cider-stacktrace-toggle-tooling] |
||||
["Show/hide duplicate frames" cider-stacktrace-toggle-duplicates] |
||||
["Show/hide all frames" cider-stacktrace-toggle-all])) |
||||
map)) |
||||
|
||||
(define-derived-mode cider-stacktrace-mode special-mode "Stacktrace" |
||||
"Major mode for filtering and navigating CIDER stacktraces. |
||||
|
||||
\\{cider-stacktrace-mode-map}" |
||||
(setq buffer-read-only t) |
||||
(setq-local truncate-lines t) |
||||
(setq-local electric-indent-chars nil) |
||||
(setq-local cider-stacktrace-prior-filters nil) |
||||
(setq-local cider-stacktrace-hidden-frame-count 0) |
||||
(setq-local cider-stacktrace-filters cider-stacktrace-default-filters) |
||||
(setq-local cider-stacktrace-cause-visibility (make-vector 10 0))) |
||||
|
||||
|
||||
;; Stacktrace filtering |
||||
|
||||
(defun cider-stacktrace-indicate-filters (filters) |
||||
"Update enabled state of filter buttons. |
||||
|
||||
Find buttons with a 'filter property; if filter is a member of FILTERS, or |
||||
if filter is nil ('show all') and the argument list is non-nil, fontify the |
||||
button as disabled. Upon finding text with a 'hidden-count property, stop |
||||
searching and update the hidden count text." |
||||
(with-current-buffer cider-error-buffer |
||||
(save-excursion |
||||
(goto-char (point-min)) |
||||
(let ((inhibit-read-only t) |
||||
(get-face (lambda (hide) |
||||
(if hide |
||||
'cider-stacktrace-filter-hidden-face |
||||
'cider-stacktrace-filter-shown-face)))) |
||||
;; Toggle buttons |
||||
(while (not (or (get-text-property (point) 'hidden-count) (eobp))) |
||||
(let ((button (button-at (point)))) |
||||
(when button |
||||
(let* ((filter (button-get button 'filter)) |
||||
(face (funcall get-face (if filter |
||||
(member filter filters) |
||||
filters)))) |
||||
(button-put button 'face face))) |
||||
(goto-char (or (next-property-change (point)) |
||||
(point-max))))) |
||||
;; Update hidden count |
||||
(when (and (get-text-property (point) 'hidden-count) |
||||
(re-search-forward "[0-9]+" (line-end-position) t)) |
||||
(replace-match |
||||
(number-to-string cider-stacktrace-hidden-frame-count))))))) |
||||
|
||||
(defun cider-stacktrace-apply-filters (filters) |
||||
"Set visibility on stack frames using FILTERS. |
||||
Update `cider-stacktrace-hidden-frame-count' and indicate filters applied. |
||||
Currently collapsed stacktraces are ignored, and do not contribute to the |
||||
hidden count." |
||||
(with-current-buffer cider-error-buffer |
||||
(save-excursion |
||||
(goto-char (point-min)) |
||||
(let ((inhibit-read-only t) |
||||
(hidden 0)) |
||||
(while (not (eobp)) |
||||
(unless (get-text-property (point) 'collapsed) |
||||
(let* ((flags (get-text-property (point) 'flags)) |
||||
(hide (if (seq-intersection filters flags) t nil))) |
||||
(when hide (setq hidden (+ 1 hidden))) |
||||
(put-text-property (point) (line-beginning-position 2) 'invisible hide))) |
||||
(forward-line 1)) |
||||
(setq cider-stacktrace-hidden-frame-count hidden))) |
||||
(cider-stacktrace-indicate-filters filters))) |
||||
|
||||
|
||||
(defun cider-stacktrace-apply-cause-visibility () |
||||
"Apply `cider-stacktrace-cause-visibility' to causes and reapply filters." |
||||
(with-current-buffer cider-error-buffer |
||||
(save-excursion |
||||
(goto-char (point-min)) |
||||
(cl-flet ((next-detail (end) |
||||
(when-let ((pos (next-single-property-change (point) 'detail))) |
||||
(when (< pos end) |
||||
(goto-char pos))))) |
||||
(let ((inhibit-read-only t)) |
||||
;; For each cause... |
||||
(while (cider-stacktrace-next-cause) |
||||
(let* ((num (get-text-property (point) 'cause)) |
||||
(level (elt cider-stacktrace-cause-visibility num)) |
||||
(cause-end (cadr (cider-property-bounds 'cause)))) |
||||
;; For each detail level within the cause, set visibility. |
||||
(while (next-detail cause-end) |
||||
(let* ((detail (get-text-property (point) 'detail)) |
||||
(detail-end (cadr (cider-property-bounds 'detail))) |
||||
(hide (if (> detail level) t nil))) |
||||
(add-text-properties (point) detail-end |
||||
(list 'invisible hide |
||||
'collapsed hide)))))))) |
||||
(cider-stacktrace-apply-filters |
||||
cider-stacktrace-filters)))) |
||||
|
||||
|
||||
;; Interactive functions |
||||
|
||||
(defun cider-stacktrace-previous-cause () |
||||
"Move point to the previous exception cause, if one exists." |
||||
(interactive) |
||||
(with-current-buffer cider-error-buffer |
||||
(when-let ((pos (previous-single-property-change (point) 'cause))) |
||||
(goto-char pos)))) |
||||
|
||||
(defun cider-stacktrace-next-cause () |
||||
"Move point to the next exception cause, if one exists." |
||||
(interactive) |
||||
(with-current-buffer cider-error-buffer |
||||
(when-let ((pos (next-single-property-change (point) 'cause))) |
||||
(goto-char pos)))) |
||||
|
||||
(defun cider-stacktrace-cycle-cause (num &optional level) |
||||
"Update element NUM of `cider-stacktrace-cause-visibility', optionally to LEVEL. |
||||
If LEVEL is not specified, its current value is incremented. When it reaches 3, |
||||
it wraps to 0." |
||||
(let ((level (or level (1+ (elt cider-stacktrace-cause-visibility num))))) |
||||
(aset cider-stacktrace-cause-visibility num (mod level 3)) |
||||
(cider-stacktrace-apply-cause-visibility))) |
||||
|
||||
(defun cider-stacktrace-cycle-all-causes () |
||||
"Cycle the visibility of all exception causes." |
||||
(interactive) |
||||
(with-current-buffer cider-error-buffer |
||||
(save-excursion |
||||
;; Find nearest cause. |
||||
(unless (get-text-property (point) 'cause) |
||||
(cider-stacktrace-next-cause) |
||||
(unless (get-text-property (point) 'cause) |
||||
(cider-stacktrace-previous-cause))) |
||||
;; Cycle its level, and apply that to all causes. |
||||
(let* ((num (get-text-property (point) 'cause)) |
||||
(level (1+ (elt cider-stacktrace-cause-visibility num)))) |
||||
(setq-local cider-stacktrace-cause-visibility |
||||
(make-vector 10 (mod level 3))) |
||||
(cider-stacktrace-apply-cause-visibility))))) |
||||
|
||||
(defun cider-stacktrace-cycle-current-cause () |
||||
"Cycle the visibility of current exception at point, if any." |
||||
(interactive) |
||||
(with-current-buffer cider-error-buffer |
||||
(when-let ((num (get-text-property (point) 'cause))) |
||||
(cider-stacktrace-cycle-cause num)))) |
||||
|
||||
(defun cider-stacktrace-cycle-cause-1 () |
||||
"Cycle the visibility of exception cause #1." |
||||
(interactive) |
||||
(cider-stacktrace-cycle-cause 1)) |
||||
|
||||
(defun cider-stacktrace-cycle-cause-2 () |
||||
"Cycle the visibility of exception cause #2." |
||||
(interactive) |
||||
(cider-stacktrace-cycle-cause 2)) |
||||
|
||||
(defun cider-stacktrace-cycle-cause-3 () |
||||
"Cycle the visibility of exception cause #3." |
||||
(interactive) |
||||
(cider-stacktrace-cycle-cause 3)) |
||||
|
||||
(defun cider-stacktrace-cycle-cause-4 () |
||||
"Cycle the visibility of exception cause #4." |
||||
(interactive) |
||||
(cider-stacktrace-cycle-cause 4)) |
||||
|
||||
(defun cider-stacktrace-cycle-cause-5 () |
||||
"Cycle the visibility of exception cause #5." |
||||
(interactive) |
||||
(cider-stacktrace-cycle-cause 5)) |
||||
|
||||
|
||||
(defun cider-stacktrace-toggle-all () |
||||
"Reset `cider-stacktrace-filters' if present; otherwise restore prior filters." |
||||
(interactive) |
||||
(when cider-stacktrace-filters |
||||
(setq-local cider-stacktrace-prior-filters |
||||
cider-stacktrace-filters)) |
||||
(cider-stacktrace-apply-filters |
||||
(setq cider-stacktrace-filters |
||||
(unless cider-stacktrace-filters ; when current filters are nil, |
||||
cider-stacktrace-prior-filters)))) ; reenable prior filter set |
||||
|
||||
(defun cider-stacktrace-toggle (flag) |
||||
"Update `cider-stacktrace-filters' to add or remove FLAG, and apply filters." |
||||
(cider-stacktrace-apply-filters |
||||
(setq cider-stacktrace-filters |
||||
(if (memq flag cider-stacktrace-filters) |
||||
(remq flag cider-stacktrace-filters) |
||||
(cons flag cider-stacktrace-filters))))) |
||||
|
||||
(defun cider-stacktrace-toggle-java () |
||||
"Toggle display of Java stack frames." |
||||
(interactive) |
||||
(cider-stacktrace-toggle 'java)) |
||||
|
||||
(defun cider-stacktrace-toggle-clj () |
||||
"Toggle display of Clojure stack frames." |
||||
(interactive) |
||||
(cider-stacktrace-toggle 'clj)) |
||||
|
||||
(defun cider-stacktrace-toggle-repl () |
||||
"Toggle display of REPL stack frames." |
||||
(interactive) |
||||
(cider-stacktrace-toggle 'repl)) |
||||
|
||||
(defun cider-stacktrace-toggle-tooling () |
||||
"Toggle display of Tooling stack frames (compiler, nREPL middleware, etc)." |
||||
(interactive) |
||||
(cider-stacktrace-toggle 'tooling)) |
||||
|
||||
(defun cider-stacktrace-toggle-duplicates () |
||||
"Toggle display of stack frames that are duplicates of their descendents." |
||||
(interactive) |
||||
(cider-stacktrace-toggle 'dup)) |
||||
|
||||
|
||||
;; Text button functions |
||||
|
||||
(defun cider-stacktrace-filter (button) |
||||
"Apply filter(s) indicated by the BUTTON." |
||||
(with-temp-message "Filters may also be toggled with the keyboard." |
||||
(let ((flag (button-get button 'filter))) |
||||
(if flag |
||||
(cider-stacktrace-toggle flag) |
||||
(cider-stacktrace-toggle-all))) |
||||
(sit-for 5))) |
||||
|
||||
(defun cider-stacktrace-navigate (button) |
||||
"Navigate to the stack frame source represented by the BUTTON." |
||||
(let* ((var (button-get button 'var)) |
||||
(class (button-get button 'class)) |
||||
(method (button-get button 'method)) |
||||
(info (or (and var (cider-var-info var)) |
||||
(and class method (cider-member-info class method)) |
||||
(nrepl-dict))) |
||||
;; Stacktrace returns more accurate line numbers, but if the function's |
||||
;; line was unreliable, then so is the stacktrace by the same amount. |
||||
;; Set `line-shift' to the number of lines from the beginning of defn. |
||||
(line-shift (- (or (button-get button 'line) 0) |
||||
(or (nrepl-dict-get info "line") 1))) |
||||
;; give priority to `info` files as `info` returns full paths. |
||||
(info (nrepl-dict-put info "file" (or (nrepl-dict-get info "file") |
||||
(button-get button 'file))))) |
||||
(cider--jump-to-loc-from-info info t) |
||||
(forward-line line-shift) |
||||
(back-to-indentation))) |
||||
|
||||
(defun cider-stacktrace-jump (&optional arg) |
||||
"Like `cider-find-var', but uses the stack frame source at point, if available." |
||||
(interactive "P") |
||||
(let ((button (button-at (point)))) |
||||
(if (and button (button-get button 'line)) |
||||
(cider-stacktrace-navigate button) |
||||
(cider-find-var arg)))) |
||||
|
||||
|
||||
;; Rendering |
||||
|
||||
(defun cider-stacktrace-emit-indented (text indent &optional fill) |
||||
"Insert TEXT, and INDENT and optionally FILL the entire block." |
||||
(let ((beg (point))) |
||||
(insert text) |
||||
(goto-char beg) |
||||
(while (not (eobp)) |
||||
(insert indent) |
||||
(forward-line)) |
||||
(when (and fill cider-stacktrace-fill-column) |
||||
(when (and (numberp cider-stacktrace-fill-column)) |
||||
(setq-local fill-column cider-stacktrace-fill-column)) |
||||
(setq-local fill-prefix indent) |
||||
(fill-region beg (point))))) |
||||
|
||||
(defun cider-stacktrace-render-filters (buffer filters) |
||||
"Emit into BUFFER toggle buttons for each of the FILTERS." |
||||
(with-current-buffer buffer |
||||
(insert " Show: ") |
||||
(dolist (filter filters) |
||||
(insert-text-button (car filter) |
||||
'filter (cadr filter) |
||||
'follow-link t |
||||
'action 'cider-stacktrace-filter |
||||
'help-echo (format "Toggle %s stack frames" |
||||
(car filter))) |
||||
(insert " ")) |
||||
(let ((hidden "(0 frames hidden)")) |
||||
(put-text-property 0 (length hidden) 'hidden-count t hidden) |
||||
(insert " " hidden "\n")))) |
||||
|
||||
(defun cider-stacktrace-render-frame (buffer frame) |
||||
"Emit into BUFFER function call site info for the stack FRAME. |
||||
This associates text properties to enable filtering and source navigation." |
||||
(with-current-buffer buffer |
||||
(nrepl-dbind-response frame (file line flags class method name var ns fn) |
||||
(let ((flags (mapcar 'intern flags))) ; strings -> symbols |
||||
(insert-text-button (format "%30s:%5d %s/%s" |
||||
(if (member 'repl flags) "REPL" file) line |
||||
(if (member 'clj flags) ns class) |
||||
(if (member 'clj flags) fn method)) |
||||
'var var 'class class 'method method |
||||
'name name 'file file 'line line |
||||
'flags flags 'follow-link t |
||||
'action 'cider-stacktrace-navigate |
||||
'help-echo "View source at this location" |
||||
'face 'cider-stacktrace-face) |
||||
(save-excursion |
||||
(let ((p4 (point)) |
||||
(p1 (search-backward " ")) |
||||
(p2 (search-forward "/")) |
||||
(p3 (search-forward-regexp "[^/$]+"))) |
||||
(put-text-property p1 p4 'font-lock-face 'cider-stacktrace-ns-face) |
||||
(put-text-property p2 p3 'font-lock-face 'cider-stacktrace-fn-face))) |
||||
(insert "\n"))))) |
||||
|
||||
(defun cider-stacktrace--create-go-to-err-button (beg message) |
||||
"Create a button that jumps to the relevant error. |
||||
|
||||
Buttons span over the region from BEG to current point. |
||||
MESSAGE is parsed to find line, col and buffer name to jump to." |
||||
(when (and message |
||||
(string-match "\\([^:]+\\):\\([^:]+\\):\\([^:]+\\):\\([^:]+\\)\\'" message)) |
||||
(let* ((line (string-to-number (match-string 3 message))) |
||||
(col (string-to-number (match-string 4 message))) |
||||
(buf-name (car (last (split-string (match-string 2 message) "\\/"))))) |
||||
(when buf-name |
||||
(make-button (+ beg 3) |
||||
(point) |
||||
'action (lambda (_button) |
||||
(let ((the-buf-window (get-buffer-window buf-name))) |
||||
(if the-buf-window |
||||
(select-window the-buf-window) |
||||
(switch-to-buffer buf-name))) |
||||
(goto-char (point-min)) |
||||
(forward-line line) |
||||
(move-to-column col t))))))) |
||||
|
||||
(defun cider-stacktrace-render-cause (buffer cause num note) |
||||
"Emit into BUFFER the CAUSE NUM, exception class, message, data, and NOTE." |
||||
(with-current-buffer buffer |
||||
(nrepl-dbind-response cause (class message data stacktrace) |
||||
(let ((indent " ") |
||||
(class-face 'cider-stacktrace-error-class-face) |
||||
(message-face 'cider-stacktrace-error-message-face)) |
||||
(cider-propertize-region `(cause ,num) |
||||
;; Detail level 0: exception class |
||||
(cider-propertize-region '(detail 0) |
||||
(insert (format "%d. " num) |
||||
(propertize note 'font-lock-face 'font-lock-comment-face) " " |
||||
(propertize class 'font-lock-face class-face) |
||||
"\n")) |
||||
;; Detail level 1: message + ex-data |
||||
(cider-propertize-region '(detail 1) |
||||
(let ((beg (point))) |
||||
(cider-stacktrace-emit-indented |
||||
(propertize (or message "(No message)") 'font-lock-face message-face) indent t) |
||||
(cider-stacktrace--create-go-to-err-button beg message)) |
||||
(insert "\n") |
||||
(when data |
||||
(cider-stacktrace-emit-indented |
||||
(cider-font-lock-as-clojure data) indent nil))) |
||||
;; Detail level 2: stacktrace |
||||
(cider-propertize-region '(detail 2) |
||||
(insert "\n") |
||||
(let ((beg (point)) |
||||
(bg `(:background ,cider-stacktrace-frames-background-color))) |
||||
(dolist (frame stacktrace) |
||||
(cider-stacktrace-render-frame buffer frame)) |
||||
(overlay-put (make-overlay beg (point)) 'font-lock-face bg))) |
||||
;; Add line break between causes, even when collapsed. |
||||
(cider-propertize-region '(detail 0) |
||||
(insert "\n"))))))) |
||||
|
||||
(defun cider-stacktrace-initialize (causes) |
||||
"Set and apply CAUSES initial visibility, filters, and cursor position." |
||||
;; Partially display outermost cause if it's a compiler exception (the |
||||
;; description reports reader location of the error). |
||||
(nrepl-dbind-response (car causes) (class) |
||||
(when (equal class "clojure.lang.Compiler$CompilerException") |
||||
(cider-stacktrace-cycle-cause (length causes) 1))) |
||||
;; Fully display innermost cause. This also applies visibility/filters. |
||||
(cider-stacktrace-cycle-cause 1 cider-stacktrace-detail-max) |
||||
;; Move point to first stacktrace frame in displayed cause. If the error |
||||
;; buffer is visible in a window, ensure that window is selected while moving |
||||
;; point, so as to move both the buffer's and the window's point. |
||||
(with-selected-window (or (get-buffer-window cider-error-buffer) |
||||
(selected-window)) |
||||
(with-current-buffer cider-error-buffer |
||||
(goto-char (point-min)) |
||||
(while (cider-stacktrace-next-cause)) |
||||
(goto-char (next-single-property-change (point) 'flags))))) |
||||
|
||||
(defun cider-stacktrace-render (buffer causes) |
||||
"Emit into BUFFER useful stacktrace information for the CAUSES." |
||||
(with-current-buffer buffer |
||||
(let ((inhibit-read-only t)) |
||||
(erase-buffer) |
||||
(insert "\n") |
||||
;; Stacktrace filters |
||||
(cider-stacktrace-render-filters |
||||
buffer |
||||
`(("Clojure" clj) ("Java" java) ("REPL" repl) |
||||
("Tooling" tooling) ("Duplicates" dup) ("All" ,nil))) |
||||
(insert "\n") |
||||
;; Stacktrace exceptions & frames |
||||
(let ((num (length causes))) |
||||
(dolist (cause causes) |
||||
(let ((note (if (= num (length causes)) "Unhandled" "Caused by"))) |
||||
(cider-stacktrace-render-cause buffer cause num note) |
||||
(setq num (1- num)))))) |
||||
(cider-stacktrace-initialize causes) |
||||
(font-lock-refresh-defaults))) |
||||
|
||||
(provide 'cider-stacktrace) |
||||
|
||||
;;; cider-stacktrace.el ends here |
@ -1,499 +0,0 @@
|
||||
;;; cider-test.el --- Test result viewer -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright © 2014-2015 Jeff Valk |
||||
|
||||
;; Author: Jeff Valk <jv@jeffvalk.com> |
||||
|
||||
;; 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/>. |
||||
|
||||
;; This file is not part of GNU Emacs. |
||||
|
||||
;;; Commentary: |
||||
|
||||
;; This provides execution, reporting, and navigation support for Clojure tests, |
||||
;; specifically using the `clojure.test' machinery. This functionality replaces |
||||
;; the venerable `clojure-test-mode' (deprecated in June 2014), and relies on |
||||
;; nREPL middleware for report running and session support. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cider-common) |
||||
(require 'cider-client) |
||||
(require 'cider-popup) |
||||
(require 'cider-stacktrace) |
||||
(require 'cider-compat) |
||||
|
||||
(require 'button) |
||||
(require 'easymenu) |
||||
|
||||
;;; Variables |
||||
|
||||
(defgroup cider-test nil |
||||
"Presentation and navigation for test results." |
||||
:prefix "cider-test-" |
||||
:group 'cider) |
||||
|
||||
(defcustom cider-test-show-report-on-success nil |
||||
"Whether to show the `*cider-test-report*` buffer on passing tests." |
||||
:type 'boolean |
||||
:group 'cider-test |
||||
:package-version '(cider . "0.8.0")) |
||||
|
||||
(defcustom cider-auto-select-test-report-buffer t |
||||
"Determines if the test-report buffer should be auto-selected." |
||||
:type 'boolean |
||||
:group 'cider-test |
||||
:package-version '(cider . "0.9.0")) |
||||
|
||||
(defvar cider-test-last-test-ns nil |
||||
"The namespace for which tests were last run.") |
||||
|
||||
(defvar cider-test-last-results nil |
||||
"The results of the last run test.") |
||||
|
||||
(defconst cider-test-report-buffer "*cider-test-report*" |
||||
"Buffer name in which to display test reports.") |
||||
(add-to-list 'cider-ancillary-buffers cider-test-report-buffer) |
||||
|
||||
|
||||
;;; Faces |
||||
;; These are as defined in clojure-test-mode. |
||||
|
||||
(defface cider-test-failure-face |
||||
'((((class color) (background light)) |
||||
:background "orange red") |
||||
(((class color) (background dark)) |
||||
:background "firebrick")) |
||||
"Face for failed tests." |
||||
:group 'cider-test |
||||
:package-version '(cider . "0.7.0")) |
||||
|
||||
(defface cider-test-error-face |
||||
'((((class color) (background light)) |
||||
:background "orange1") |
||||
(((class color) (background dark)) |
||||
:background "orange4")) |
||||
"Face for erring tests." |
||||
:group 'cider-test |
||||
:package-version '(cider . "0.7.0")) |
||||
|
||||
(defface cider-test-success-face |
||||
'((((class color) (background light)) |
||||
:foreground "black" |
||||
:background "green") |
||||
(((class color) (background dark)) |
||||
:foreground "black" |
||||
:background "green")) |
||||
"Face for passing tests." |
||||
:group 'cider-test |
||||
:package-version '(cider . "0.7.0")) |
||||
|
||||
|
||||
;;; Report mode & key bindings |
||||
;; The primary mode of interacting with test results is the report buffer, which |
||||
;; allows navigation among tests, jumping to test definitions, expected/actual |
||||
;; diff-ing, and cause/stacktrace inspection for test errors. |
||||
|
||||
(defvar cider-test-report-mode-map |
||||
(let ((map (make-sparse-keymap))) |
||||
(define-key map (kbd "C-c ,") #'cider-test-run-tests) |
||||
(define-key map (kbd "C-c C-,") #'cider-test-rerun-tests) |
||||
(define-key map (kbd "C-c M-,") #'cider-test-run-test) |
||||
(define-key map (kbd "M-p") #'cider-test-previous-result) |
||||
(define-key map (kbd "M-n") #'cider-test-next-result) |
||||
(define-key map (kbd "M-.") #'cider-test-jump) |
||||
(define-key map (kbd "<backtab>") #'cider-test-previous-result) |
||||
(define-key map (kbd "TAB") #'cider-test-next-result) |
||||
(define-key map (kbd "RET") #'cider-test-jump) |
||||
(define-key map (kbd "t") #'cider-test-jump) |
||||
(define-key map (kbd "d") #'cider-test-ediff) |
||||
(define-key map (kbd "e") #'cider-test-stacktrace) |
||||
(define-key map "q" #'cider-popup-buffer-quit-function) |
||||
(easy-menu-define cider-test-report-mode-menu map |
||||
"Menu for CIDER's test result mode" |
||||
'("Test-Report" |
||||
["Previous result" cider-test-previous-result] |
||||
["Next result" cider-test-next-result] |
||||
"--" |
||||
["Rerun current test" cider-test-run-test] |
||||
["Rerun failed/erring tests" cider-test-rerun-tests] |
||||
["Rerun all tests" cider-test-run-tests] |
||||
"--" |
||||
["Jump to test definition" cider-test-jump] |
||||
["Display test error" cider-test-stacktrace] |
||||
["Display expected/actual diff" cider-test-ediff])) |
||||
map)) |
||||
|
||||
(define-derived-mode cider-test-report-mode fundamental-mode "Test Report" |
||||
"Major mode for presenting Clojure test results. |
||||
|
||||
\\{cider-test-report-mode-map}" |
||||
(setq buffer-read-only t) |
||||
(setq-local truncate-lines t) |
||||
(setq-local electric-indent-chars nil)) |
||||
|
||||
;; Report navigation |
||||
|
||||
(defun cider-test-show-report () |
||||
"Show the test report buffer, if one exists." |
||||
(interactive) |
||||
(if-let ((report-buffer (get-buffer cider-test-report-buffer))) |
||||
(switch-to-buffer report-buffer) |
||||
(message "No test report buffer"))) |
||||
|
||||
(defun cider-test-previous-result () |
||||
"Move point to the previous test result, if one exists." |
||||
(interactive) |
||||
(with-current-buffer (get-buffer cider-test-report-buffer) |
||||
(when-let ((pos (previous-single-property-change (point) 'type))) |
||||
(goto-char pos)))) |
||||
|
||||
(defun cider-test-next-result () |
||||
"Move point to the next test result, if one exists." |
||||
(interactive) |
||||
(with-current-buffer (get-buffer cider-test-report-buffer) |
||||
(when-let ((pos (next-single-property-change (point) 'type))) |
||||
(goto-char pos)))) |
||||
|
||||
(defun cider-test-jump (&optional arg) |
||||
"Like `cider-find-var', but uses the test at point's definition, if available." |
||||
(interactive "P") |
||||
(let ((ns (get-text-property (point) 'ns)) |
||||
(var (get-text-property (point) 'var)) |
||||
(line (get-text-property (point) 'line))) |
||||
(if (and ns var) |
||||
(cider-find-var arg (concat ns "/" var) line) |
||||
(cider-find-var arg)))) |
||||
|
||||
;;; Error stacktraces |
||||
|
||||
(defvar cider-auto-select-error-buffer) |
||||
|
||||
(defun cider-test-stacktrace-for (ns var index) |
||||
"Display stacktrace for the erring NS VAR test with the assertion INDEX." |
||||
(let (causes) |
||||
(cider-nrepl-send-request |
||||
(append |
||||
(list "op" "test-stacktrace" "session" (cider-current-session) |
||||
"ns" ns "var" var "index" index) |
||||
(when cider-stacktrace-print-length |
||||
(list "print-length" cider-stacktrace-print-length)) |
||||
(when cider-stacktrace-print-level |
||||
(list "print-level" cider-stacktrace-print-level))) |
||||
(lambda (response) |
||||
(nrepl-dbind-response response (class status) |
||||
(cond (class (setq causes (cons response causes))) |
||||
(status (when causes |
||||
(cider-stacktrace-render |
||||
(cider-popup-buffer cider-error-buffer |
||||
cider-auto-select-error-buffer) |
||||
(reverse causes)))))))))) |
||||
|
||||
(defun cider-test-stacktrace () |
||||
"Display stacktrace for the erring test at point." |
||||
(interactive) |
||||
(let ((ns (get-text-property (point) 'ns)) |
||||
(var (get-text-property (point) 'var)) |
||||
(index (get-text-property (point) 'index)) |
||||
(err (get-text-property (point) 'error))) |
||||
(if (and err ns var index) |
||||
(cider-test-stacktrace-for ns var index) |
||||
(message "No test error at point")))) |
||||
|
||||
|
||||
;;; Expected vs actual diffing |
||||
|
||||
(defvar cider-test-ediff-buffers nil |
||||
"The expected/actual buffers used to display diff.") |
||||
|
||||
(defun cider-test-ediff () |
||||
"Show diff of the expected vs actual value for the test at point. |
||||
With the actual value, the outermost '(not ...)' s-expression is removed." |
||||
(interactive) |
||||
(let ((expected (get-text-property (point) 'expected)) |
||||
(actual (get-text-property (point) 'actual))) |
||||
(if (and expected actual) |
||||
(let ((expected-buffer (generate-new-buffer " *expected*")) |
||||
(actual-buffer (generate-new-buffer " *actual*"))) |
||||
(with-current-buffer expected-buffer |
||||
(insert expected) |
||||
(clojure-mode)) |
||||
(with-current-buffer actual-buffer |
||||
(insert actual) |
||||
(goto-char (point-min)) |
||||
(forward-char) |
||||
(forward-sexp) |
||||
(forward-whitespace 1) |
||||
(let ((beg (point))) |
||||
(forward-sexp) |
||||
(let ((actual* (buffer-substring beg (point)))) |
||||
(erase-buffer) |
||||
(insert actual*))) |
||||
(clojure-mode)) |
||||
(apply 'ediff-buffers |
||||
(setq cider-test-ediff-buffers |
||||
(list (buffer-name expected-buffer) |
||||
(buffer-name actual-buffer))))) |
||||
(message "No test failure at point")))) |
||||
|
||||
(defun cider-test-ediff-cleanup () |
||||
"Cleanup expected/actual buffers used for diff." |
||||
(interactive) |
||||
(mapc (lambda (b) (when (get-buffer b) (kill-buffer b))) |
||||
cider-test-ediff-buffers)) |
||||
|
||||
(add-hook 'ediff-cleanup-hook #'cider-test-ediff-cleanup) |
||||
|
||||
|
||||
;;; Report rendering |
||||
|
||||
(defun cider-test-type-face (type) |
||||
"Return the font lock face for the test result TYPE." |
||||
(pcase type |
||||
("pass" 'cider-test-success-face) |
||||
("fail" 'cider-test-failure-face) |
||||
("error" 'cider-test-error-face) |
||||
(_ 'default))) |
||||
|
||||
(defun cider-test-render-summary (buffer summary) |
||||
"Emit into BUFFER the report SUMMARY statistics." |
||||
(with-current-buffer buffer |
||||
(nrepl-dbind-response summary (var test pass fail error) |
||||
(insert (format "Ran %d tests, in %d test functions\n" test var)) |
||||
(unless (zerop fail) |
||||
(cider-insert (format "%d failures" fail) 'cider-test-failure-face t)) |
||||
(unless (zerop error) |
||||
(cider-insert (format "%d errors" error) 'cider-test-error-face t)) |
||||
(when (zerop (+ fail error)) |
||||
(cider-insert (format "%d passed" pass) 'cider-test-success-face t)) |
||||
(insert "\n\n")))) |
||||
|
||||
(defun cider-test-render-assertion (buffer test) |
||||
"Emit into BUFFER report detail for the TEST assertion." |
||||
(with-current-buffer buffer |
||||
(nrepl-dbind-response test (var context type message expected actual error) |
||||
(cider-propertize-region (cider-intern-keys (cdr test)) |
||||
(cider-insert (capitalize type) (cider-test-type-face type) nil " in ") |
||||
(cider-insert var 'font-lock-function-name-face t) |
||||
(when context (cider-insert context 'font-lock-doc-face t)) |
||||
(when message (cider-insert message 'font-lock-doc-string-face t)) |
||||
(when expected (cider-insert "expected: " 'font-lock-comment-face nil |
||||
(cider-font-lock-as-clojure expected))) |
||||
(when actual (cider-insert " actual: " 'font-lock-comment-face) |
||||
(if error |
||||
(progn (insert-text-button |
||||
error |
||||
'follow-link t |
||||
'action '(lambda (_button) (cider-test-stacktrace)) |
||||
'help-echo "View causes and stacktrace") |
||||
(insert "\n")) |
||||
(insert (cider-font-lock-as-clojure actual))))) |
||||
(insert "\n")))) |
||||
|
||||
(defun cider-test-render-report (buffer ns summary results) |
||||
"Emit into BUFFER the report for the NS, SUMMARY, and test RESULTS." |
||||
(with-current-buffer buffer |
||||
(let ((inhibit-read-only t)) |
||||
(cider-test-report-mode) |
||||
(cider-insert "Test Summary" 'bold t) |
||||
(cider-insert ns 'font-lock-function-name-face t "\n") |
||||
(cider-test-render-summary buffer summary) |
||||
(nrepl-dbind-response summary (fail error) |
||||
(unless (zerop (+ fail error)) |
||||
(cider-insert "Results" 'bold t "\n") |
||||
(nrepl-dict-map |
||||
(lambda (_var tests) |
||||
(dolist (test tests) |
||||
(nrepl-dbind-response test (type) |
||||
(unless (equal "pass" type) |
||||
(cider-test-render-assertion buffer test))))) |
||||
results))) |
||||
(goto-char (point-min)) |
||||
(current-buffer)))) |
||||
|
||||
|
||||
;;; Summary echo |
||||
|
||||
(defun cider-test-echo-summary (summary) |
||||
"Echo SUMMARY statistics for a test run." |
||||
(nrepl-dbind-response summary (test fail error) |
||||
(message |
||||
(propertize |
||||
(format "Ran %s tests. %s failures, %s errors." test fail error) |
||||
'face (cond ((not (zerop error)) 'cider-test-error-face) |
||||
((not (zerop fail)) 'cider-test-failure-face) |
||||
(t 'cider-test-success-face)))))) |
||||
|
||||
|
||||
;;; Test definition highlighting |
||||
;; On receipt of test results, failing/erring test definitions are highlighted. |
||||
;; Highlights are cleared on the next report run, and may be cleared manually |
||||
;; by the user. |
||||
|
||||
;; NOTE If keybindings specific to test sources are desired, it would be |
||||
;; straightforward to turn this into a `cider-test-mode' minor mode, which we |
||||
;; enable on test sources, much like the legacy `clojure-test-mode'. At present, |
||||
;; though, there doesn't seem to be much value in this, since the report buffer |
||||
;; provides the primary means of interacting with test results. |
||||
|
||||
(defun cider-test-highlight-problem (buffer test) |
||||
"Highlight the BUFFER test definition for the non-passing TEST." |
||||
(with-current-buffer buffer |
||||
(nrepl-dbind-response test (type file line message expected actual) |
||||
;; we have to watch out for vars without proper location metadata |
||||
;; right now everything evaluated interactively lacks this data |
||||
;; TODO: Figure out what to do when the metadata is missing |
||||
(when (and file line (not (cider--tooling-file-p file))) |
||||
(save-excursion |
||||
(goto-char (point-min)) |
||||
(forward-line (1- line)) |
||||
(forward-whitespace 1) |
||||
(forward-char) |
||||
(let ((beg (point))) |
||||
(forward-sexp) |
||||
(let ((overlay (make-overlay beg (point)))) |
||||
(overlay-put overlay 'font-lock-face (cider-test-type-face type)) |
||||
(overlay-put overlay 'type type) |
||||
(overlay-put overlay 'help-echo message) |
||||
(overlay-put overlay 'message message) |
||||
(overlay-put overlay 'expected expected) |
||||
(overlay-put overlay 'actual actual)))))))) |
||||
|
||||
(defun cider-find-var-file (var) |
||||
"Return the buffer visiting the file in which VAR is defined, or nil if |
||||
not found." |
||||
(cider-ensure-op-supported "info") |
||||
(when-let ((info (cider-var-info var)) |
||||
(file (nrepl-dict-get info "file"))) |
||||
(cider-find-file file))) |
||||
|
||||
(defun cider-test-highlight-problems (ns results) |
||||
"Highlight all non-passing tests in the NS test RESULTS." |
||||
(nrepl-dict-map |
||||
(lambda (var tests) |
||||
(when-let ((buffer (cider-find-var-file (concat ns "/" var)))) |
||||
(dolist (test tests) |
||||
(nrepl-dbind-response test (type) |
||||
(unless (equal "pass" type) |
||||
(cider-test-highlight-problem buffer test)))))) |
||||
results)) |
||||
|
||||
(defun cider-test-clear-highlights () |
||||
"Clear highlighting of non-passing tests from the last test run." |
||||
(interactive) |
||||
(when-let ((ns cider-test-last-test-ns)) |
||||
(dolist (var (nrepl-dict-keys cider-test-last-results)) |
||||
(when-let ((buffer (cider-find-var-file (concat ns "/" var)))) |
||||
(with-current-buffer buffer |
||||
(remove-overlays)))))) |
||||
|
||||
|
||||
;;; Test namespaces |
||||
;; Test namespace inference exists to enable DWIM test running functions: the |
||||
;; same "run-tests" function should be able to be used in a source file, and in |
||||
;; its corresponding test namespace. To provide this, we need to map the |
||||
;; relationship between those namespaces. |
||||
|
||||
(defcustom cider-test-infer-test-ns 'cider-test-default-test-ns-fn |
||||
"Function to infer the test namespace for NS. |
||||
The default implementation uses the simple Leiningen convention of appending |
||||
'-test' to the namespace name." |
||||
:type 'symbol |
||||
:group 'cider-test |
||||
:package-version '(cider . "0.7.0")) |
||||
|
||||
(defun cider-test-default-test-ns-fn (ns) |
||||
"For a NS, return the test namespace, which may be the argument itself. |
||||
This uses the Leiningen convention of appending '-test' to the namespace name." |
||||
(when ns |
||||
(let ((suffix "-test")) |
||||
;; string-suffix-p is only available in Emacs 24.4+ |
||||
(if (string-match-p (rx-to-string `(: ,suffix eos) t) ns) |
||||
ns |
||||
(concat ns suffix))))) |
||||
|
||||
|
||||
;;; Test execution |
||||
|
||||
(declare-function cider-emit-interactive-eval-output "cider-interaction") |
||||
(declare-function cider-emit-interactive-eval-err-output "cider-interaction") |
||||
|
||||
(defun cider-test-execute (ns &optional retest tests) |
||||
"Run tests for NS; optionally RETEST failures or run only specified TESTS. |
||||
Upon test completion, results are echoed and a test report is optionally |
||||
displayed. When test failures/errors occur, their sources are highlighted." |
||||
(cider-test-clear-highlights) |
||||
(message "Testing...") |
||||
(cider-nrepl-send-request |
||||
(list "ns" ns "op" (if retest "retest" "test") |
||||
"tests" tests "session" (cider-current-session)) |
||||
(lambda (response) |
||||
(nrepl-dbind-response response (summary results status out err) |
||||
(cond ((member "namespace-not-found" status) |
||||
(message "No tests namespace: %s" ns)) |
||||
(out (cider-emit-interactive-eval-output out)) |
||||
(err (cider-emit-interactive-eval-err-output err)) |
||||
(results |
||||
(nrepl-dbind-response summary (error fail) |
||||
(setq cider-test-last-test-ns ns) |
||||
(setq cider-test-last-results results) |
||||
(cider-test-highlight-problems ns results) |
||||
(cider-test-echo-summary summary) |
||||
(when (or (not (zerop (+ error fail))) |
||||
cider-test-show-report-on-success) |
||||
(cider-test-render-report |
||||
(cider-popup-buffer cider-test-report-buffer |
||||
cider-auto-select-test-report-buffer) |
||||
ns summary results))))))))) |
||||
|
||||
(defun cider-test-rerun-tests () |
||||
"Rerun failed and erring tests from the last tested namespace." |
||||
(interactive) |
||||
(if-let ((ns cider-test-last-test-ns)) |
||||
(cider-test-execute ns t) |
||||
(message "No namespace to retest"))) |
||||
|
||||
(defun cider-test-run-tests (suppress-inference) |
||||
"Run all tests for the current Clojure source or test report context. |
||||
|
||||
With a prefix arg SUPPRESS-INFERENCE it will try to run the tests in the |
||||
current ns." |
||||
(interactive "P") |
||||
(if-let ((ns (if suppress-inference |
||||
(clojure-find-ns) |
||||
(or (funcall cider-test-infer-test-ns (clojure-find-ns)) |
||||
(when (eq major-mode 'cider-test-report-mode) |
||||
cider-test-last-test-ns))))) |
||||
(cider-test-execute ns nil) |
||||
(message "No namespace to test in current context"))) |
||||
|
||||
(defun cider-test-run-test () |
||||
"Run the test at point. |
||||
The test ns/var exist as text properties on report items and on highlighted |
||||
failed/erred test definitions. When not found, a test definition at point |
||||
is searched." |
||||
(interactive) |
||||
(let ((ns (get-text-property (point) 'ns)) |
||||
(var (get-text-property (point) 'var))) |
||||
(if (and ns var) |
||||
(cider-test-execute ns nil (list var)) |
||||
(let ((ns (clojure-find-ns)) |
||||
(def (clojure-find-def))) |
||||
(if (and ns (member (car def) '("deftest" "defspec"))) |
||||
(cider-test-execute ns nil (cdr def)) |
||||
(message "No test at point")))))) |
||||
|
||||
(provide 'cider-test) |
||||
|
||||
;;; cider-test.el ends here |
@ -1,383 +0,0 @@
|
||||
;;; cider-util.el --- Common utility functions that don't belong anywhere else -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright © 2012-2015 Tim King, Phil Hagelberg |
||||
;; Copyright © 2013-2015 Bozhidar Batsov, Hugo Duncan, Steve Purcell |
||||
;; |
||||
;; Author: Tim King <kingtim@gmail.com> |
||||
;; Phil Hagelberg <technomancy@gmail.com> |
||||
;; Bozhidar Batsov <bozhidar@batsov.com> |
||||
;; Hugo Duncan <hugo@hugoduncan.org> |
||||
;; Steve Purcell <steve@sanityinc.com> |
||||
|
||||
;; 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/>. |
||||
|
||||
;; This file is not part of GNU Emacs. |
||||
|
||||
;;; Commentary: |
||||
|
||||
;; Common utility functions that don't belong anywhere else. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'seq) |
||||
(require 'cl-lib) |
||||
(require 'clojure-mode) |
||||
|
||||
(defalias 'cider-pop-back 'pop-tag-mark) |
||||
(define-obsolete-function-alias 'cider-jump-back 'cider-pop-back "0.10.0") |
||||
|
||||
(defcustom cider-font-lock-max-length 10000 |
||||
"The max length of strings to fontify in `cider-font-lock-as'. |
||||
|
||||
Setting this to nil removes the fontification restriction." |
||||
:group 'cider |
||||
:type 'boolean |
||||
:package-version '(cider . "0.10.0")) |
||||
|
||||
(defun cider-util--hash-keys (hashtable) |
||||
"Return a list of keys in HASHTABLE." |
||||
(let ((keys '())) |
||||
(maphash (lambda (k _v) (setq keys (cons k keys))) hashtable) |
||||
keys)) |
||||
|
||||
(defun cider-util--clojure-buffers () |
||||
"Return a list of all existing `clojure-mode' buffers." |
||||
(seq-filter |
||||
(lambda (buffer) (with-current-buffer buffer (derived-mode-p 'clojure-mode))) |
||||
(buffer-list))) |
||||
|
||||
(defun cider-current-dir () |
||||
"Return the directory of the current buffer." |
||||
(if buffer-file-name |
||||
(file-name-directory buffer-file-name) |
||||
default-directory)) |
||||
|
||||
(defun cider-in-string-p () |
||||
"Return true if point is in a string." |
||||
(let ((beg (save-excursion (beginning-of-defun) (point)))) |
||||
(nth 3 (parse-partial-sexp beg (point))))) |
||||
|
||||
(defun cider-in-comment-p () |
||||
"Return true if point is in a comment." |
||||
(let ((beg (save-excursion (beginning-of-defun) (point)))) |
||||
(nth 4 (parse-partial-sexp beg (point))))) |
||||
|
||||
(defun cider--tooling-file-p (file-name) |
||||
"Return t if FILE-NAME is not a 'real' source file. |
||||
Currently, only check if the relative file name starts with 'form-init' |
||||
which nREPL uses for temporary evaluation file names." |
||||
(let ((fname (file-name-nondirectory file-name))) |
||||
(string-match-p "^form-init" fname))) |
||||
|
||||
;;; Thing at point |
||||
(defun cider-defun-at-point () |
||||
"Return the text of the top-level sexp at point." |
||||
(apply #'buffer-substring-no-properties |
||||
(cider--region-for-defun-at-point))) |
||||
|
||||
(defun cider--region-for-defun-at-point () |
||||
"Return the start and end position of defun at point." |
||||
(save-excursion |
||||
(save-match-data |
||||
(end-of-defun) |
||||
(let ((end (point))) |
||||
(beginning-of-defun) |
||||
(list (point) end))))) |
||||
|
||||
(defun cider-defun-at-point-start-pos () |
||||
"Return the starting position of the current defun." |
||||
(car (cider--region-for-defun-at-point))) |
||||
|
||||
(defun cider-ns-form () |
||||
"Retrieve the ns form." |
||||
(when (clojure-find-ns) |
||||
(save-excursion |
||||
(goto-char (match-beginning 0)) |
||||
(cider-defun-at-point)))) |
||||
|
||||
(defun cider-bounds-of-sexp-at-point () |
||||
"Return the bounds sexp at point as a pair (or nil)." |
||||
(or (and (equal (char-after) ?\() |
||||
(member (char-before) '(?\' ?\, ?\@)) |
||||
;; hide stuff before ( to avoid quirks with '( etc. |
||||
(save-restriction |
||||
(narrow-to-region (point) (point-max)) |
||||
(bounds-of-thing-at-point 'sexp))) |
||||
(bounds-of-thing-at-point 'sexp))) |
||||
|
||||
(defun cider-map-indexed (f list) |
||||
"Return a list of (F index item) for each item in LIST." |
||||
(let ((i 0) |
||||
(out)) |
||||
(dolist (it list (nreverse out)) |
||||
(push (funcall f i it) out) |
||||
(setq i (1+ i))))) |
||||
|
||||
(defun cider-symbol-at-point () |
||||
"Return the name of the symbol at point, otherwise nil." |
||||
(let ((str (or (thing-at-point 'symbol) ""))) |
||||
(if (text-property-any 0 (length str) 'field 'cider-repl-prompt str) |
||||
"" |
||||
str))) |
||||
|
||||
(defun cider-sexp-at-point () |
||||
"Return the sexp at point as a string, otherwise nil." |
||||
(let ((bounds (cider-bounds-of-sexp-at-point))) |
||||
(if bounds |
||||
(buffer-substring-no-properties (car bounds) |
||||
(cdr bounds))))) |
||||
|
||||
(defun cider-sexp-at-point-with-bounds () |
||||
"Return a list containing the sexp at point and its bounds." |
||||
(let ((bounds (cider-bounds-of-sexp-at-point))) |
||||
(if bounds |
||||
(let ((start (car bounds)) |
||||
(end (cdr bounds))) |
||||
(list (buffer-substring-no-properties start end) |
||||
(cons (set-marker (make-marker) start) |
||||
(set-marker (make-marker) end))))))) |
||||
|
||||
(defun cider-last-sexp (&optional bounds) |
||||
"Return the sexp preceding the point. |
||||
If BOUNDS is non-nil, return a list of its starting and ending position |
||||
instead." |
||||
(apply (if bounds #'list #'buffer-substring-no-properties) |
||||
(save-excursion |
||||
(clojure-backward-logical-sexp 1) |
||||
(list (point) |
||||
(progn (clojure-forward-logical-sexp 1) |
||||
(point)))))) |
||||
|
||||
|
||||
;;; Text properties |
||||
|
||||
(defun cider-maybe-intern (name) |
||||
"If NAME is a symbol, return it; otherwise, intern it." |
||||
(if (symbolp name) name (intern name))) |
||||
|
||||
(defun cider-intern-keys (props) |
||||
"Copy plist-style PROPS with any non-symbol keys replaced with symbols." |
||||
(cider-map-indexed (lambda (i x) (if (cl-oddp i) x (cider-maybe-intern x))) props)) |
||||
|
||||
(defmacro cider-propertize-region (props &rest body) |
||||
"Execute BODY and add PROPS to all the text it inserts. |
||||
More precisely, PROPS are added to the region between the point's |
||||
positions before and after executing BODY." |
||||
(declare (indent 1)) |
||||
(let ((start (cl-gensym))) |
||||
`(let ((,start (point))) |
||||
(prog1 (progn ,@body) |
||||
(add-text-properties ,start (point) ,props))))) |
||||
|
||||
(put 'cider-propertize-region 'lisp-indent-function 1) |
||||
|
||||
(defun cider-property-bounds (prop) |
||||
"Return the the positions of the previous and next change to PROP. |
||||
PROP is the name of a text property." |
||||
(let ((end (next-single-char-property-change (point) prop))) |
||||
(list (previous-single-char-property-change end prop) end))) |
||||
|
||||
(defun cider-insert (text &optional face break more-text) |
||||
"Insert TEXT with FACE, optionally followed by a line BREAK and MORE-TEXT." |
||||
(insert (if face (propertize text 'font-lock-face face) text)) |
||||
(when more-text (insert more-text)) |
||||
(when break (insert "\n"))) |
||||
|
||||
;;; Font lock |
||||
|
||||
(defun cider--font-lock-ensure () |
||||
"Call `font-lock-ensure' or `font-lock-fontify-buffer', as appropriate." |
||||
(if (fboundp 'font-lock-ensure) |
||||
(font-lock-ensure) |
||||
(with-no-warnings |
||||
(font-lock-fontify-buffer)))) |
||||
|
||||
(defvar cider--mode-buffers nil |
||||
"A list of buffers for different major modes.") |
||||
|
||||
(defun cider--make-buffer-for-mode (mode) |
||||
"Return a temp buffer using major-mode MODE. |
||||
This buffer is not designed to display anything to the user. For that, use |
||||
`cider-make-popup-buffer' instead." |
||||
(or (cdr (assq mode cider--mode-buffers)) |
||||
(let ((b (generate-new-buffer (format " *cider-temp %s*" mode)))) |
||||
(push (cons mode b) cider--mode-buffers) |
||||
(with-current-buffer b |
||||
;; suppress major mode hooks as we care only about their font-locking |
||||
;; otherwise modes like whitespace-mode and paredit might interfere |
||||
(setq-local delay-mode-hooks t) |
||||
(setq delayed-mode-hooks nil) |
||||
(funcall mode)) |
||||
b))) |
||||
|
||||
(defun cider-font-lock-as (mode string) |
||||
"Use MODE to font-lock the STRING." |
||||
(if (or (null cider-font-lock-max-length) |
||||
(< (length string) cider-font-lock-max-length)) |
||||
(with-current-buffer (cider--make-buffer-for-mode mode) |
||||
(erase-buffer) |
||||
(insert string) |
||||
(font-lock-fontify-region (point-min) (point-max)) |
||||
(buffer-string)) |
||||
string)) |
||||
|
||||
(defun cider-font-lock-region-as (mode beg end &optional buffer) |
||||
"Use MODE to font-lock text between BEG and END. |
||||
|
||||
Unless you specify a BUFFER it will default to the current one." |
||||
(with-current-buffer (or buffer (current-buffer)) |
||||
(let ((text (buffer-substring beg end))) |
||||
(delete-region beg end) |
||||
(goto-char beg) |
||||
(insert (cider-font-lock-as mode text))))) |
||||
|
||||
(defun cider-font-lock-as-clojure (string) |
||||
"Font-lock STRING as Clojure code." |
||||
(cider-font-lock-as 'clojure-mode string)) |
||||
|
||||
;;; Colors |
||||
|
||||
(defun cider-scale-color (color scale) |
||||
"For a COLOR hex string or name, adjust intensity of RGB components by SCALE." |
||||
(let* ((rgb (color-values color)) |
||||
(scaled-rgb (mapcar (lambda (n) |
||||
(format "%04x" (round (+ n (* scale 65535))))) |
||||
rgb))) |
||||
(apply #'concat "#" scaled-rgb))) |
||||
|
||||
(defun cider-scale-background-color () |
||||
"Scale the current background color to get a slighted muted version." |
||||
(let ((color (frame-parameter nil 'background-color)) |
||||
(dark (eq (frame-parameter nil 'background-mode) 'dark))) |
||||
(cider-scale-color color (if dark 0.05 -0.05)))) |
||||
|
||||
(autoload 'pkg-info-version-info "pkg-info.el") |
||||
|
||||
(defvar cider-version) |
||||
|
||||
(defun cider--version () |
||||
"Retrieve CIDER's version." |
||||
(condition-case nil |
||||
(pkg-info-version-info 'cider) |
||||
(error cider-version))) |
||||
|
||||
;;; Strings |
||||
|
||||
(defun cider-string-join (strings &optional separator) |
||||
"Join all STRINGS using SEPARATOR." |
||||
(mapconcat #'identity strings separator)) |
||||
|
||||
(defun cider-join-into-alist (candidates &optional separator) |
||||
"Make an alist from CANDIDATES. |
||||
The keys are the elements joined with SEPARATOR and values are the original |
||||
elements. Useful for `completing-read' when candidates are complex |
||||
objects." |
||||
(mapcar (lambda (el) |
||||
(if (listp el) |
||||
(cons (cider-string-join el (or separator ":")) el) |
||||
(cons el el))) |
||||
candidates)) |
||||
|
||||
(defun cider-namespace-qualified-p (sym) |
||||
"Return t if SYM is namespace-qualified." |
||||
(string-match-p "[^/]+/" sym)) |
||||
|
||||
(defun cider--readme-button (label section-id) |
||||
"Return a button string that links to the online readme. |
||||
LABEL is the displayed string, and SECTION-ID is where it points |
||||
to." |
||||
(with-temp-buffer |
||||
(insert-text-button |
||||
label |
||||
'follow-link t |
||||
'action (lambda (&rest _) (interactive) |
||||
(browse-url (concat "https://github.com/clojure-emacs/cider#" |
||||
section-id)))) |
||||
(buffer-string))) |
||||
|
||||
(defun cider--project-name (dir) |
||||
"Extracts a project name from DIR, possibly nil. |
||||
The project name is the final component of DIR if not nil." |
||||
(when dir |
||||
(file-name-nondirectory (directory-file-name dir)))) |
||||
|
||||
;;; Vectors |
||||
(defun cider--deep-vector-to-list (x) |
||||
"Convert vectors in X to lists. |
||||
If X is a sequence, return a list of `cider--deep-vector-to-list' applied to |
||||
each of its elements. |
||||
Any other value is just returned." |
||||
(if (sequencep x) |
||||
(mapcar #'cider--deep-vector-to-list x) |
||||
x)) |
||||
|
||||
;;; Words of inspiration |
||||
(defun cider-user-first-name () |
||||
"Find the current user's first name." |
||||
(let ((name (if (string= (user-full-name) "") |
||||
(user-login-name) |
||||
(user-full-name)))) |
||||
(string-match "^[^ ]*" name) |
||||
(capitalize (match-string 0 name)))) |
||||
|
||||
(defvar cider-words-of-inspiration |
||||
`("The best way to predict the future is to invent it. -Alan Kay" |
||||
"A point of view is worth 80 IQ points. -Alan Kay" |
||||
"Lisp isn't a language, it's a building material. -Alan Kay" |
||||
"Simple things should be simple, complex things should be possible. -Alan Kay" |
||||
"Everything should be as simple as possible, but not simpler. -Albert Einstein" |
||||
"Measuring programming progress by lines of code is like measuring aircraft building progress by weight. -Bill Gates" |
||||
"Controlling complexity is the essence of computer programming. -Brian Kernighan" |
||||
"The unavoidable price of reliability is simplicity. -C.A.R. Hoare" |
||||
"You're bound to be unhappy if you optimize everything. -Donald Knuth" |
||||
"Simplicity is prerequisite for reliability. -Edsger W. Dijkstra" |
||||
"Elegance is not a dispensable luxury but a quality that decides between success and failure. -Edsger W. Dijkstra" |
||||
"Deleted code is debugged code. -Jeff Sickel" |
||||
"The key to performance is elegance, not battalions of special cases. -Jon Bentley and Doug McIlroy" |
||||
"First, solve the problem. Then, write the code. -John Johnson" |
||||
"Simplicity is the ultimate sophistication. -Leonardo da Vinci" |
||||
"Programming is not about typing... it's about thinking. -Rich Hickey" |
||||
"Design is about pulling things apart. -Rich Hickey" |
||||
"Programmers know the benefits of everything and the tradeoffs of nothing. -Rich Hickey" |
||||
"Code never lies, comments sometimes do. -Ron Jeffries" |
||||
"The true delight is in the finding out rather than in the knowing. -Isaac Asimov" |
||||
"If paredit is not for you, then you need to become the sort of person that paredit is for. -Phil Hagelberg" |
||||
"Express Yourself. -Madonna" |
||||
"Take this REPL, fellow hacker, and may it serve you well." |
||||
"Let the hacking commence!" |
||||
"Hacks and glory await!" |
||||
"Hack and be merry!" |
||||
"Your hacking starts... NOW!" |
||||
"May the Source be with you!" |
||||
"May the Source shine upon thy REPL!" |
||||
"Code long and prosper!" |
||||
"Happy hacking!" |
||||
"nREPL server is up, CIDER REPL is online!" |
||||
"CIDER REPL operational!" |
||||
"Your imagination is the only limit to what you can do with this REPL!" |
||||
"This REPL is yours to command!" |
||||
"Fame is but a hack away!" |
||||
"The REPL is not enough, but it is such a perfect place to start..." |
||||
,(format "%s, this could be the start of a beautiful program." |
||||
(cider-user-first-name))) |
||||
"Scientifically-proven optimal words of hackerish encouragement.") |
||||
|
||||
(defun cider-random-words-of-inspiration () |
||||
"Select a random entry from `cider-words-of-inspiration'." |
||||
(eval (nth (random (length cider-words-of-inspiration)) |
||||
cider-words-of-inspiration))) |
||||
|
||||
(provide 'cider-util) |
||||
|
||||
;;; cider-util.el ends here |
File diff suppressed because it is too large
Load Diff
@ -1 +0,0 @@
|
||||
(define-package "flycheck-clojure" "20150831.631" "Flycheck: Clojure support" '((cider "0.8.1") (flycheck "0.22alpha1") (let-alist "1.0.1") (emacs "24")) :url "https://github.com/clojure-emacs/squiggly-clojure") |
@ -1,205 +0,0 @@
|
||||
;;; flycheck-clojure.el --- Flycheck: Clojure support -*- lexical-binding: t; -*- |
||||
|
||||
;; Copyright © 2014 Peter Fraenkel |
||||
;; Copyright (C) 2014 Sebastian Wiesner <swiesner@lunaryorn.com> |
||||
;; |
||||
;; Author: Peter Fraenkel <pnf@podsnap.com> |
||||
;; Sebastian Wiesner <swiesner@lunaryorn.com> |
||||
;; Maintainer: Peter Fraenkel <pnf@podsnap.com> |
||||
;; URL: https://github.com/clojure-emacs/squiggly-clojure |
||||
;; Package-Version: 20150831.631 |
||||
;; Version: 1.1.0 |
||||
;; Package-Requires: ((cider "0.8.1") (flycheck "0.22-cvs1") (let-alist "1.0.1") (emacs "24")) |
||||
|
||||
;; 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: |
||||
|
||||
;; Add Clojure support to Flycheck. |
||||
;; |
||||
;; Provide syntax checkers to check Clojure code using a running Cider repl. |
||||
;; |
||||
;; Installation: |
||||
;; |
||||
;; (eval-after-load 'flycheck '(flycheck-clojure-setup)) |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cider-client) |
||||
(require 'flycheck) |
||||
(require 'json) |
||||
(require 'url-parse) |
||||
(eval-when-compile (require 'let-alist)) |
||||
|
||||
;;;###autoload |
||||
(defun flycheck-clojure-parse-cider-errors (value checker) |
||||
"Parse cider errors from JSON VALUE from CHECKER. |
||||
|
||||
Return a list of parsed `flycheck-error' objects." |
||||
;; Parse the nested JSON from Cider. The outer JSON contains the return value |
||||
;; from Cider, and the inner JSON the errors returned by the individual |
||||
;; checker. |
||||
(let ((error-objects (json-read-from-string (json-read-from-string value)))) |
||||
(mapcar (lambda (o) |
||||
(let-alist o |
||||
;; Use the file name reported by the syntax checker, but only if |
||||
;; its absolute, because typed reports relative file names that |
||||
;; are hard to expand correctly, since they are relative to the |
||||
;; source directory (not the project directory). |
||||
(let* ((parsed-file (when .file |
||||
(url-filename |
||||
(url-generic-parse-url .file)))) |
||||
(filename (if (and parsed-file |
||||
(file-name-absolute-p parsed-file)) |
||||
parsed-file |
||||
(buffer-file-name)))) |
||||
(flycheck-error-new-at .line .column (intern .level) .msg |
||||
:checker checker |
||||
:filename filename)))) |
||||
error-objects))) |
||||
|
||||
(defun cider-flycheck-eval (input callback) |
||||
"Send the request INPUT and register the CALLBACK as the response handler. |
||||
Uses the tooling session, with no specified namespace." |
||||
(cider-nrepl-request:eval input callback)) |
||||
|
||||
(defun flycheck-clojure-may-use-cider-checker () |
||||
"Determine whether a cider checker may be used. |
||||
|
||||
Checks for `cider-mode', and a current nREPL connection. |
||||
|
||||
Standard predicate for cider checkers." |
||||
(let ((connection-buffer (cider-default-connection :no-error))) |
||||
(and (bound-and-true-p cider-mode) |
||||
connection-buffer |
||||
(buffer-live-p (get-buffer connection-buffer)) |
||||
(clojure-find-ns)))) |
||||
|
||||
(defun flycheck-clojure-start-cider (checker callback) |
||||
"Start a cider syntax CHECKER with CALLBACK." |
||||
(let ((ns (clojure-find-ns)) |
||||
(form (get checker 'flycheck-clojure-form))) |
||||
(cider-flycheck-eval |
||||
(funcall form ns) |
||||
(nrepl-make-response-handler |
||||
(current-buffer) |
||||
(lambda (buffer value) |
||||
(funcall callback 'finished |
||||
(with-current-buffer buffer |
||||
(flycheck-clojure-parse-cider-errors value checker)))) |
||||
nil ; stdout |
||||
nil ; stderr |
||||
(lambda (_) |
||||
;; If the evaluation completes without returning any value, there has |
||||
;; gone something wrong. Ideally, we'd report *what* was wrong, but |
||||
;; `nrepl-make-response-handler' is close to useless for this :(, |
||||
;; because it just `message's for many status codes that are errors for |
||||
;; us :( |
||||
(funcall callback 'errored "Done with no errors")) |
||||
(lambda (_buffer ex _rootex _sess) |
||||
(funcall callback 'errored |
||||
(format "Form %s of checker %s failed: %s" |
||||
form checker ex)))))) |
||||
) |
||||
|
||||
(defun flycheck-clojure-define-cider-checker (name docstring &rest properties) |
||||
"Define a Cider syntax checker with NAME, DOCSTRING and PROPERTIES. |
||||
|
||||
NAME, DOCSTRING, and PROPERTIES are like for |
||||
`flycheck-define-generic-checker', except that `:start' and |
||||
`:modes' are invalid PROPERTIES. A syntax checker defined with |
||||
this function will always check in `clojure-mode', and only if |
||||
`cider-mode' is enabled. |
||||
|
||||
Instead of `:start', this syntax checker requires a `:form |
||||
FUNCTION' property. FUNCTION takes the current Clojure namespace |
||||
as single argument, and shall return a string containing a |
||||
Clojure form to be sent to Cider to check the current buffer." |
||||
(declare (indent 1) |
||||
(doc-string 2)) |
||||
(let* ((form (plist-get properties :form)) |
||||
(orig-predicate (plist-get properties :predicate))) |
||||
|
||||
(when (plist-get :start properties) |
||||
(error "Checker %s may not have :start" name)) |
||||
(when (plist-get :modes properties) |
||||
(error "Checker %s may not have :modes" name)) |
||||
(unless (functionp form) |
||||
(error ":form %s of %s not a valid function" form name)) |
||||
(apply #'flycheck-define-generic-checker |
||||
name docstring |
||||
:start #'flycheck-clojure-start-cider |
||||
:modes '(clojure-mode) |
||||
:predicate (if orig-predicate |
||||
(lambda () |
||||
(and (flycheck-clojure-may-use-cider-checker) |
||||
(funcall orig-predicate))) |
||||
#'flycheck-clojure-may-use-cider-checker) |
||||
properties) |
||||
|
||||
(put name 'flycheck-clojure-form form))) |
||||
|
||||
(flycheck-clojure-define-cider-checker 'clojure-cider-eastwood |
||||
"A syntax checker for Clojure, using Eastwood in Cider. |
||||
|
||||
See URL `https://github.com/jonase/eastwood' and URL |
||||
`https://github.com/clojure-emacs/cider/' for more information." |
||||
:form (lambda (ns) |
||||
(format "(do (require 'squiggly-clojure.core) (squiggly-clojure.core/check-ew '%s))" |
||||
ns)) |
||||
:next-checkers '(clojure-cider-kibit clojure-cider-typed)) |
||||
|
||||
(flycheck-clojure-define-cider-checker 'clojure-cider-kibit |
||||
"A syntax checker for Clojure, using Kibit in Cider. |
||||
|
||||
See URL `https://github.com/jonase/kibit' and URL |
||||
`https://github.com/clojure-emacs/cider/' for more information." |
||||
:form (lambda (ns) |
||||
(format |
||||
"(do (require 'squiggly-clojure.core) (squiggly-clojure.core/check-kb '%s %s))" |
||||
ns |
||||
;; Escape file name for Clojure |
||||
(flycheck-sexp-to-string (buffer-file-name)))) |
||||
:predicate (lambda () (buffer-file-name)) |
||||
:next-checkers '(clojure-cider-typed)) |
||||
|
||||
(flycheck-clojure-define-cider-checker 'clojure-cider-typed |
||||
"A syntax checker for Clojure, using Typed Clojure in Cider. |
||||
|
||||
See URL `https://github.com/clojure-emacs/cider/' and URL |
||||
`https://github.com/clojure/core.typed' for more information." |
||||
:form (lambda (ns) |
||||
(format |
||||
"(do (require 'squiggly-clojure.core) (squiggly-clojure.core/check-tc '%s))" |
||||
ns))) |
||||
|
||||
;;;###autoload |
||||
(defun flycheck-clojure-setup () |
||||
"Setup Flycheck for Clojure." |
||||
(interactive) |
||||
;; Add checkers in reverse order, because `add-to-list' adds to front. |
||||
(dolist (checker '(clojure-cider-typed |
||||
clojure-cider-kibit |
||||
clojure-cider-eastwood)) |
||||
(add-to-list 'flycheck-checkers checker))) |
||||
|
||||
(provide 'flycheck-clojure) |
||||
|
||||
;; Local Variables: |
||||
;; indent-tabs-mode: nil |
||||
;; End: |
||||
|
||||
;;; flycheck-clojure.el ends here |
@ -1,11 +0,0 @@
|
||||
(define-package "flycheck-haskell" "20151010.340" "Flycheck: Cabal projects and sandboxes" |
||||
'((emacs "24.1") |
||||
(flycheck "0.22") |
||||
(haskell-mode "13.7") |
||||
(dash "2.4.0") |
||||
(let-alist "1.0.1")) |
||||
:url "https://github.com/flycheck/flycheck-haskell" :keywords |
||||
'("tools" "convenience")) |
||||
;; Local Variables: |
||||
;; no-byte-compile: t |
||||
;; End: |
@ -1,209 +0,0 @@
|
||||
-- Copyright (C) 2014, 2015 Sebastian Wiesner <swiesner@lunaryorn.com> |
||||
-- Copyright (C) 2014 Gracjan Polak <gracjanpolak@gmail.com> |
||||
-- Copyright (C) 2015 Michael Alan Dorman <mdorman@ironicdesign.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/>. |
||||
|
||||
{-# LANGUAGE CPP #-} |
||||
{-# LANGUAGE TypeSynonymInstances #-} |
||||
{-# LANGUAGE FlexibleInstances #-} |
||||
|
||||
import Control.Arrow (second) |
||||
import Data.List (nub, isPrefixOf) |
||||
import Data.Maybe (listToMaybe) |
||||
#ifdef USE_COMPILER_ID |
||||
import Distribution.Compiler |
||||
(CompilerFlavor(GHC), CompilerId(CompilerId), buildCompilerFlavor) |
||||
#else |
||||
import Distribution.Compiler |
||||
(AbiTag(NoAbiTag), CompilerFlavor(GHC), CompilerId(CompilerId), |
||||
CompilerInfo, buildCompilerFlavor, unknownCompilerInfo) |
||||
#endif |
||||
import Distribution.Package |
||||
(PackageName(..), PackageIdentifier(..), Dependency(..)) |
||||
import Distribution.PackageDescription |
||||
(PackageDescription(..), allBuildInfo, BuildInfo(..), |
||||
usedExtensions, allLanguages, hcOptions, exeName, testEnabled, |
||||
condTestSuites, benchmarkEnabled, condBenchmarks) |
||||
import Distribution.PackageDescription.Configuration |
||||
(finalizePackageDescription, mapTreeData) |
||||
import Distribution.PackageDescription.Parse (readPackageDescription) |
||||
import Distribution.Simple.BuildPaths (defaultDistPref) |
||||
import Distribution.System (buildPlatform) |
||||
import Distribution.Verbosity (silent) |
||||
import Language.Haskell.Extension (Extension(..),Language(..)) |
||||
import System.Environment (getArgs) |
||||
import System.Exit (exitFailure) |
||||
import System.FilePath ((</>),dropFileName,normalise) |
||||
import System.Info (compilerVersion) |
||||
|
||||
data Sexp |
||||
= SList [Sexp] |
||||
| SString String |
||||
| SSymbol String |
||||
|
||||
sym :: String -> Sexp |
||||
sym = SSymbol |
||||
|
||||
instance Show Sexp where |
||||
show (SSymbol s) = s |
||||
show (SString s) = show s -- Poor man's escaping |
||||
show (SList s) = "(" ++ unwords (map show s) ++ ")" |
||||
|
||||
class ToSexp a where |
||||
toSexp :: a -> Sexp |
||||
|
||||
instance ToSexp String where |
||||
toSexp = SString |
||||
|
||||
instance ToSexp Extension where |
||||
toSexp (EnableExtension ext) = toSexp (show ext) |
||||
toSexp (DisableExtension ext) = toSexp ("No" ++ show ext) |
||||
toSexp (UnknownExtension ext) = toSexp ext |
||||
|
||||
instance ToSexp Language where |
||||
toSexp (UnknownLanguage lang) = toSexp lang |
||||
toSexp lang = toSexp (show lang) |
||||
|
||||
instance ToSexp Dependency where |
||||
toSexp (Dependency (PackageName dependency) _) = toSexp dependency |
||||
|
||||
instance ToSexp Sexp where |
||||
toSexp = id |
||||
|
||||
cons :: (ToSexp a, ToSexp b) => a -> [b] -> Sexp |
||||
cons h t = SList (toSexp h : map toSexp t) |
||||
|
||||
getBuildDirectories :: PackageDescription -> FilePath -> [String] |
||||
getBuildDirectories pkgDesc cabalDir = |
||||
case library pkgDesc of |
||||
Just _ -> buildDir : buildDirs |
||||
Nothing -> buildDirs |
||||
where |
||||
distDir = cabalDir </> defaultDistPref |
||||
buildDir = distDir </> "build" |
||||
autogenDir = buildDir </> "autogen" |
||||
executableBuildDir e = buildDir </> exeName e </> (exeName e ++ "-tmp") |
||||
buildDirs = autogenDir : map executableBuildDir (executables pkgDesc) |
||||
|
||||
getSourceDirectories :: [BuildInfo] -> FilePath -> [String] |
||||
getSourceDirectories buildInfo cabalDir = |
||||
map (cabalDir </>) (concatMap hsSourceDirs buildInfo) |
||||
|
||||
allowedOptions :: [String] |
||||
allowedOptions = |
||||
[ "-W" |
||||
, "-w" |
||||
, "-Wall" |
||||
, "-fglasgow-exts" |
||||
, "-fpackage-trust" |
||||
, "-fhelpful-errors" |
||||
, "-F" |
||||
, "-cpp"] |
||||
|
||||
allowedOptionPrefixes :: [String] |
||||
allowedOptionPrefixes = |
||||
[ "-fwarn-" |
||||
, "-fno-warn-" |
||||
, "-fcontext-stack=" |
||||
, "-firrefutable-tuples" |
||||
, "-D" |
||||
, "-U" |
||||
, "-I" |
||||
, "-fplugin=" |
||||
, "-fplugin-opt=" |
||||
, "-pgm" |
||||
, "-opt"] |
||||
|
||||
isAllowedOption :: String -> Bool |
||||
isAllowedOption opt = |
||||
elem opt allowedOptions || any (`isPrefixOf` opt) allowedOptionPrefixes |
||||
|
||||
dumpPackageDescription :: PackageDescription -> FilePath -> Sexp |
||||
dumpPackageDescription pkgDesc cabalFile = |
||||
SList |
||||
[ cons (sym "build-directories") buildDirs |
||||
, cons (sym "source-directories") sourceDirs |
||||
, cons (sym "extensions") exts |
||||
, cons (sym "languages") langs |
||||
, cons (sym "dependencies") deps |
||||
, cons (sym "other-options") otherOptions] |
||||
where |
||||
cabalDir = dropFileName cabalFile |
||||
buildInfo = allBuildInfo pkgDesc |
||||
buildDirs = nub (map normalise (getBuildDirectories pkgDesc cabalDir)) |
||||
sourceDirs = nub (map normalise (getSourceDirectories buildInfo cabalDir)) |
||||
exts = nub (concatMap usedExtensions buildInfo) |
||||
langs = nub (concatMap allLanguages buildInfo) |
||||
thisPackage = (pkgName . package) pkgDesc |
||||
deps = |
||||
nub |
||||
(filter |
||||
(\(Dependency name _) -> |
||||
name /= thisPackage) |
||||
(buildDepends pkgDesc)) |
||||
otherOptions = |
||||
nub (filter isAllowedOption (concatMap (hcOptions GHC) buildInfo)) |
||||
|
||||
dumpCabalConfiguration :: String -> IO () |
||||
dumpCabalConfiguration cabalFile = do |
||||
genericDesc <- readPackageDescription silent cabalFile |
||||
-- This let block is eerily like one in Cabal.Distribution.Simple.Configure |
||||
let enableTest t = |
||||
t |
||||
{ testEnabled = True |
||||
} |
||||
flaggedTests = |
||||
map (second (mapTreeData enableTest)) (condTestSuites genericDesc) |
||||
enableBenchmark bm = |
||||
bm |
||||
{ benchmarkEnabled = True |
||||
} |
||||
flaggedBenchmarks = |
||||
map |
||||
(second (mapTreeData enableBenchmark)) |
||||
(condBenchmarks genericDesc) |
||||
genericDesc' = |
||||
genericDesc |
||||
{ condTestSuites = flaggedTests |
||||
, condBenchmarks = flaggedBenchmarks |
||||
} |
||||
case finalizePackageDescription |
||||
[] |
||||
(const True) |
||||
buildPlatform |
||||
buildCompilerId |
||||
[] |
||||
genericDesc' of |
||||
Left e -> putStrLn $ "Issue with package configuration\n" ++ show e |
||||
Right (pkgDesc,_) -> print (dumpPackageDescription pkgDesc cabalFile) |
||||
|
||||
#ifdef USE_COMPILER_ID |
||||
buildCompilerId :: CompilerId |
||||
buildCompilerId = CompilerId buildCompilerFlavor compilerVersion |
||||
#else |
||||
buildCompilerId :: CompilerInfo |
||||
buildCompilerId = |
||||
unknownCompilerInfo |
||||
(CompilerId buildCompilerFlavor compilerVersion) |
||||
NoAbiTag |
||||
#endif |
||||
|
||||
main :: IO () |
||||
main = do |
||||
args <- getArgs |
||||
let cabalFile = listToMaybe args |
||||
maybe exitFailure dumpCabalConfiguration cabalFile |
@ -1,48 +0,0 @@
|
||||
-- Copyright (C) 2015 Michael Alan Dorman <mdorman@ironicdesign.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/>. |
||||
|
||||
import Data.Version (Version (Version)) |
||||
import Distribution.Simple.Utils (cabalVersion) |
||||
import System.Environment (getArgs) |
||||
|
||||
data Mode |
||||
= GHC |
||||
| HLint |
||||
|
||||
define :: Mode -> String -> String |
||||
define GHC def = "-D" ++ def |
||||
define HLint def = "--cpp-define=" ++ def |
||||
|
||||
legacyFlags :: Mode -> [String] |
||||
legacyFlags mode = [define mode "USE_COMPILER_ID"] |
||||
|
||||
isLegacyCabal :: Bool |
||||
isLegacyCabal = cabalVersion < Version [1, 22] [] |
||||
|
||||
getMode :: [String] -> Mode |
||||
getMode ("hlint":_) = HLint |
||||
getMode _ = GHC |
||||
|
||||
main :: IO () |
||||
main = do |
||||
args <- getArgs |
||||
mapM_ putStrLn (flags (getMode args)) |
||||
where |
||||
flags mode = |
||||
if isLegacyCabal |
||||
then legacyFlags mode |
||||
else [] |
@ -1 +0,0 @@
|
||||
(define-package "flycheck-pos-tip" "20140606.510" "Flycheck errors display in tooltip" '((flycheck "0.18") (popup "0.5.0")) :url "https://github.com/flycheck/flycheck-pos-tip" :keywords '("tools" "convenience")) |
@ -1,61 +0,0 @@
|
||||
;;; flycheck-pos-tip.el --- Flycheck errors display in tooltip |
||||
|
||||
;; Copyright (C) 2014 Akiha Senda |
||||
|
||||
;; Author: Akiha Senda <senda.akiha@gmail.com> |
||||
;; URL: https://github.com/flycheck/flycheck-pos-tip |
||||
;; Package-Version: 20140606.510 |
||||
;; Keywords: tools, convenience |
||||
;; Version: 0.0.1 |
||||
;; Package-Requires: ((flycheck "0.18") (popup "0.5.0")) |
||||
|
||||
;; 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: |
||||
|
||||
;; Add error message display method for Flycheck. |
||||
|
||||
;;;; Setup |
||||
|
||||
;; (eval-after-load 'flycheck |
||||
;; '(custom-set-variables |
||||
;; '(flycheck-display-errors-function #'flycheck-pos-tip-error-messages))) |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'flycheck) |
||||
(require 'popup) |
||||
|
||||
(defgroup flycheck-pos-tip nil |
||||
"Flycheck errors display in tooltip" |
||||
:prefix "flycheck-pos-tip-" |
||||
:group 'flycheck |
||||
:link '(url-link :tag "Github" "https://github.com/flycheck/flycheck-pos-tip")) |
||||
|
||||
;;;###autoload |
||||
(defun flycheck-pos-tip-error-messages (errors) |
||||
"Display the tooltip that the messages of ERRORS. |
||||
|
||||
Concatenate all non-nil messages of ERRORS separated by empty |
||||
lines, and display them with `pos-tip-show-no-propertize', which shows |
||||
the messages in tooltip, depending on the number of lines." |
||||
(-when-let (messages (-keep #'flycheck-error-message errors)) |
||||
(popup-tip |
||||
(mapconcat 'identity messages "\n")))) |
||||
|
||||
(provide 'flycheck-pos-tip) |
||||
|
||||
;;; flycheck-pos-tip.el ends here |
@ -1 +0,0 @@
|
||||
(define-package "flycheck-rust" "20150609.1248" "Flycheck: Rust additions and Cargo support" '((emacs "24.1") (flycheck "0.20") (dash "2.4.0")) :url "https://github.com/flycheck/flycheck-rust" :keywords '("tools" "convenience")) |
@ -1,121 +0,0 @@
|
||||
;;; flycheck-rust.el --- Flycheck: Rust additions and Cargo support -*- lexical-binding: t; -*- |
||||
|
||||
;; Copyright (C) 2014, 2015 Sebastian Wiesner <swiesner@lunaryorn.com> |
||||
|
||||
;; Author: Sebastian Wiesner <swiesner@lunaryorn.com> |
||||
;; URL: https://github.com/flycheck/flycheck-rust |
||||
;; Package-Version: 20150609.1248 |
||||
;; Keywords: tools, convenience |
||||
;; Version: 0.1-cvs |
||||
;; Package-Requires: ((emacs "24.1") (flycheck "0.20") (dash "2.4.0")) |
||||
|
||||
;; 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: |
||||
|
||||
;; This Flycheck extension configures Flycheck automatically for the current |
||||
;; Cargo project. |
||||
;; |
||||
;; # Setup |
||||
;; |
||||
;; (add-hook 'flycheck-mode-hook #'flycheck-rust-setup) |
||||
;; |
||||
;; # Usage |
||||
;; |
||||
;; Just use Flycheck as usual in your Rust/Cargo projects. |
||||
;; |
||||
;; Note: You must run `cargo build` initially to install all dependencies. If |
||||
;; you add new dependencies to `Cargo.toml` you need to run `cargo build` |
||||
;; again. Otherwise you will see spurious errors about missing crates. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'dash) |
||||
(require 'flycheck) |
||||
|
||||
(defun flycheck-rust-executable-p (rel-name) |
||||
"Whether REL-NAME denotes an executable. |
||||
|
||||
REL-NAME is the file relative to the Cargo.toml file." |
||||
(or (string= "src/main.rs" rel-name) |
||||
(string-prefix-p "src/bin/" rel-name))) |
||||
|
||||
(defun flycheck-rust-test-p (rel-name) |
||||
"Whether REL-NAME denotes a test. |
||||
|
||||
REL-NAME is the file relative to the Cargo.toml file." |
||||
(string-prefix-p "tests/" rel-name)) |
||||
|
||||
(defun flycheck-rust-bench-p (rel-name) |
||||
"Whether REL-NAME denotes a bench. |
||||
|
||||
REL-NAME is the file relative to the Cargo.toml file." |
||||
(string-prefix-p "benches/" rel-name)) |
||||
|
||||
(defun flycheck-rust-example-p (rel-name) |
||||
"Whether REL-NAME denotes an example. |
||||
|
||||
REL-NAME is the file relative to the Cargo.toml file." |
||||
(string-prefix-p "examples/" rel-name)) |
||||
|
||||
(defun flycheck-rust-project-root () |
||||
"Get the project root for the current buffer. |
||||
|
||||
Return the directory containing the Cargo file, or nil if there |
||||
is none." |
||||
(locate-dominating-file (buffer-file-name) "Cargo.toml")) |
||||
|
||||
(defun flycheck-rust-find-crate-root () |
||||
"Get the crate root (the nearest lib.rs or main.rs) |
||||
relative to the current file." |
||||
(-if-let (lib-crate-dir (locate-dominating-file (buffer-file-name) "lib.rs")) |
||||
(expand-file-name "lib.rs" lib-crate-dir) |
||||
(-when-let (exe-crate-dir (locate-dominating-file (buffer-file-name) "main.rs")) |
||||
(expand-file-name "main.rs" exe-crate-dir)))) |
||||
|
||||
;;;###autoload |
||||
(defun flycheck-rust-setup () |
||||
"Setup Rust in Flycheck. |
||||
|
||||
If the current file is part of a Cargo project, configure |
||||
Flycheck according to the Cargo project layout." |
||||
(interactive) |
||||
(when (buffer-file-name) |
||||
(-when-let (root (flycheck-rust-project-root)) |
||||
(let ((rel-name (file-relative-name (buffer-file-name) root))) |
||||
;; These are valid crate roots as by Cargo's layout |
||||
(unless (or (flycheck-rust-executable-p rel-name) |
||||
(flycheck-rust-test-p rel-name) |
||||
(flycheck-rust-bench-p rel-name) |
||||
(flycheck-rust-example-p rel-name) |
||||
(string= "src/lib.rs" rel-name)) |
||||
;; For other files, the library is either the default library or the |
||||
;; executable |
||||
(setq-local flycheck-rust-crate-root (flycheck-rust-find-crate-root))) |
||||
;; Check tests in libraries and integration tests |
||||
(setq-local flycheck-rust-check-tests |
||||
(not (flycheck-rust-executable-p rel-name))) |
||||
;; Set the crate type |
||||
(setq-local flycheck-rust-crate-type |
||||
(if (flycheck-rust-executable-p rel-name) "bin" "lib")) |
||||
;; Find build libraries |
||||
(setq-local flycheck-rust-library-path |
||||
(list (expand-file-name "target/debug" root) |
||||
(expand-file-name "target/debug/deps" root))))))) |
||||
|
||||
(provide 'flycheck-rust) |
||||
|
||||
;;; flycheck-rust.el ends here |
@ -1 +0,0 @@
|
||||
(define-package "gitignore-mode" "20150330.1048" "Major mode for editing .gitignore files" 'nil :url "https://github.com/magit/git-modes" :keywords '("convenience" "vc" "git")) |
@ -1,61 +0,0 @@
|
||||
;;; gitignore-mode.el --- Major mode for editing .gitignore files -*- lexical-binding: t; -*- |
||||
|
||||
;; Copyright (c) 2012-2013 Sebastian Wiesner |
||||
;; Copyright (C) 2012-2015 The Magit Project Developers |
||||
|
||||
;; Author: Sebastian Wiesner <lunaryorn@gmail.com> |
||||
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li> |
||||
;; Homepage: https://github.com/magit/git-modes |
||||
;; Keywords: convenience vc git |
||||
;; Package-Version: 20150330.1048 |
||||
|
||||
;; This file is not part of GNU Emacs. |
||||
|
||||
;; This file is free software; you can redistribute it and/or modify |
||||
;; it under the terms of the GNU General Public License as published by |
||||
;; the Free Software Foundation; either version 3, or (at your option) |
||||
;; any later version. |
||||
|
||||
;; This file is distributed in the hope that it will be useful, |
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
;; GNU General Public License for more details. |
||||
|
||||
;; You should have received a copy of the GNU General Public License |
||||
;; along with this file. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
;;; Commentary: |
||||
|
||||
;; A major mode for editing .gitignore files. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'conf-mode) |
||||
|
||||
(defvar gitignore-mode-font-lock-keywords |
||||
'(("^\\s<.*$" . font-lock-comment-face) |
||||
("^!" . font-lock-negation-char-face) ; Negative pattern |
||||
("/" . font-lock-constant-face) ; Directory separators |
||||
("[*?]" . font-lock-keyword-face) ; Glob patterns |
||||
("\\[.+?\\]" . font-lock-keyword-face))) ; Ranged glob patterns |
||||
|
||||
;;;###autoload |
||||
(define-derived-mode gitignore-mode conf-unix-mode "Gitignore" |
||||
"A major mode for editing .gitignore files." |
||||
(conf-mode-initialize "#") |
||||
;; Disable syntactic font locking, because comments are only valid at |
||||
;; beginning of line. |
||||
(setq font-lock-defaults '(gitignore-mode-font-lock-keywords t t)) |
||||
(set (make-local-variable 'conf-assignment-sign) nil)) |
||||
|
||||
;;;###autoload |
||||
(dolist (pattern (list "/\\.gitignore\\'" |
||||
"/\\.git/info/exclude\\'" |
||||
"/git/ignore\\'")) |
||||
(add-to-list 'auto-mode-alist (cons pattern 'gitignore-mode))) |
||||
|
||||
(provide 'gitignore-mode) |
||||
;; Local Variables: |
||||
;; indent-tabs-mode: nil |
||||
;; End: |
||||
;;; gitignore-mode.el ends here |
Binary file not shown.
Binary file not shown.
@ -1,438 +0,0 @@
|
||||
Haskell Mode NEWS -*- org -*- |
||||
|
||||
This file uses Org mode. Some useful (default) key-bindings: |
||||
- Use "C-c C-n"/"C-c C-p" to jump to next/prev heading |
||||
- Use "<tab>" to expand/collapse nodes |
||||
- Use "<backtab>" to cycle visibility of all nodes |
||||
- Use "C-c C-o" to open links |
||||
|
||||
* Changes in 13.12 |
||||
|
||||
- Added haskell-bot.el |
||||
|
||||
- Added support for cabal repl build targets |
||||
|
||||
- Automatically add import lines via Hoogle |
||||
|
||||
- Automatically add package to cabal file |
||||
|
||||
- Added w3m-haddock.el |
||||
|
||||
- Added debugger mode |
||||
|
||||
- Added preliminary :present support |
||||
|
||||
- Added haskell-sort-imports |
||||
|
||||
- Added haskell-complete-module |
||||
|
||||
- Support if and multi-way if in indentation |
||||
|
||||
- Add support to generate tags on windows |
||||
|
||||
- Add haskell-language-extensions variable |
||||
|
||||
- Improve haskell-simple-indent mode |
||||
|
||||
- Improve test cases |
||||
|
||||
* Changes in 13.10 |
||||
|
||||
- Small fix for haskell-simple-indent: Certain indentation situations |
||||
cause valname-string to be nil, which haskell-trim did not handle |
||||
gracefully (naturally, since nil != ""). |
||||
|
||||
- Luke Hoersten's Shnippet merged in under snippets/. |
||||
|
||||
- haskell-presentation-mode is now a haskell-mode derived mode. |
||||
|
||||
- Small improvement to haskell-process-do-info (works on constructors |
||||
now and underscored names). |
||||
|
||||
- Add haskell-indent-spaces configuration variable. |
||||
|
||||
- The command string to run cabal commands is slightly more |
||||
configurable. See: C-h f haskell-process-do-cabal-format-string |
||||
|
||||
* Changes in 13.8 |
||||
|
||||
See also [[https://github.com/haskell/haskell-mode/compare/v13.07...v13.08][detailed Git history]]. |
||||
|
||||
- Make `haskell-simple-indent-mode' a proper minor mode with `SInd` as |
||||
mode-line lighter |
||||
|
||||
- Support popular "λ> " prompt in inf-haskell by default |
||||
|
||||
- Hide internal `*print-haskell-mode*' buffers |
||||
(used when `haskell-interactive-mode-eval-mode' is active) |
||||
|
||||
- Add tab-completion support for haskell-interactive-mode |
||||
(requires `:complete' command support in GHCi) |
||||
|
||||
- Add support to `haskell-process-do-info` to perform `:browse!` query |
||||
on module name when called on import statement line |
||||
|
||||
- `haskell-decl-scan-mode': |
||||
- New customize group `haskell-decl-scan' |
||||
- New flag `haskell-decl-scan-bindings-as-variables' for controlling |
||||
whether to put value bindings into the "Variables" category. |
||||
- New flag `haskell-decl-scan-add-to-menubar' for controlling |
||||
whether to add "Declarations" menu entry to menu bar. |
||||
- New manual section node `(haskell-mode)haskell-decl-scan-mode' |
||||
|
||||
- Add support for [[http://www.haskell.org/ghc/docs/latest/html/users_guide/syntax-extns.html#lambda-case][LambdaCase]] syntax extension to `haskell-indentation` |
||||
|
||||
- Change `haskell-indentation-mode' to never jump back a whole line |
||||
when pressing DEL. The old behavior can be restored by setting |
||||
`haskell-indentation-delete-backward-jump-line' to t |
||||
|
||||
- New convenience function `haskell-cabal-visit-file' for locating and |
||||
visiting most likely `.cabal` file associated with current buffer |
||||
|
||||
- Add support for [[http://www.haskell.org/ghc/docs/latest/html/users_guide/syntax-extns.html#package-import][PackageImports]] and [[http://www.haskell.org/ghc/docs/latest/html/users_guide/syntax-extns.html#safe-imports-ext][SafeHaskell]] syntax extensions to |
||||
`haskell-decl-scan-mode' parser |
||||
|
||||
- Add `turn-{on,off}-haskell-doc' commands as aliases for the existing |
||||
`turn-{on,off}-haskell-doc-mode' commands |
||||
|
||||
- Add support for "cabal repl" process type to `haskell-interactive-mode' |
||||
|
||||
- Add new Haskell compilation sub-mode and associated `haskell-compile' |
||||
command |
||||
|
||||
* Changes in 13.7 |
||||
|
||||
See also [[https://github.com/haskell/haskell-mode/compare/v13.06...v13.07][detailed Git history]]. |
||||
|
||||
- Convert NEWS (this file) to Org mode style and include NEWS file in |
||||
package and add command for visiting NEWS file |
||||
(M-x haskell-mode-view-news) |
||||
|
||||
- Officially drop support for versions prior to Emacs 23 |
||||
|
||||
- New work-in-progress Info manual for haskell-mode |
||||
|
||||
- Remove deprecated `haskell-{hugs,ghci}' modules |
||||
|
||||
- Font-locking changes: |
||||
- Remove deprecated `turn-on-haskell-font-lock` function |
||||
- Improve font-locking of type-signatures in presence of newlines |
||||
- Use `font-lock-preprocessor-face' instead of the previously used |
||||
`font-lock-warning-face` for CPP directives |
||||
- Use `font-lock-warning-face` instead of the previously used |
||||
`font-lock-preprocessor-face` for Git merge conflict annotations. |
||||
|
||||
- Improvements to `haskell-move-nested' module: |
||||
- Add support for operating on active regions |
||||
- New interactive commands `haskell-move-nested-{left,right}` which |
||||
support numeric prefix arguments for controlling the amount of |
||||
shifting to apply. |
||||
|
||||
- Add `haskell-unicode-input-method.el` to distribution |
||||
(enable with `turn-on-haskell-unicode-input-method`) |
||||
|
||||
- Fix all byte-compilation warnings |
||||
|
||||
- Build-system: |
||||
- For in-place installation, `haskell-site-file.el' is renamed |
||||
to `haskell-mode-autoloads.el` |
||||
- Auto-generate ELPA compatible README file by extracting header of |
||||
haskell-mode.el |
||||
- New "make check" target |
||||
- Add Travis-CI build jobs for testing byte-compilation with |
||||
multiple Emacs versions |
||||
|
||||
- Reorganize customize settings |
||||
- Add new convenience function for browsing all Haskell Mode settings |
||||
(M-x haskell-customize) |
||||
- Add `:link' keywords pointing to the new Info manual |
||||
- Add `:group' keywords to modes to make (M-x customize-mode) work |
||||
- Create new customization groups `haskell-interactive' and `inferior-haskell' |
||||
to clean up namespace |
||||
- Create new customization group `ghc-core` containing the two new |
||||
customization variables `ghc-core-program` and `ghc-core-program-args`. |
||||
|
||||
- Improvements to haskell-interactive-mode |
||||
- Add support for deleting compile messages superseded by recompile/reloads |
||||
(M-x customize-variable RET haskell-interactive-mode-delete-superseded-errors) |
||||
- Fix `C-u M-x haskell-process-do-type` inserting bad signatures |
||||
- Integrate with Emacs' `next-error` subsystem |
||||
- Add "C-c C-f" binding to REPL keymap for enabling `next-error-follow-minor-mode' |
||||
- Add support for `-ferror-spans`-style compile messages |
||||
- Add `-ferror-spans` as default for `haskell-process-args-ghci` |
||||
- Add optional argument to |
||||
`haskell-session-{all,installed,project}-modules' to suppress |
||||
session-creation. This is useful for yasnippet usage, see commit |
||||
517fd7e for an example. |
||||
- Change default for `haskell-process-path-ghci` to a static "ghci" |
||||
- Fix `haskell-interactive-switch` not selecting the REPL window |
||||
- Make `*haskell-process-log*` buffer configurable |
||||
(controlled via new `haskell-process-log` customize option) |
||||
|
||||
* Changes in 13.6 |
||||
|
||||
See also [[https://github.com/haskell/haskell-mode/compare/2_9_1...v13.06][detailed Git history]]. |
||||
|
||||
- Switch to new versioning scheme |
||||
|
||||
- Switch to MELPA/Marmalade based packaging |
||||
|
||||
- Cleanup/refactor build-system |
||||
|
||||
- Enhance `M-x haskell-version` to report more detailed versioning |
||||
information |
||||
|
||||
- Make haskell-interactive-mode emulate comint/eshell history navigation |
||||
(see commit 0e96843 for more details) |
||||
|
||||
- Improvements to haskell-interactive-mode |
||||
- Improve killing/restarting haskell-interactive sessions |
||||
- Improve directory prompting and resolution |
||||
- Fix redundant-import suggest trigger to support qualified imports |
||||
- Detect all abbreviations of an user-inputted ":quit" |
||||
- Fix regexps for recent GHC 7.x compiler messages |
||||
- Customizable commandline args for GHCi |
||||
(M-x customize-variable RET haskell-process-args-ghci) |
||||
- New command to load or reload via prefix argument |
||||
(M-x haskell-process-load-or-reload) |
||||
- Fix haskell-interactive-mode prompt detection |
||||
- Add cabal-ghci as supported process mode |
||||
- Add a customization option for the visibility of multi-line errors |
||||
(M-x customize-variable RET haskell-interactive-mode-hide-multi-line-errors) |
||||
|
||||
- Add forward declarations to reduce Elisp bytecompile warnings |
||||
|
||||
- Improvements to `haskell-indentation` |
||||
- Add support for the UnicodeSyntax tokens `→`, `←`, and `∷`. |
||||
- Indent "=" following data/type/newtype declarations. |
||||
- Align "->"/"→" arrows in types under "::"/"∷" |
||||
- Make customizable whether "<backspace>" deletes indentation too |
||||
(via `haskell-indentation-delete-backward-indentation` and |
||||
`haskell-indentation-delete-indentation`) |
||||
- Properly indent 'rec' keyword, same as 'mdo' |
||||
- Minor optimizations. |
||||
|
||||
- Add support for "'"-prefixed constructors (-> DataKinds) to font-locking |
||||
|
||||
- New experimental haskell session menu mode (M-x haskell-menu) |
||||
|
||||
- Various minor cleanups/fixes/improvements... |
||||
|
||||
* Changes in 2.9.1 |
||||
|
||||
See also [[https://github.com/haskell/haskell-mode/compare/2_9_0...2_9_1][detailed Git history]]. |
||||
|
||||
- Bugfix release adding missing autoload declaration |
||||
|
||||
* Changes in 2.9.0 |
||||
|
||||
See also [[https://github.com/haskell/haskell-mode/compare/2_8_0...2_9_0][detailed Git history]]. |
||||
|
||||
- This is the first release after haskell-mode was migrated to GitHub |
||||
|
||||
- New experimental `haskell-interactive-mode' module implementing a |
||||
new REPL interaction mode for GHCi sessions to eventually replace |
||||
the existing "inf-haskell" mode. |
||||
|
||||
- New `haskell-process-cabal' command for interaction with cabal-install |
||||
|
||||
- New `haskell-checkers' module |
||||
|
||||
- Update haskell-cabal-mode font-lock keywords |
||||
|
||||
- Improve scrolling of hoogle output (haskell-mode.el) |
||||
|
||||
- Derive `haskell-mode` from `prog-mode` for Emacs 24+ |
||||
|
||||
- Add new binding for "<backtab>" to haskell-mode's keymap which |
||||
unindents current line |
||||
|
||||
- New modules `haskell-navigate-imports`, `haskell-sort-imports' and |
||||
`haskell-align-imports' for operating on module import lines in |
||||
Haskell source code |
||||
|
||||
- Add new binding for "C-c C-." to haskell-mode's keymap to sort and |
||||
realign Haskell module imports |
||||
|
||||
- Add new binding for "C-c i" to haskell-mode's keymap to jump back and |
||||
forth from/to the current Haskell module's module import section. |
||||
|
||||
- New `inferior-haskell-kind' function for querying kind via GHCi's ":kind" |
||||
|
||||
- New `inferior-haskell-send-decl' for sending declarations to GHCi |
||||
(bound to "C-x C-d" by default) |
||||
|
||||
- Add new `haskell-doc-use-inf-haskell` customization variable |
||||
|
||||
- Add support for bird-style literate haskell editing and a new |
||||
related customization variable |
||||
`haskell-indentation-birdtrack-extra-space' |
||||
|
||||
- Font locking improvements |
||||
- Add support for Git's merge annotation |
||||
(with `font-lock-preprocessor-face') |
||||
- Improve `import', `foreign import' and `foreign export' font |
||||
locking |
||||
- Add support for `rec', `proc' and `mdo` as keywords |
||||
- Make whitespace within `-- |' and `{- |' optional when possible |
||||
|
||||
- New `haskell-move-nested` module providing utilities for |
||||
interactively {in,de}denting nested "hanging" blocks. |
||||
|
||||
- Add stylish-haskell support |
||||
(enable via `haskell-stylish-on-save` customization variable) |
||||
|
||||
- Add support for generating tags on save |
||||
(enable via `haskell-tags-on-save' customization variable) |
||||
|
||||
- Set sensible dabbrev defaults in haskell-mode |
||||
|
||||
- Added `SCC` pragma insert/delete commands |
||||
(`haskell-mode-insert-scc-at-point` and `haskell-mode-kill-scc-at-point') |
||||
|
||||
- New experimental `haskell-mode-contextual-space' command |
||||
|
||||
- And a couple more cleanups/fixes/improvements... |
||||
|
||||
* Changes in 2.8.0 (since 2.7.0) |
||||
|
||||
See also [[https://github.com/haskell/haskell-mode/compare/2_7_0...2_8_0][detailed Git history]]. |
||||
|
||||
- Minimal indentation support for arrow syntax |
||||
|
||||
- Avoid opening a new inf-haskell window if one is already visible. |
||||
Windows on other virtual desktops or iconified frames don't count. |
||||
|
||||
- Force comint-process-echoes to nil |
||||
|
||||
- Autolaunch haskell-mode for files starting with #!/usr/bin/runghc |
||||
and similar |
||||
|
||||
- Added minimal major mode for parsing GHC core files, courtesy of Johan Tibell. |
||||
There is a corresponding Haskell menu entry. |
||||
|
||||
- Allow configuration of where-clause indentation; M-x customize-group |
||||
haskell-indentation. |
||||
|
||||
* Changes since 2.6.4 |
||||
|
||||
- fill-paragraph (M-q) now only affects comments, and correctly |
||||
handles Haddock commentary. adaptive-fill-mode is turned off, as it |
||||
was interfering. |
||||
|
||||
- Yet more unicode symbols |
||||
|
||||
- Better support for unicode encoding of haskell source files |
||||
|
||||
- mdo correctly indented |
||||
|
||||
- Indentation fixes, fixes to the fixes, and fixes to the fixes to the |
||||
fixes |
||||
|
||||
- New command: M-x haskell-check, calls (by default) hlint on the |
||||
current file. Also bound to C-c C-v. |
||||
|
||||
You can also use the flymake minor mode with this. |
||||
|
||||
* Changes since 2.5.1 |
||||
|
||||
- Parser corrections for haskell-indentation and haskell-decl-scan |
||||
|
||||
- haskell-indentation: Pressing tab in the rightmost position now |
||||
moves to the leftmost, by default with a warning. |
||||
|
||||
- Typo fix: One haskell-indentation variable had ended up in the |
||||
haskell-ntation customize group. |
||||
|
||||
- haskell-hoogle aliased to hoogle, haskell-hayoo aliased to hayoo |
||||
|
||||
- Courtesy of Alex Ott: |
||||
- Additional unicode symbols for font-lock-symbols: () == /= >= <= !! && || sqrt |
||||
- M-x haskell-hayoo search added, opens using browse-url |
||||
- Bug-fix for inferior-haskell-type |
||||
|
||||
- If haskell-indentation errors out, it now fail-safes to inserting |
||||
a literal newline or deleting one character, for return and |
||||
backspace respectively. |
||||
|
||||
* Changes since 2.4: |
||||
|
||||
- haskell-indentation, a new minor mode for indentation. |
||||
|
||||
* Changes since 2.3: |
||||
|
||||
- Update license to GPLv3. |
||||
|
||||
- New derived major mode for .hsc files. |
||||
|
||||
- Removed the C-c C-r binding to reload a file. You can still call |
||||
inferior-haskell-reload-file (and/or bind it to your favorite key, |
||||
including C-c C-r) or you can now use C-u C-c C-l. |
||||
|
||||
- C-c C-d looks up the symbol at point in the Haddock docs. |
||||
|
||||
- Haddock comments are highlighted with font-lock-doc-face if it exists. |
||||
|
||||
- Use `tex' rather than `latex' for haskell-literate. |
||||
|
||||
- inf-haskell.el tries to find the root of the module hierarchy to determine |
||||
the root of a project (either by looking for a Cabal file or relying on |
||||
the `module' declaration line). If all works well, this will make C-c C-l |
||||
automatically switch to the root dir, so that dependencies in other |
||||
directories are automatically found. If it doesn't, complain and/or set |
||||
inferior-haskell-find-project-root to nil. |
||||
|
||||
- The new command haskell-hoogle helps you query Hoogle from Emacs. |
||||
|
||||
* Changes since 2.2: |
||||
|
||||
- Trivial support for Cabal package description files. |
||||
|
||||
- Minor bug fixes. |
||||
|
||||
* Changes since 2.1: |
||||
|
||||
- There are now commands to find type and info of identifiers by querying an |
||||
inferior haskell process. Available under C-c C-t, C-c C-i, and C-c M-. |
||||
|
||||
- Indentation now looks back further, until a line that has no indentation. |
||||
To recover the earlier behavior of stopping at the first empty line |
||||
instead, configure haskell-indent-look-past-empty-line. |
||||
|
||||
- inf-haskell can wait until a file load completes and jump directly to the |
||||
first error, like haskell-ghci and haskell-hugs used to do. See the var |
||||
inferior-haskell-wait-and-jump. |
||||
|
||||
* Changes since 2.0: |
||||
|
||||
- inf-haskell uses ghci if hugs is absent. |
||||
|
||||
- Fix up some binding conflicts (C-c C-o in haskell-doc) |
||||
|
||||
- Many (hopefully minor) changes to the indentation. |
||||
|
||||
- New symbols in haskell-font-lock-symbols-alist. |
||||
|
||||
* Changes since 1.45: |
||||
|
||||
- keybindings C-c <char> have been replaced by C-c C-<char> so as not |
||||
to collide with minor modes. |
||||
|
||||
- The following modules are now automatically activated without having to |
||||
add anything to haskell-mode-hook: |
||||
haskell-font-lock (just turn on global-font-lock-mode). |
||||
haskell-decl-scan (just bind `imenu' to some key). |
||||
|
||||
- In recent Emacsen, haskell-doc hooks into eldoc-mode. |
||||
|
||||
- haskell-hugs and haskell-ghci are superceded by inf-haskell. |
||||
|
||||
- Indentation rules have been improved when using layout inside parens/braces. |
||||
|
||||
- Symbols like -> and \ can be displayed as actual arrows and lambdas. |
||||
See haskell-font-lock-symbols. |
||||
|
||||
- Tweaks to the font-lock settings. Among other things paren-matching |
||||
with things like \(x,y) should work correctly now. |
||||
|
||||
- New maintainer <monnier@gnu.org>. |
@ -1,125 +0,0 @@
|
||||
;;; ghc-core.el --- Syntax highlighting module for GHC Core -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright (C) 2010 Johan Tibell |
||||
|
||||
;; Author: Johan Tibell <johan.tibell@gmail.com> |
||||
|
||||
;; 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: |
||||
|
||||
;; Purpose: |
||||
;; |
||||
;; To make it easier to read GHC Core output by providing highlighting |
||||
;; and removal of commonly ignored annotations. |
||||
|
||||
;;; Code: |
||||
(require 'haskell-mode) |
||||
(require 'haskell-font-lock) |
||||
|
||||
;;;###autoload |
||||
(defgroup ghc-core nil |
||||
"Major mode for viewing pretty printed GHC Core output." |
||||
:link '(custom-manual "(haskell-mode)") |
||||
:group 'haskell |
||||
:prefix "ghc-core-") |
||||
|
||||
;;;###autoload |
||||
(defcustom ghc-core-program |
||||
"ghc" |
||||
"Name of the GHC executable (excluding any arguments)." |
||||
:type 'string |
||||
:group 'ghc-core) |
||||
|
||||
;;;###autoload |
||||
(defcustom ghc-core-program-args |
||||
'("-O2") |
||||
"Additional options to be passed to GHC when generating core output. |
||||
GHC (see variable `ghc-core-program') is invoked with the basic |
||||
command line options \"-ddump-simpl -c <source-file>\" |
||||
followed by the additional options defined here. |
||||
|
||||
The following `-ddump-simpl` options might be of interest: |
||||
|
||||
- `-dsuppress-all' |
||||
- `-dsuppress-uniques' |
||||
- `-dsuppress-idinfo' |
||||
- `-dsuppress-module-prefixes' |
||||
- `-dsuppress-type-signatures' |
||||
- `-dsuppress-type-applications' |
||||
- `-dsuppress-coercions' |
||||
|
||||
See `M-x manual-entry RET ghc' for more details." |
||||
:type '(repeat (string :tag "Argument")) |
||||
:group 'ghc-core) |
||||
|
||||
(define-obsolete-variable-alias 'ghc-core-create-options 'ghc-core-program-args |
||||
"haskell-mode 13.7") |
||||
|
||||
(defun ghc-core-clean-region (start end) |
||||
"Remove commonly ignored annotations and namespace prefixes |
||||
in the region between START and END." |
||||
(interactive "r") |
||||
(save-restriction |
||||
(narrow-to-region start end) |
||||
(goto-char (point-min)) |
||||
(while (search-forward-regexp "GHC\.[^\.]*\." nil t) |
||||
(replace-match "" nil t)) |
||||
(goto-char (point-min)) |
||||
(while (flush-lines "^ *GblId *$" nil)) |
||||
(goto-char (point-min)) |
||||
(while (flush-lines "^ *LclId *$" nil)) |
||||
(goto-char (point-min)) |
||||
(while (flush-lines (concat "^ *\\[\\(?:Arity [0-9]+\\|NoCafRefs\\|" |
||||
"Str: DmdType\\|Worker \\)" |
||||
"\\([^]]*\\n?\\).*\\] *$") nil)) |
||||
(goto-char (point-min)) |
||||
(while (search-forward "Main." nil t) (replace-match "" nil t)))) |
||||
|
||||
(defun ghc-core-clean-buffer () |
||||
"Remove commonly ignored annotations and namespace prefixes |
||||
in the current buffer." |
||||
(interactive) |
||||
(ghc-core-clean-region (point-min) (point-max))) |
||||
|
||||
;;;###autoload |
||||
(defun ghc-core-create-core () |
||||
"Compile and load the current buffer as tidy core." |
||||
(interactive) |
||||
(save-buffer) |
||||
(let* ((core-buffer (generate-new-buffer "ghc-core")) |
||||
(neh (lambda () (kill-buffer core-buffer)))) |
||||
(add-hook 'next-error-hook neh) |
||||
(apply #'call-process ghc-core-program nil core-buffer nil |
||||
"-ddump-simpl" "-c" (buffer-file-name) ghc-core-program-args) |
||||
(display-buffer core-buffer) |
||||
(with-current-buffer core-buffer |
||||
(ghc-core-mode)) |
||||
(remove-hook 'next-error-hook neh))) |
||||
|
||||
;;;###autoload |
||||
(add-to-list 'auto-mode-alist '("\\.hcr\\'" . ghc-core-mode)) |
||||
;;;###autoload |
||||
(add-to-list 'auto-mode-alist '("\\.dump-simpl\\'" . ghc-core-mode)) |
||||
|
||||
;;;###autoload |
||||
(define-derived-mode ghc-core-mode haskell-mode "GHC-Core" |
||||
"Major mode for GHC Core files.") |
||||
|
||||
(provide 'ghc-core) |
||||
;;; ghc-core.el ends here |
@ -1,68 +0,0 @@
|
||||
;;; ghci-script-mode.el --- GHCi scripts major mode -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright (c) 2014 Chris Done. All rights reserved. |
||||
|
||||
;; This file is free software; you can redistribute it and/or modify |
||||
;; it under the terms of the GNU General Public License as published by |
||||
;; the Free Software Foundation; either version 3, or (at your option) |
||||
;; any later version. |
||||
|
||||
;; This file is distributed in the hope that it will be useful, |
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
;; GNU General Public License for more details. |
||||
|
||||
;; You should have received a copy of the GNU General Public License |
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'haskell) |
||||
|
||||
(defvar ghci-script-mode-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)) |
||||
("^:[a-z{]+ *\\+" . font-lock-keyword-face) |
||||
("^:[a-z{]+ " . font-lock-keyword-face))) |
||||
|
||||
;;;###autoload |
||||
(define-derived-mode ghci-script-mode text-mode "GHCi-Script" |
||||
"Major mode for working with .ghci files." |
||||
(set (make-local-variable 'adaptive-fill-mode) nil) |
||||
(set (make-local-variable 'comment-start) "-- ") |
||||
(set (make-local-variable 'comment-padding) 0) |
||||
(set (make-local-variable 'comment-start-skip) "[-{]-[ \t]*") |
||||
(set (make-local-variable 'comment-end) "") |
||||
(set (make-local-variable 'comment-end-skip) "[ \t]*\\(-}\\|\\s>\\)") |
||||
(set (make-local-variable 'indent-line-function) 'haskell-mode-suggest-indent-choice) |
||||
(set (make-local-variable 'font-lock-defaults) |
||||
'(ghci-script-mode-keywords t t nil nil)) |
||||
(set (make-local-variable 'indent-tabs-mode) nil) |
||||
(set (make-local-variable 'tab-width) 8) |
||||
(when (boundp 'electric-indent-inhibit) |
||||
(setq electric-indent-inhibit t)) |
||||
(set (make-local-variable 'dabbrev-case-fold-search) nil) |
||||
(set (make-local-variable 'dabbrev-case-distinction) nil) |
||||
(set (make-local-variable 'dabbrev-case-replace) nil) |
||||
(set (make-local-variable 'dabbrev-abbrev-char-regexp) "\\sw\\|[.]") |
||||
(setq haskell-literate nil)) |
||||
|
||||
;;;###autoload |
||||
(add-to-list 'auto-mode-alist '("\\.ghci\\'" . ghci-script-mode)) |
||||
|
||||
(define-key ghci-script-mode-map (kbd "C-c C-l") 'ghci-script-mode-load) |
||||
|
||||
(defun ghci-script-mode-load () |
||||
"Load the current script file into the GHCi session." |
||||
(interactive) |
||||
(let ((buffer (haskell-session-interactive-buffer (haskell-session))) |
||||
(filename (buffer-file-name))) |
||||
(save-buffer) |
||||
(with-current-buffer buffer |
||||
(set-marker haskell-interactive-mode-prompt-start (point-max)) |
||||
(haskell-interactive-mode-run-expr |
||||
(concat ":script " filename))))) |
||||
|
||||
(provide 'ghci-script-mode) |
@ -1,231 +0,0 @@
|
||||
;;; 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.") |
||||
|
||||
;;;###autoload |
||||
(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)) |
||||
(not (not (search-forward-regexp "\\( = \\|\\<instance\\>\\| :: \\| ∷ \\)" |
||||
(line-end-position) t 1))))) |
||||
|
||||
(provide 'haskell-align-imports) |
||||
|
||||
;;; haskell-align-imports.el ends here |
@ -1,974 +0,0 @@
|
||||
;;; haskell-cabal.el --- Support for Cabal packages -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright (C) 2007, 2008 Stefan Monnier |
||||
|
||||
;; 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) |
||||
;; (with-no-warnings (require 'cl)) |
||||
;; (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 (remove-if-not 'consp (cddar xml))) |
||||
;; (syms (mapcar (lambda (entry) (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) |
||||
|
||||
(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")) |
||||
|
||||
(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) |
||||
(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\\)[ \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)))) |
||||
|
||||
(defvar haskell-cabal-buffers nil |
||||
"List of Cabal buffers.") |
||||
|
||||
(defun haskell-cabal-buffers-clean (&optional buffer) |
||||
(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 () |
||||
(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 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." |
||||
(set (make-local-variable '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) |
||||
(set (make-local-variable 'comment-start) "-- ") |
||||
(set (make-local-variable 'comment-start-skip) "\\(^[ \t]*\\)--[ \t]*") |
||||
(set (make-local-variable 'comment-end) "") |
||||
(set (make-local-variable 'comment-end-skip) "[ \t]*\\(\\s>\\|\n\\)") |
||||
(set (make-local-variable 'indent-line-function) 'haskell-cabal-indent-line) |
||||
(setq indent-tabs-mode nil) |
||||
) |
||||
|
||||
(defun haskell-cabal-get-setting (name) |
||||
(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))))) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-cabal-guess-setting (name) |
||||
"Guess the specified setting of this project. |
||||
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-setting 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 |
||||
) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-cabal-list-comma-position |
||||
'before |
||||
"Where to put the comma in lists" |
||||
:safe t |
||||
:group 'haskell-cabal |
||||
:type '(choice (const before) |
||||
(const after))) |
||||
|
||||
(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 extion" |
||||
(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 extion" |
||||
(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) |
||||
))))) |
||||
|
||||
|
||||
(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-enum-targets () |
||||
"Enumerate .cabal targets." |
||||
(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-mode) |
||||
(let (matches) |
||||
(goto-char (point-min)) |
||||
(haskell-cabal-next-section) |
||||
(while (not (eobp)) |
||||
(if (haskell-cabal-source-section-p (haskell-cabal-section)) |
||||
(let ((val (haskell-cabal-section-value (haskell-cabal-section)))) |
||||
(if (string= val "") |
||||
(push "library" matches) |
||||
(push 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 |
||||
resultung 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 FOMRS 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-strip-list () |
||||
"strip commas from comma-seperated list" |
||||
(goto-char (point-min)) |
||||
;; split list items on single line |
||||
(while (re-search-forward |
||||
"\\([^ \t,\n]\\)[ \t]*,[ \t]*\\([^ \t,\n]\\)" nil t) |
||||
(replace-match "\\1\n\\2" nil nil)) |
||||
(goto-char (point-min)) |
||||
(while (re-search-forward "^\\([ \t]*\\),\\([ \t]*\\)" nil t) |
||||
(replace-match "" nil nil)) |
||||
(goto-char (point-min)) |
||||
(while (re-search-forward ",[ \t]*$" nil t) |
||||
(replace-match "" nil nil)) |
||||
(goto-char (point-min)) |
||||
(haskell-cabal-each-line (haskell-cabal-chomp-line))) |
||||
|
||||
(defun haskell-cabal-listify () |
||||
"Add commas so that buffer contains a comma-seperated list" |
||||
(cl-case haskell-cabal-list-comma-position |
||||
('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 (not (bobp)) |
||||
(unless (haskell-cabal-ignore-line-p) |
||||
(forward-line -1) |
||||
(end-of-line) |
||||
(insert ",") |
||||
(beginning-of-line)))))) |
||||
|
||||
|
||||
|
||||
(defmacro haskell-cabal-with-cs-list (&rest funs) |
||||
"format buffer so that each line contains a list element " |
||||
`(progn |
||||
(save-excursion (haskell-cabal-strip-list)) |
||||
(unwind-protect (progn ,@funs) |
||||
(haskell-cabal-listify)))) |
||||
|
||||
|
||||
(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-subsection-arrange-lines () |
||||
"Sort lines of current subsection" |
||||
(interactive) |
||||
(haskell-cabal-save-position |
||||
(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) |
||||
)))) |
||||
|
||||
(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 beginniing 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 next 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 sunsection 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 sunsection 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 and " |
||||
`(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) |
||||
(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 ))) |
||||
(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-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-start-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 a build dependencies to sections" |
||||
(haskell-cabal-map-sections |
||||
(lambda (section) |
||||
(when (haskell-cabal-source-section-p section) |
||||
(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))))) |
||||
|
||||
(defun haskell-cabal-add-dependency (package &optional version no-prompt |
||||
sort silent) |
||||
"Add PACKAGE (and optionally suffix -VERSION) to the cabal |
||||
file. Prompts the user before doing so. |
||||
|
||||
If VERSION is non-nil it will be appended as a minimum version. |
||||
If NO-PROMPT is nil the minimum-version is read from the minibuffer |
||||
When SORT is non-nil the package entries are sorted afterwards |
||||
If SILENT ist nil the user is prompted for each source-section |
||||
" |
||||
(interactive |
||||
(list (read-from-minibuffer "Package entry: ") |
||||
nil t t nil)) |
||||
(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))))) |
||||
|
||||
(provide 'haskell-cabal) |
||||
|
||||
;;; haskell-cabal.el ends here |
@ -1,184 +0,0 @@
|
||||
;;; haskell-checkers.el --- Emacs interface to haskell lint and style checkers -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright (C) 2009-2011 Alex Ott, Liam O'Reilly |
||||
;; |
||||
;; Author: Alex Ott <alexott@gmail.com>, Liam O'Reilly <csliam@swansea.ac.uk> |
||||
;; Keywords: haskell, lint, hlint, style scanner |
||||
;; Requirements: hlint, scan, haskell |
||||
|
||||
;; 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 2 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: |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'compile) |
||||
|
||||
;;;###autoload |
||||
(defgroup haskell-checkers nil |
||||
"Run HLint as inferior of Emacs, parse error messages." |
||||
:group 'haskell) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-lint-command "hlint" |
||||
"The default lint command for \\[hlint]." |
||||
:type 'string |
||||
:group 'haskell-checkers) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-scan-command "scan" |
||||
"The default scan command for \\[haskell-scan]." |
||||
:type 'string |
||||
:group 'haskell-checkers) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-scan-options "" |
||||
"The default options for \\[haskell-scan]." |
||||
:type 'string |
||||
:group 'haskell-checkers) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-lint-options "" |
||||
"The default options for \\[hlint]." |
||||
:type 'string |
||||
:group 'haskell-checkers) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-checkers-save-files t |
||||
"Save modified files when run checker or not (ask user)" |
||||
:type 'boolean |
||||
:group 'haskell-checkers) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-checkers-replace-with-suggestions nil |
||||
"Replace user's code with suggested replacements (hlint only)" |
||||
:type 'boolean |
||||
:group 'haskell-checkers) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-checkers-replace-without-ask nil |
||||
"Replace user's code with suggested replacements automatically (hlint only)" |
||||
:type 'boolean |
||||
:group 'haskell-checkers) |
||||
|
||||
;; regex for replace HLint's suggestions |
||||
;; |
||||
;; ^\(.*?\):\([0-9]+\):\([0-9]+\): .* |
||||
;; Found: |
||||
;; \s +\(.*\) |
||||
;; Why not: |
||||
;; \s +\(.*\) |
||||
|
||||
(defvar haskell-lint-regex |
||||
"^\\(.*?\\):\\([0-9]+\\):\\([0-9]+\\): .*[\n\C-m]Found:[\n\C-m]\\s +\\(.*\\)[\n\C-m]Why not:[\n\C-m]\\s +\\(.*\\)[\n\C-m]" |
||||
"Regex for HLint messages") |
||||
|
||||
(defun haskell-checkers-make-short-string (str maxlen) |
||||
(if (< (length str) maxlen) |
||||
str |
||||
(concat (substring str 0 (- maxlen 3)) "..."))) |
||||
|
||||
;; TODO: check, is it possible to adopt it for haskell-scan? |
||||
(defun haskell-lint-replace-suggestions () |
||||
"Perform actual replacement of HLint's suggestions" |
||||
(goto-char (point-min)) |
||||
(while (re-search-forward haskell-lint-regex nil t) |
||||
(let* ((fname (match-string 1)) |
||||
(fline (string-to-number (match-string 2))) |
||||
(old-code (match-string 4)) |
||||
(new-code (match-string 5)) |
||||
(msg (concat "Replace '" (haskell-checkers-make-short-string old-code 30) |
||||
"' with '" (haskell-checkers-make-short-string new-code 30) "'")) |
||||
(bline 0) |
||||
(eline 0) |
||||
(spos 0) |
||||
(new-old-code "")) |
||||
(save-excursion |
||||
(switch-to-buffer (get-file-buffer fname)) |
||||
(goto-char (point-min)) |
||||
(forward-line (1- fline)) |
||||
(beginning-of-line) |
||||
(setq bline (point)) |
||||
(when (or haskell-checkers-replace-without-ask |
||||
(yes-or-no-p msg)) |
||||
(end-of-line) |
||||
(setq eline (point)) |
||||
(beginning-of-line) |
||||
(setq old-code (regexp-quote old-code)) |
||||
(while (string-match "\\\\ " old-code spos) |
||||
(setq new-old-code (concat new-old-code |
||||
(substring old-code spos (match-beginning 0)) |
||||
"\\ *")) |
||||
(setq spos (match-end 0))) |
||||
(setq new-old-code (concat new-old-code (substring old-code spos))) |
||||
(remove-text-properties bline eline '(composition nil)) |
||||
(when (re-search-forward new-old-code eline t) |
||||
(replace-match new-code nil t))))))) |
||||
|
||||
(defun haskell-lint-finish-hook (_buf _msg) |
||||
"Function, that is executed at the end of HLint or scan execution" |
||||
(if haskell-checkers-replace-with-suggestions |
||||
(haskell-lint-replace-suggestions) |
||||
(next-error 1 t))) |
||||
|
||||
(defun haskell-scan-finish-hook (_buf _msg) |
||||
"Function, that is executed at the end of haskell-scan execution" |
||||
(next-error 1 t)) |
||||
|
||||
(defun haskell-scan-make-command (file) |
||||
"Generates command line for scan" |
||||
(concat haskell-scan-command " " haskell-scan-options " \"" file "\"")) |
||||
|
||||
(defun haskell-lint-make-command (file) |
||||
"Generates command line for scan" |
||||
(concat haskell-lint-command " \"/" file "/\"" " " haskell-lint-options)) |
||||
|
||||
(defmacro haskell-checkers-setup (type name) |
||||
"Performs setup of corresponding checker. Receives two arguments: |
||||
type - checker's type (lint or scan) that is expanded into functions and hooks names |
||||
name - user visible name for this mode" |
||||
(let ((nm (concat "haskell-" (symbol-name type)))) |
||||
`(progn |
||||
;;;###autoload |
||||
(defvar ,(intern (concat nm "-setup-hook")) nil |
||||
,(concat "Hook, that will executed before running " name)) |
||||
(defun ,(intern (concat nm "-process-setup")) () |
||||
"Setup compilation variables and buffer for `hlint'." |
||||
(run-hooks ',(intern (concat nm "-setup-hook")))) |
||||
;;;###autoload |
||||
(define-compilation-mode ,(intern (concat nm "-mode")) ,name |
||||
,(concat "Mode to check Haskell source code using " name) |
||||
(set (make-local-variable 'compilation-process-setup-function) |
||||
',(intern (concat nm "-process-setup"))) |
||||
(set (make-local-variable 'compilation-disable-input) t) |
||||
(set (make-local-variable 'compilation-scroll-output) nil) |
||||
(set (make-local-variable 'compilation-finish-functions) |
||||
(list ',(intern (concat nm "-finish-hook"))))) |
||||
;;;###autoload |
||||
(defun ,(intern nm) () |
||||
,(concat "Run " name " for current buffer with haskell source") |
||||
(interactive) |
||||
(save-some-buffers haskell-checkers-save-files) |
||||
(compilation-start (,(intern (concat nm "-make-command")) buffer-file-name) |
||||
',(intern (concat nm "-mode"))))) |
||||
)) |
||||
|
||||
(haskell-checkers-setup lint "HLint") |
||||
(haskell-checkers-setup scan "HScan") |
||||
|
||||
(provide 'haskell-checkers) |
||||
|
||||
;;; haskell-checkers.el ends here |
@ -1,65 +0,0 @@
|
||||
;;; haskell-collapse.el --- Collapse expressions -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright (c) 2014 Chris Done. All rights reserved. |
||||
|
||||
;; This file is free software; you can redistribute it and/or modify |
||||
;; it under the terms of the GNU General Public License as published by |
||||
;; the Free Software Foundation; either version 3, or (at your option) |
||||
;; any later version. |
||||
|
||||
;; This file is distributed in the hope that it will be useful, |
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
;; GNU General Public License for more details. |
||||
|
||||
;; You should have received a copy of the GNU General Public License |
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
;;; Code: |
||||
|
||||
(define-button-type 'haskell-collapse-toggle-button |
||||
'action 'haskell-collapse-toggle-button-callback |
||||
'follow-link t |
||||
'help-echo "Click to expand…") |
||||
|
||||
(defun haskell-collapse (beg end) |
||||
"Collapse." |
||||
(interactive "r") |
||||
(goto-char end) |
||||
(let ((break nil)) |
||||
(while (and (not break) |
||||
(search-backward-regexp "[[({]" beg t 1)) |
||||
(unless (elt (syntax-ppss) 3) |
||||
(let ((orig (point))) |
||||
(haskell-collapse-sexp) |
||||
(goto-char orig) |
||||
(forward-char -1) |
||||
(when (= (point) orig) |
||||
(setq break t))))))) |
||||
|
||||
(defun haskell-collapse-sexp () |
||||
"Collapse the sexp starting at point." |
||||
(let ((beg (point))) |
||||
(forward-sexp) |
||||
(let ((end (point))) |
||||
(let ((o (make-overlay beg end))) |
||||
(overlay-put o 'invisible t) |
||||
(let ((start (point))) |
||||
(insert "…") |
||||
(let ((button (make-text-button start (point) |
||||
:type 'haskell-collapse-toggle-button))) |
||||
(button-put button 'overlay o) |
||||
(button-put button 'hide-on-click t))))))) |
||||
|
||||
(defun haskell-collapse-toggle-button-callback (btn) |
||||
"The callback to toggle the overlay visibility." |
||||
(let ((overlay (button-get btn 'overlay))) |
||||
(when overlay |
||||
(overlay-put overlay |
||||
'invisible |
||||
(not (overlay-get overlay |
||||
'invisible))))) |
||||
(button-put btn 'invisible t) |
||||
(delete-region (button-start btn) (button-end btn))) |
||||
|
||||
(provide 'haskell-collapse) |
@ -1,944 +0,0 @@
|
||||
;;; haskell-commands.el --- Commands that can be run on the process -*- lexical-binding: t -*- |
||||
|
||||
;;; Commentary: |
||||
|
||||
;;; This module provides varoius `haskell-mode' and `haskell-interactive-mode' |
||||
;;; specific commands such as show type signature, show info, haskell process |
||||
;;; commands and etc. |
||||
|
||||
;; Copyright (c) 2014 Chris Done. All rights reserved. |
||||
|
||||
;; This file is free software; you can redistribute it and/or modify |
||||
;; it under the terms of the GNU General Public License as published by |
||||
;; the Free Software Foundation; either version 3, or (at your option) |
||||
;; any later version. |
||||
|
||||
;; This file is distributed in the hope that it will be useful, |
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
;; GNU General Public License for more details. |
||||
|
||||
;; You should have received a copy of the GNU General Public License |
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cl-lib) |
||||
(require 'etags) |
||||
(require 'haskell-compat) |
||||
(require 'haskell-process) |
||||
(require 'haskell-font-lock) |
||||
(require 'haskell-interactive-mode) |
||||
(require 'haskell-session) |
||||
(require 'haskell-presentation-mode) |
||||
(require 'haskell-utils) |
||||
(require 'highlight-uses-mode) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-process-restart () |
||||
"Restart the inferior Haskell process." |
||||
(interactive) |
||||
(haskell-process-reset (haskell-interactive-process)) |
||||
(haskell-process-set (haskell-interactive-process) 'command-queue nil) |
||||
(haskell-process-start (haskell-interactive-session))) |
||||
|
||||
(defun haskell-process-start (session) |
||||
"Start the inferior Haskell process with a given SESSION. |
||||
You can create new session using function `haskell-session-make'." |
||||
(let ((existing-process (get-process (haskell-session-name (haskell-interactive-session))))) |
||||
(when (processp existing-process) |
||||
(haskell-interactive-mode-echo session "Restarting process ...") |
||||
(haskell-process-set (haskell-session-process session) 'is-restarting t) |
||||
(delete-process existing-process))) |
||||
(let ((process (or (haskell-session-process session) |
||||
(haskell-process-make (haskell-session-name session)))) |
||||
(old-queue (haskell-process-get (haskell-session-process session) |
||||
'command-queue))) |
||||
(haskell-session-set-process session process) |
||||
(haskell-process-set-session process session) |
||||
(haskell-process-set-cmd process nil) |
||||
(haskell-process-set (haskell-session-process session) 'is-restarting nil) |
||||
(let ((default-directory (haskell-session-cabal-dir session)) |
||||
(log-and-command (haskell-process-compute-process-log-and-command session (haskell-process-type)))) |
||||
(haskell-session-prompt-set-current-dir session (not haskell-process-load-or-reload-prompt)) |
||||
(haskell-process-set-process |
||||
process |
||||
(progn |
||||
(haskell-process-log (propertize (format "%S" log-and-command))) |
||||
(apply #'start-process (cdr log-and-command))))) |
||||
(progn (set-process-sentinel (haskell-process-process process) 'haskell-process-sentinel) |
||||
(set-process-filter (haskell-process-process process) 'haskell-process-filter)) |
||||
(haskell-process-send-startup process) |
||||
(unless (or (eq 'cabal-repl (haskell-process-type)) |
||||
(eq 'stack-ghci (haskell-process-type))) ;; Both "cabal repl" and "stack ghci" set the proper CWD. |
||||
(haskell-process-change-dir session |
||||
process |
||||
(haskell-session-current-dir session))) |
||||
(haskell-process-set process 'command-queue |
||||
(append (haskell-process-get (haskell-session-process session) |
||||
'command-queue) |
||||
old-queue)) |
||||
process)) |
||||
|
||||
(defun haskell-process-send-startup (process) |
||||
"Send the necessary start messages to haskell PROCESS." |
||||
(haskell-process-queue-command |
||||
process |
||||
(make-haskell-command |
||||
:state process |
||||
|
||||
:go (lambda (process) |
||||
;; We must set the prompt last, so that this command as a |
||||
;; whole produces only one prompt marker as a response. |
||||
(haskell-process-send-string process "Prelude.putStrLn \"\"") |
||||
(haskell-process-send-string process ":set -v1") |
||||
(haskell-process-send-string process ":set prompt \"\\4\"")) |
||||
|
||||
:live (lambda (process buffer) |
||||
(when (haskell-process-consume |
||||
process |
||||
"^\*\*\* WARNING: \\(.+\\) is writable by someone else, IGNORING!$") |
||||
(let ((path (match-string 1 buffer))) |
||||
(haskell-session-modify |
||||
(haskell-process-session process) |
||||
'ignored-files |
||||
(lambda (files) |
||||
(cl-remove-duplicates (cons path files) :test 'string=))) |
||||
(haskell-interactive-mode-compile-warning |
||||
(haskell-process-session process) |
||||
(format "GHCi is ignoring: %s (run M-x haskell-process-unignore)" |
||||
path))))) |
||||
|
||||
:complete (lambda (process _) |
||||
(haskell-interactive-mode-echo |
||||
(haskell-process-session process) |
||||
(concat (nth (random (length haskell-process-greetings)) |
||||
haskell-process-greetings) |
||||
(when haskell-process-show-debug-tips |
||||
" |
||||
If I break, you can: |
||||
1. Restart: M-x haskell-process-restart |
||||
2. Configure logging: C-h v haskell-process-log (useful for debugging) |
||||
3. General config: M-x customize-mode |
||||
4. Hide these tips: C-h v haskell-process-show-debug-tips"))))))) |
||||
|
||||
(defun haskell-commands-process () |
||||
"Get the Haskell session, throws an error if not available." |
||||
(or (haskell-session-process (haskell-session-maybe)) |
||||
(error "No Haskell session/process associated with this |
||||
buffer. Maybe run M-x haskell-session-change?"))) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-process-clear () |
||||
"Clear the current process." |
||||
(interactive) |
||||
(haskell-process-reset (haskell-commands-process)) |
||||
(haskell-process-set (haskell-commands-process) 'command-queue nil)) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-process-interrupt () |
||||
"Interrupt the process (SIGINT)." |
||||
(interactive) |
||||
(interrupt-process (haskell-process-process (haskell-commands-process)))) |
||||
|
||||
(defun haskell-process-reload-with-fbytecode (process module-buffer) |
||||
"Query a PROCESS to reload MODULE-BUFFER with -fbyte-code set. |
||||
Restores -fobject-code after reload finished. |
||||
MODULE-BUFFER is the actual Emacs buffer of the module being loaded." |
||||
(haskell-process-queue-without-filters process ":set -fbyte-code") |
||||
;; We prefix the module's filename with a "*", which asks ghci to |
||||
;; ignore any existing object file and interpret the module. |
||||
;; Dependencies will still use their object files as usual. |
||||
(haskell-process-queue-without-filters |
||||
process |
||||
(format ":load \"*%s\"" |
||||
(replace-regexp-in-string |
||||
"\"" |
||||
"\\\\\"" |
||||
(buffer-file-name module-buffer)))) |
||||
(haskell-process-queue-without-filters process ":set -fobject-code")) |
||||
|
||||
(defvar url-http-response-status) |
||||
(defvar url-http-end-of-headers) |
||||
(defvar haskell-cabal-targets-history nil |
||||
"History list for session targets.") |
||||
|
||||
(defun haskell-process-hayoo-ident (ident) |
||||
;; FIXME Obsolete doc string, CALLBACK is not used. |
||||
"Hayoo for IDENT, return a list of modules asyncronously through CALLBACK." |
||||
;; We need a real/simulated closure, because otherwise these |
||||
;; variables will be unbound when the url-retrieve callback is |
||||
;; called. |
||||
;; TODO: Remove when this code is converted to lexical bindings by |
||||
;; default (Emacs 24.1+) |
||||
(let ((url (format haskell-process-hayoo-query-url (url-hexify-string ident)))) |
||||
(with-current-buffer (url-retrieve-synchronously url) |
||||
(if (= 200 url-http-response-status) |
||||
(progn |
||||
(goto-char url-http-end-of-headers) |
||||
(let* ((res (json-read)) |
||||
(results (assoc-default 'result res))) |
||||
;; TODO: gather packages as well, and when we choose a |
||||
;; given import, check that we have the package in the |
||||
;; cabal file as well. |
||||
(cl-mapcan (lambda (r) |
||||
;; append converts from vector -> list |
||||
(append (assoc-default 'resultModules r) nil)) |
||||
results))) |
||||
(warn "HTTP error %s fetching %s" url-http-response-status url))))) |
||||
|
||||
(defun haskell-process-hoogle-ident (ident) |
||||
"Hoogle for IDENT, return a list of modules." |
||||
(with-temp-buffer |
||||
(let ((hoogle-error (call-process "hoogle" nil t nil "search" "--exact" ident))) |
||||
(goto-char (point-min)) |
||||
(unless (or (/= 0 hoogle-error) |
||||
(looking-at "^No results found") |
||||
(looking-at "^package ")) |
||||
(while (re-search-forward "^\\([^ ]+\\).*$" nil t) |
||||
(replace-match "\\1" nil nil)) |
||||
(cl-remove-if (lambda (a) (string= "" a)) |
||||
(split-string (buffer-string) |
||||
"\n")))))) |
||||
|
||||
(defun haskell-process-haskell-docs-ident (ident) |
||||
"Search with haskell-docs for IDENT, return a list of modules." |
||||
(cl-remove-if-not |
||||
(lambda (a) (string-match "^[[:upper:]][[:alnum:]_'.]+$" a)) |
||||
(split-string |
||||
(with-output-to-string |
||||
(with-current-buffer |
||||
standard-output |
||||
(call-process "haskell-docs" |
||||
nil ; no infile |
||||
t ; output to current buffer (that is string) |
||||
nil ; do not redisplay |
||||
"--modules" ident))) |
||||
"\n"))) |
||||
|
||||
(defun haskell-process-import-modules (process modules) |
||||
"Query PROCESS `:m +' command to import MODULES." |
||||
(when haskell-process-auto-import-loaded-modules |
||||
(haskell-process-queue-command |
||||
process |
||||
(make-haskell-command |
||||
:state (cons process modules) |
||||
:go (lambda (state) |
||||
(haskell-process-send-string |
||||
(car state) |
||||
(format ":m + %s" (mapconcat 'identity (cdr state) " ")))))))) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-describe (ident) |
||||
"Describe the given identifier IDENT." |
||||
(interactive (list (read-from-minibuffer "Describe identifier: " |
||||
(haskell-ident-at-point)))) |
||||
(let ((results (read (shell-command-to-string |
||||
(concat "haskell-docs --sexp " |
||||
ident))))) |
||||
(help-setup-xref (list #'haskell-describe ident) |
||||
(called-interactively-p 'interactive)) |
||||
(save-excursion |
||||
(with-help-window (help-buffer) |
||||
(with-current-buffer (help-buffer) |
||||
(if results |
||||
(cl-loop for result in results |
||||
do (insert (propertize ident 'font-lock-face |
||||
'((:inherit font-lock-type-face |
||||
:underline t))) |
||||
" is defined in " |
||||
(let ((module (cadr (assoc 'module result)))) |
||||
(if module |
||||
(concat module " ") |
||||
"")) |
||||
(cadr (assoc 'package result)) |
||||
"\n\n") |
||||
do (let ((type (cadr (assoc 'type result)))) |
||||
(when type |
||||
(insert (haskell-fontify-as-mode type 'haskell-mode) |
||||
"\n"))) |
||||
do (let ((args (cadr (assoc 'type results)))) |
||||
(cl-loop for arg in args |
||||
do (insert arg "\n")) |
||||
(insert "\n")) |
||||
do (insert (cadr (assoc 'documentation result))) |
||||
do (insert "\n\n")) |
||||
(insert "No results for " ident))))))) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-rgrep (&optional prompt) |
||||
"Grep the effective project for the symbol at point. |
||||
Very useful for codebase navigation. |
||||
|
||||
Prompts for an arbitrary regexp given a prefix arg PROMPT." |
||||
(interactive "P") |
||||
(let ((sym (if prompt |
||||
(read-from-minibuffer "Look for: ") |
||||
(haskell-ident-at-point)))) |
||||
(rgrep sym |
||||
"*.hs" ;; TODO: common Haskell extensions. |
||||
(haskell-session-current-dir (haskell-interactive-session))))) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-process-do-info (&optional prompt-value) |
||||
"Print info on the identifier at point. |
||||
If PROMPT-VALUE is non-nil, request identifier via mini-buffer." |
||||
(interactive "P") |
||||
(let ((at-point (haskell-ident-at-point))) |
||||
(when (or prompt-value at-point) |
||||
(let* ((ident (replace-regexp-in-string |
||||
"^!\\([A-Z_a-z]\\)" |
||||
"\\1" |
||||
(if prompt-value |
||||
(read-from-minibuffer "Info: " at-point) |
||||
at-point))) |
||||
(modname (unless prompt-value |
||||
(haskell-utils-parse-import-statement-at-point))) |
||||
(command (cond |
||||
(modname |
||||
(format ":browse! %s" modname)) |
||||
((string= ident "") ; For the minibuffer input case |
||||
nil) |
||||
(t (format (if (string-match "^[a-zA-Z_]" ident) |
||||
":info %s" |
||||
":info (%s)") |
||||
(or ident |
||||
at-point)))))) |
||||
(when command |
||||
(haskell-process-show-repl-response command)))))) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-process-do-type (&optional insert-value) |
||||
;; FIXME insert value functionallity seems to be missing. |
||||
"Print the type of the given expression. |
||||
|
||||
Given INSERT-VALUE prefix indicates that result type signature |
||||
should be inserted." |
||||
(interactive "P") |
||||
(if insert-value |
||||
(haskell-process-insert-type) |
||||
(let* ((expr |
||||
(if (use-region-p) |
||||
(buffer-substring-no-properties (region-beginning) (region-end)) |
||||
(haskell-ident-at-point))) |
||||
(expr-okay (and expr |
||||
(not (string-match-p "\\`[[:space:]]*\\'" expr)) |
||||
(not (string-match-p "\n" expr))))) |
||||
;; No newlines in expressions, and surround with parens if it |
||||
;; might be a slice expression |
||||
(when expr-okay |
||||
(haskell-process-show-repl-response |
||||
(format |
||||
(if (or (string-match-p "\\`(" expr) |
||||
(string-match-p "\\`[_[:alpha:]]" expr)) |
||||
":type %s" |
||||
":type (%s)") |
||||
expr)))))) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-mode-jump-to-def-or-tag (&optional _next-p) |
||||
;; FIXME NEXT-P arg is not used |
||||
"Jump to the definition. |
||||
Jump to definition of identifier at point by consulting GHCi, or |
||||
tag table as fallback. |
||||
|
||||
Remember: If GHCi is busy doing something, this will delay, but |
||||
it will always be accurate, in contrast to tags, which always |
||||
work but are not always accurate. |
||||
If the definition or tag is found, the location from which you jumped |
||||
will be pushed onto `xref--marker-ring', so you can return to that |
||||
position with `xref-pop-marker-stack'." |
||||
(interactive "P") |
||||
(let ((initial-loc (point-marker)) |
||||
(loc (haskell-mode-find-def (haskell-ident-at-point)))) |
||||
(if loc |
||||
(haskell-mode-handle-generic-loc loc) |
||||
(call-interactively 'haskell-mode-tag-find)) |
||||
(unless (equal initial-loc (point-marker)) |
||||
(xref-push-marker-stack initial-loc)))) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-mode-goto-loc () |
||||
"Go to the location of the thing at point. |
||||
Requires the :loc-at command from GHCi." |
||||
(interactive) |
||||
(let ((loc (haskell-mode-loc-at))) |
||||
(when loc |
||||
(haskell-mode-goto-span loc)))) |
||||
|
||||
(defun haskell-mode-goto-span (span) |
||||
"Jump to the SPAN, whatever file and line and column it needs to get there." |
||||
(xref-push-marker-stack) |
||||
(find-file (expand-file-name (plist-get span :path) |
||||
(haskell-session-cabal-dir (haskell-interactive-session)))) |
||||
(goto-char (point-min)) |
||||
(forward-line (1- (plist-get span :start-line))) |
||||
(forward-char (plist-get span :start-col))) |
||||
|
||||
(defun haskell-process-insert-type () |
||||
"Get the identifer at the point and insert its type. |
||||
Use GHCi's :type if it's possible." |
||||
(let ((ident (haskell-ident-at-point))) |
||||
(when ident |
||||
(let ((process (haskell-interactive-process)) |
||||
(query (format (if (string-match "^[_[:lower:][:upper:]]" ident) |
||||
":type %s" |
||||
":type (%s)") |
||||
ident))) |
||||
(haskell-process-queue-command |
||||
process |
||||
(make-haskell-command |
||||
:state (list process query (current-buffer)) |
||||
:go (lambda (state) |
||||
(haskell-process-send-string (nth 0 state) |
||||
(nth 1 state))) |
||||
:complete (lambda (state response) |
||||
(cond |
||||
;; TODO: Generalize this into a function. |
||||
((or (string-match "^Top level" response) |
||||
(string-match "^<interactive>" response)) |
||||
(message response)) |
||||
(t |
||||
(with-current-buffer (nth 2 state) |
||||
(goto-char (line-beginning-position)) |
||||
(insert (format "%s\n" (replace-regexp-in-string "\n$" "" response))))))))))))) |
||||
|
||||
(defun haskell-mode-find-def (ident) |
||||
;; TODO Check if it possible to exploit `haskell-process-do-info' |
||||
"Find definition location of identifier IDENT. |
||||
Uses the GHCi process to find the location. Returns nil if it |
||||
can't find the identifier or the identifier isn't a string. |
||||
|
||||
Returns: |
||||
|
||||
(library <package> <module>) |
||||
(file <path> <line> <col>) |
||||
(module <name>) |
||||
nil" |
||||
(when (stringp ident) |
||||
(let ((reply (haskell-process-queue-sync-request |
||||
(haskell-interactive-process) |
||||
(format (if (string-match "^[a-zA-Z_]" ident) |
||||
":info %s" |
||||
":info (%s)") |
||||
ident)))) |
||||
(let ((match (string-match "-- Defined \\(at\\|in\\) \\(.+\\)$" reply))) |
||||
(when match |
||||
(let ((defined (match-string 2 reply))) |
||||
(let ((match (string-match "\\(.+?\\):\\([0-9]+\\):\\([0-9]+\\)$" defined))) |
||||
(cond |
||||
(match |
||||
(list 'file |
||||
(expand-file-name (match-string 1 defined) |
||||
(haskell-session-current-dir (haskell-interactive-session))) |
||||
(string-to-number (match-string 2 defined)) |
||||
(string-to-number (match-string 3 defined)))) |
||||
(t |
||||
(let ((match (string-match "`\\(.+?\\):\\(.+?\\)'$" defined))) |
||||
(if match |
||||
(list 'library |
||||
(match-string 1 defined) |
||||
(match-string 2 defined)) |
||||
(let ((match (string-match "`\\(.+?\\)'$" defined))) |
||||
(if match |
||||
(list 'module |
||||
(match-string 1 defined))))))))))))))) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-mode-jump-to-def (ident) |
||||
"Jump to definition of identifier IDENT at point." |
||||
(interactive (list (haskell-ident-at-point))) |
||||
(let ((loc (haskell-mode-find-def ident))) |
||||
(when loc |
||||
(haskell-mode-handle-generic-loc loc)))) |
||||
|
||||
(defun haskell-mode-handle-generic-loc (loc) |
||||
"Either jump to or echo a generic location LOC. |
||||
Either a file or a library." |
||||
(cl-case (car loc) |
||||
(file (haskell-mode-jump-to-loc (cdr loc))) |
||||
(library (message "Defined in `%s' (%s)." |
||||
(elt loc 2) |
||||
(elt loc 1))) |
||||
(module (message "Defined in `%s'." |
||||
(elt loc 1))))) |
||||
|
||||
(defun haskell-mode-loc-at () |
||||
"Get the location at point. |
||||
Requires the :loc-at command from GHCi." |
||||
(let ((pos (or (when (region-active-p) |
||||
(cons (region-beginning) |
||||
(region-end))) |
||||
(haskell-spanable-pos-at-point) |
||||
(cons (point) |
||||
(point))))) |
||||
(when pos |
||||
(let ((reply (haskell-process-queue-sync-request |
||||
(haskell-interactive-process) |
||||
(save-excursion |
||||
(format ":loc-at %s %d %d %d %d %s" |
||||
(buffer-file-name) |
||||
(progn (goto-char (car pos)) |
||||
(line-number-at-pos)) |
||||
(1+ (current-column)) ;; GHC uses 1-based columns. |
||||
(progn (goto-char (cdr pos)) |
||||
(line-number-at-pos)) |
||||
(1+ (current-column)) ;; GHC uses 1-based columns. |
||||
(buffer-substring-no-properties (car pos) |
||||
(cdr pos))))))) |
||||
(if reply |
||||
(if (string-match "\\(.*?\\):(\\([0-9]+\\),\\([0-9]+\\))-(\\([0-9]+\\),\\([0-9]+\\))" |
||||
reply) |
||||
(list :path (match-string 1 reply) |
||||
:start-line (string-to-number (match-string 2 reply)) |
||||
;; ;; GHC uses 1-based columns. |
||||
:start-col (1- (string-to-number (match-string 3 reply))) |
||||
:end-line (string-to-number (match-string 4 reply)) |
||||
;; GHC uses 1-based columns. |
||||
:end-col (1- (string-to-number (match-string 5 reply)))) |
||||
(error (propertize reply 'face 'compilation-error))) |
||||
(error (propertize "No reply. Is :loc-at supported?" |
||||
'face 'compilation-error))))))) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-process-cd (&optional _not-interactive) |
||||
;; FIXME optional arg is not used |
||||
"Change directory." |
||||
(interactive) |
||||
(let* ((session (haskell-interactive-session)) |
||||
(dir (haskell-session-prompt-set-current-dir session))) |
||||
(haskell-process-log |
||||
(propertize (format "Changing directory to %s ...\n" dir) |
||||
'face font-lock-comment-face)) |
||||
(haskell-process-change-dir session |
||||
(haskell-interactive-process) |
||||
dir))) |
||||
|
||||
(defun haskell-session-buffer-default-dir (session &optional buffer) |
||||
"Try to deduce a sensible default directory for SESSION and BUFFER, |
||||
of which the latter defaults to the current buffer." |
||||
(or (haskell-session-get session 'current-dir) |
||||
(haskell-session-get session 'cabal-dir) |
||||
(if (buffer-file-name buffer) |
||||
(file-name-directory (buffer-file-name buffer)) |
||||
"~/"))) |
||||
|
||||
(defun haskell-session-prompt-set-current-dir (session &optional use-default) |
||||
"Prompt for the current directory. |
||||
Return current working directory for SESSION." |
||||
(let ((default (haskell-session-buffer-default-dir session))) |
||||
(haskell-session-set-current-dir |
||||
session |
||||
(if use-default |
||||
default |
||||
(haskell-utils-read-directory-name "Set current directory: " default)))) |
||||
(haskell-session-get session 'current-dir)) |
||||
|
||||
(defun haskell-process-change-dir (session process dir) |
||||
"Change SESSION's current directory. |
||||
Query PROCESS to `:cd` to directory DIR." |
||||
(haskell-process-queue-command |
||||
process |
||||
(make-haskell-command |
||||
:state (list session process dir) |
||||
:go |
||||
(lambda (state) |
||||
(haskell-process-send-string |
||||
(cadr state) (format ":cd %s" (cl-caddr state)))) |
||||
|
||||
:complete |
||||
(lambda (state _) |
||||
(haskell-session-set-current-dir (car state) (cl-caddr state)) |
||||
(haskell-interactive-mode-echo (car state) |
||||
(format "Changed directory: %s" |
||||
(cl-caddr state))))))) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-process-cabal-macros () |
||||
"Send the cabal macros string." |
||||
(interactive) |
||||
(haskell-process-queue-without-filters (haskell-interactive-process) |
||||
":set -optP-include -optPdist/build/autogen/cabal_macros.h")) |
||||
|
||||
(defun haskell-process-do-try-info (sym) |
||||
"Get info of SYM and echo in the minibuffer." |
||||
(let ((process (haskell-interactive-process))) |
||||
(haskell-process-queue-command |
||||
process |
||||
(make-haskell-command |
||||
:state (cons process sym) |
||||
:go (lambda (state) |
||||
(haskell-process-send-string |
||||
(car state) |
||||
(if (string-match "^[A-Za-z_]" (cdr state)) |
||||
(format ":info %s" (cdr state)) |
||||
(format ":info (%s)" (cdr state))))) |
||||
:complete (lambda (_state response) |
||||
(unless (or (string-match "^Top level" response) |
||||
(string-match "^<interactive>" response)) |
||||
(haskell-mode-message-line response))))))) |
||||
|
||||
(defun haskell-process-do-try-type (sym) |
||||
"Get type of SYM and echo in the minibuffer." |
||||
(let ((process (haskell-interactive-process))) |
||||
(haskell-process-queue-command |
||||
process |
||||
(make-haskell-command |
||||
:state (cons process sym) |
||||
:go (lambda (state) |
||||
(haskell-process-send-string |
||||
(car state) |
||||
(if (string-match "^[A-Za-z_]" (cdr state)) |
||||
(format ":type %s" (cdr state)) |
||||
(format ":type (%s)" (cdr state))))) |
||||
:complete (lambda (_state response) |
||||
(unless (or (string-match "^Top level" response) |
||||
(string-match "^<interactive>" response)) |
||||
(haskell-mode-message-line response))))))) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-mode-show-type-at (&optional insert-value) |
||||
"Show type of the thing at point or within active region asynchronously. |
||||
This function requires GHCi-ng and `:set +c` option enabled by |
||||
default (please follow GHCi-ng README available at URL |
||||
`https://github.com/chrisdone/ghci-ng'). |
||||
|
||||
\\<haskell-interactive-mode-map> |
||||
To make this function works sometimes you need to load the file in REPL |
||||
first using command `haskell-process-load-or-reload' bound to |
||||
\\[haskell-process-load-or-reload]. |
||||
|
||||
Optional argument INSERT-VALUE indicates that |
||||
recieved type signature should be inserted (but only if nothing |
||||
happened since function invocation)." |
||||
(interactive "P") |
||||
(let* ((pos (haskell-command-capture-expr-bounds)) |
||||
(req (haskell-utils-compose-type-at-command pos)) |
||||
(process (haskell-interactive-process)) |
||||
(buf (current-buffer)) |
||||
(pos-reg (cons pos (region-active-p)))) |
||||
(haskell-process-queue-command |
||||
process |
||||
(make-haskell-command |
||||
:state (list process req buf insert-value pos-reg) |
||||
:go |
||||
(lambda (state) |
||||
(let* ((prc (car state)) |
||||
(req (nth 1 state))) |
||||
(haskell-utils-async-watch-changes) |
||||
(haskell-process-send-string prc req))) |
||||
:complete |
||||
(lambda (state response) |
||||
(let* ((init-buffer (nth 2 state)) |
||||
(insert-value (nth 3 state)) |
||||
(pos-reg (nth 4 state)) |
||||
(wrap (cdr pos-reg)) |
||||
(min-pos (caar pos-reg)) |
||||
(max-pos (cdar pos-reg)) |
||||
(sig (haskell-utils-reduce-string response)) |
||||
(res-type (haskell-utils-parse-repl-response sig))) |
||||
|
||||
(cl-case res-type |
||||
;; neither popup presentation buffer |
||||
;; nor insert response in error case |
||||
('unknown-command |
||||
(message |
||||
(concat |
||||
"This command requires GHCi-ng. " |
||||
"Please read command description for details."))) |
||||
('option-missing |
||||
(message |
||||
(concat |
||||
"Could not infer type signature. " |
||||
"You need to load file first. " |
||||
"Also :set +c is required. " |
||||
"Please read command description for details."))) |
||||
('interactive-error (message "Wrong REPL response: %s" sig)) |
||||
(otherwise |
||||
(if insert-value |
||||
;; Only insert type signature and do not present it |
||||
(if (= (length haskell-utils-async-post-command-flag) 1) |
||||
(if wrap |
||||
;; Handle region case |
||||
(progn |
||||
(deactivate-mark) |
||||
(save-excursion |
||||
(delete-region min-pos max-pos) |
||||
(goto-char min-pos) |
||||
(insert (concat "(" sig ")")))) |
||||
;; Non-region cases |
||||
(haskell-command-insert-type-signature sig)) |
||||
;; Some commands registered, prevent insertion |
||||
(let* ((rev (reverse haskell-utils-async-post-command-flag)) |
||||
(cs (format "%s" (cdr rev)))) |
||||
(message |
||||
(concat |
||||
"Type signature insertion was prevented. " |
||||
"These commands were registered:" |
||||
cs)))) |
||||
;; Present the result only when response is valid and not asked |
||||
;; to insert result |
||||
(haskell-command-echo-or-present response))) |
||||
|
||||
(haskell-utils-async-stop-watching-changes init-buffer)))))))) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-process-generate-tags (&optional and-then-find-this-tag) |
||||
"Regenerate the TAGS table. |
||||
If optional AND-THEN-FIND-THIS-TAG argument is present it is used with |
||||
function `xref-find-definitions' after new table was generated." |
||||
(interactive) |
||||
(let ((process (haskell-interactive-process))) |
||||
(haskell-process-queue-command |
||||
process |
||||
(make-haskell-command |
||||
:state (cons process and-then-find-this-tag) |
||||
:go (lambda (state) |
||||
(if (eq system-type 'windows-nt) |
||||
(haskell-process-send-string |
||||
(car state) |
||||
(format ":!hasktags --output=\"%s\\TAGS\" -x -e \"%s\"" |
||||
(haskell-session-cabal-dir (haskell-process-session (car state))) |
||||
(haskell-session-cabal-dir (haskell-process-session (car state))))) |
||||
(haskell-process-send-string |
||||
(car state) |
||||
(format ":!cd %s && %s | %s" |
||||
(haskell-session-cabal-dir |
||||
(haskell-process-session (car state))) |
||||
"find . -type f \\( -name '*.hs' -or -name '*.lhs' -or -name '*.hsc' \\) -not \\( -name '#*' -or -name '.*' \\) -print0" |
||||
"xargs -0 hasktags -e -x")))) |
||||
:complete (lambda (state _response) |
||||
(when (cdr state) |
||||
(let ((session-tags |
||||
(haskell-session-tags-filename |
||||
(haskell-process-session (car state))))) |
||||
(add-to-list 'tags-table-list session-tags) |
||||
(setq tags-file-name nil)) |
||||
(xref-find-definitions (cdr state))) |
||||
(haskell-mode-message-line "Tags generated.")))))) |
||||
|
||||
(defun haskell-process-add-cabal-autogen () |
||||
"Add cabal's autogen dir to the GHCi search path. |
||||
Add <cabal-project-dir>/dist/build/autogen/ to GHCi seatch path. |
||||
This allows modules such as 'Path_...', generated by cabal, to be |
||||
loaded by GHCi." |
||||
(unless (eq 'cabal-repl (haskell-process-type)) ;; redundant with "cabal repl" |
||||
(let* |
||||
((session (haskell-interactive-session)) |
||||
(cabal-dir (haskell-session-cabal-dir session)) |
||||
(ghci-gen-dir (format "%sdist/build/autogen/" cabal-dir))) |
||||
(haskell-process-queue-without-filters |
||||
(haskell-interactive-process) |
||||
(format ":set -i%s" ghci-gen-dir))))) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-process-unignore () |
||||
"Unignore any ignored files. |
||||
Do not ignore files that were specified as being ignored by the |
||||
inferior GHCi process." |
||||
(interactive) |
||||
(let ((session (haskell-interactive-session)) |
||||
(changed nil)) |
||||
(if (null (haskell-session-get session |
||||
'ignored-files)) |
||||
(message "Nothing to unignore!") |
||||
(cl-loop for file in (haskell-session-get session |
||||
'ignored-files) |
||||
do (cl-case (read-event |
||||
(propertize (format "Set permissions? %s (y, n, v: stop and view file)" |
||||
file) |
||||
'face 'minibuffer-prompt)) |
||||
(?y |
||||
(haskell-process-unignore-file session file) |
||||
(setq changed t)) |
||||
(?v |
||||
(find-file file) |
||||
(cl-return)))) |
||||
(when (and changed |
||||
(y-or-n-p "Restart GHCi process now? ")) |
||||
(haskell-process-restart))))) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-session-change-target (target) |
||||
"Set the build TARGET for cabal REPL." |
||||
(interactive |
||||
(list |
||||
(completing-read "New build target: " (haskell-cabal-enum-targets) |
||||
nil nil nil 'haskell-cabal-targets-history))) |
||||
(let* ((session haskell-session) |
||||
(old-target (haskell-session-get session 'target))) |
||||
(when session |
||||
(haskell-session-set-target session target) |
||||
(when (and (not (string= old-target target)) |
||||
(y-or-n-p "Target changed, restart haskell process?")) |
||||
(haskell-process-start session))))) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-mode-stylish-buffer () |
||||
"Apply stylish-haskell to the current buffer." |
||||
(interactive) |
||||
(let ((column (current-column)) |
||||
(line (line-number-at-pos))) |
||||
(haskell-mode-buffer-apply-command "stylish-haskell") |
||||
(goto-char (point-min)) |
||||
(forward-line (1- line)) |
||||
(goto-char (+ column (point))))) |
||||
|
||||
(defun haskell-mode-buffer-apply-command (cmd) |
||||
"Execute shell command CMD with current buffer as input and output. |
||||
Use buffer as input and replace the whole buffer with the |
||||
output. If CMD fails the buffer remains unchanged." |
||||
(set-buffer-modified-p t) |
||||
(let* ((chomp (lambda (str) |
||||
(while (string-match "\\`\n+\\|^\\s-+\\|\\s-+$\\|\n+\\'" str) |
||||
(setq str (replace-match "" t t str))) |
||||
str)) |
||||
(_errout (lambda (fmt &rest args) |
||||
(let* ((warning-fill-prefix " ")) |
||||
(display-warning cmd (apply 'format fmt args) :warning)))) |
||||
(filename (buffer-file-name (current-buffer))) |
||||
(cmd-prefix (replace-regexp-in-string " .*" "" cmd)) |
||||
(tmp-file (make-temp-file cmd-prefix)) |
||||
(err-file (make-temp-file cmd-prefix)) |
||||
(default-directory (if (and (boundp 'haskell-session) |
||||
haskell-session) |
||||
(haskell-session-cabal-dir haskell-session) |
||||
default-directory)) |
||||
(_errcode (with-temp-file tmp-file |
||||
(call-process cmd filename |
||||
(list (current-buffer) err-file) nil))) |
||||
(stderr-output |
||||
(with-temp-buffer |
||||
(insert-file-contents err-file) |
||||
(funcall chomp (buffer-substring-no-properties (point-min) (point-max))))) |
||||
(stdout-output |
||||
(with-temp-buffer |
||||
(insert-file-contents tmp-file) |
||||
(buffer-substring-no-properties (point-min) (point-max))))) |
||||
(if (string= "" stderr-output) |
||||
(if (string= "" stdout-output) |
||||
(message "Error: %s produced no output, leaving buffer alone" cmd) |
||||
(save-restriction |
||||
(widen) |
||||
;; command successful, insert file with replacement to preserve |
||||
;; markers. |
||||
(insert-file-contents tmp-file nil nil nil t))) |
||||
(progn |
||||
;; non-null stderr, command must have failed |
||||
(message "Error: %s ended with errors, leaving buffer alone" cmd) |
||||
;; use (warning-minimum-level :debug) to see this |
||||
(display-warning cmd stderr-output :debug))) |
||||
(delete-file tmp-file) |
||||
(delete-file err-file))) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-mode-find-uses () |
||||
"Find use cases of the identifier at point and highlight them all." |
||||
(interactive) |
||||
(let ((spans (haskell-mode-uses-at))) |
||||
(unless (null spans) |
||||
(highlight-uses-mode 1) |
||||
(cl-loop for span in spans |
||||
do (haskell-mode-make-use-highlight span))))) |
||||
|
||||
(defun haskell-mode-make-use-highlight (span) |
||||
"Make a highlight overlay at the given SPAN." |
||||
(save-window-excursion |
||||
(save-excursion |
||||
(haskell-mode-goto-span span) |
||||
(save-excursion |
||||
(highlight-uses-mode-highlight |
||||
(progn |
||||
(goto-char (point-min)) |
||||
(forward-line (1- (plist-get span :start-line))) |
||||
(forward-char (plist-get span :start-col)) |
||||
(point)) |
||||
(progn |
||||
(goto-char (point-min)) |
||||
(forward-line (1- (plist-get span :end-line))) |
||||
(forward-char (plist-get span :end-col)) |
||||
(point))))))) |
||||
|
||||
(defun haskell-mode-uses-at () |
||||
"Get the locations of use cases for the ident at point. |
||||
Requires the :uses command from GHCi." |
||||
(let ((pos (or (when (region-active-p) |
||||
(cons (region-beginning) |
||||
(region-end))) |
||||
(haskell-ident-pos-at-point) |
||||
(cons (point) |
||||
(point))))) |
||||
(when pos |
||||
(let ((reply (haskell-process-queue-sync-request |
||||
(haskell-interactive-process) |
||||
(save-excursion |
||||
(format ":uses %s %d %d %d %d %s" |
||||
(buffer-file-name) |
||||
(progn (goto-char (car pos)) |
||||
(line-number-at-pos)) |
||||
(1+ (current-column)) ;; GHC uses 1-based columns. |
||||
(progn (goto-char (cdr pos)) |
||||
(line-number-at-pos)) |
||||
(1+ (current-column)) ;; GHC uses 1-based columns. |
||||
(buffer-substring-no-properties (car pos) |
||||
(cdr pos))))))) |
||||
(if reply |
||||
(let ((lines (split-string reply "\n" t))) |
||||
(cl-remove-if |
||||
#'null |
||||
(mapcar (lambda (line) |
||||
(if (string-match "\\(.*?\\):(\\([0-9]+\\),\\([0-9]+\\))-(\\([0-9]+\\),\\([0-9]+\\))" |
||||
line) |
||||
(list :path (match-string 1 line) |
||||
:start-line (string-to-number (match-string 2 line)) |
||||
;; ;; GHC uses 1-based columns. |
||||
:start-col (1- (string-to-number (match-string 3 line))) |
||||
:end-line (string-to-number (match-string 4 line)) |
||||
;; GHC uses 1-based columns. |
||||
:end-col (1- (string-to-number (match-string 5 line)))) |
||||
(error (propertize line 'face 'compilation-error)))) |
||||
lines))) |
||||
(error (propertize "No reply. Is :uses supported?" |
||||
'face 'compilation-error))))))) |
||||
|
||||
(defun haskell-command-echo-or-present (msg) |
||||
"Present message in some manner depending on configuration. |
||||
If variable `haskell-process-use-presentation-mode' is NIL it will output |
||||
modified message MSG to echo area." |
||||
(if haskell-process-use-presentation-mode |
||||
(let ((session (haskell-process-session (haskell-interactive-process)))) |
||||
(haskell-presentation-present session msg)) |
||||
(let ((m (haskell-utils-reduce-string msg))) |
||||
(message m)))) |
||||
|
||||
(defun haskell-command-capture-expr-bounds () |
||||
"Capture position bounds of expression at point. |
||||
If there is an active region then it returns region |
||||
bounds. Otherwise it uses `haskell-spanable-pos-at-point` to |
||||
capture identifier bounds. If latter function returns NIL this function |
||||
will return cons cell where min and max positions both are equal |
||||
to point." |
||||
(or (when (region-active-p) |
||||
(cons (region-beginning) |
||||
(region-end))) |
||||
(haskell-spanable-pos-at-point) |
||||
(cons (point) (point)))) |
||||
|
||||
(defun haskell-command-insert-type-signature (signature) |
||||
"Insert type signature. |
||||
In case of active region is present, wrap it by parentheses and |
||||
append SIGNATURE to original expression. Otherwise tries to |
||||
carefully insert SIGNATURE above identifier at point. Removes |
||||
newlines and extra whitespace in signature before insertion." |
||||
(let* ((ident-pos (or (haskell-ident-pos-at-point) |
||||
(cons (point) (point)))) |
||||
(min-pos (car ident-pos)) |
||||
(sig (haskell-utils-reduce-string signature))) |
||||
(save-excursion |
||||
(goto-char min-pos) |
||||
(let ((col (current-column))) |
||||
(insert sig "\n") |
||||
(indent-to col))))) |
||||
|
||||
(provide 'haskell-commands) |
||||
;;; haskell-commands.el ends here |
@ -1,70 +0,0 @@
|
||||
;;; haskell-compat.el --- legacy/compatibility backports for haskell-mode -*- lexical-binding: t -*- |
||||
;; |
||||
;; Filename: haskell-compat.el |
||||
;; Description: legacy/compatibility backports for haskell-mode |
||||
|
||||
;; This file is not part of GNU Emacs. |
||||
|
||||
;; This file is free software; you can redistribute it and/or modify |
||||
;; it under the terms of the GNU General Public License as published by |
||||
;; the Free Software Foundation; either version 3, or (at your option) |
||||
;; any later version. |
||||
|
||||
;; This file is distributed in the hope that it will be useful, |
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
;; GNU General Public License for more details. |
||||
|
||||
;; You should have received a copy of the GNU General Public License |
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
;;; Commentary: |
||||
|
||||
;;; Code: |
||||
(require 'etags) |
||||
(require 'ring) |
||||
(require 'outline) |
||||
(require 'xref nil t) |
||||
|
||||
(eval-when-compile |
||||
(setq byte-compile-warnings '(not cl-functions obsolete))) |
||||
|
||||
;; Missing in Emacs23, stolen from Emacs24's `subr.el' |
||||
(unless (fboundp 'process-live-p) |
||||
(defun process-live-p (process) |
||||
"Returns non-nil if PROCESS is alive. |
||||
A process is considered alive if its status is `run', `open', |
||||
`listen', `connect' or `stop'." |
||||
(memq (process-status process) |
||||
'(run open listen connect stop)))) |
||||
|
||||
;; Cross-referencing commands have been replaced since Emacs 25.1. |
||||
;; These aliases are required to provide backward compatibility. |
||||
(unless (fboundp 'xref-push-marker-stack) |
||||
(defalias 'xref-pop-marker-stack 'pop-tag-mark) |
||||
|
||||
(defun xref-push-marker-stack (&optional m) |
||||
"Add point to the marker stack." |
||||
(ring-insert find-tag-marker-ring (or m (point-marker))))) |
||||
|
||||
(unless (fboundp 'outline-hide-sublevels) |
||||
(defalias 'outline-hide-sublevels 'hide-sublevels)) |
||||
|
||||
(unless (fboundp 'outline-show-subtree) |
||||
(defalias 'outline-show-subtree 'show-subtree)) |
||||
|
||||
(unless (fboundp 'outline-hide-sublevels) |
||||
(defalias 'outline-hide-sublevels 'hide-sublevels)) |
||||
|
||||
(unless (fboundp 'outline-show-subtree) |
||||
(defalias 'outline-show-subtree 'show-subtree)) |
||||
|
||||
(unless (fboundp 'xref-find-definitions) |
||||
(defun xref-find-definitions (ident) |
||||
(let ((next-p (and (boundp 'xref-prompt-for-identifier) |
||||
xref-prompt-for-identifier))) |
||||
(find-tag ident next-p)))) |
||||
|
||||
(provide 'haskell-compat) |
||||
|
||||
;;; haskell-compat.el ends here |
@ -1,163 +0,0 @@
|
||||
;;; haskell-compile.el --- Haskell/GHC compilation sub-mode -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright (C) 2013 Herbert Valerio Riedel |
||||
|
||||
;; Author: Herbert Valerio Riedel <hvr@gnu.org> |
||||
|
||||
;; 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 of the License, or |
||||
;; (at your option) any later version. |
||||
|
||||
;; This file is distributed in the hope that it will be useful, |
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
;; GNU General Public License for more details. |
||||
|
||||
;; You should have received a copy of the GNU General Public License |
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
;;; Commentary: |
||||
|
||||
;; Simple GHC-centric compilation sub-mode; see info node |
||||
;; `(haskell-mode)compilation' for more information |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'compile) |
||||
(require 'haskell-cabal) |
||||
|
||||
;;;###autoload |
||||
(defgroup haskell-compile nil |
||||
"Settings for Haskell compilation mode" |
||||
:link '(custom-manual "(haskell-mode)compilation") |
||||
:group 'haskell) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-compile-cabal-build-command |
||||
"cd %s && cabal build --ghc-option=-ferror-spans" |
||||
"Default build command to use for `haskell-cabal-build' when a cabal file is detected. |
||||
The `%s' placeholder is replaced by the cabal package top folder." |
||||
:group 'haskell-compile |
||||
:type 'string) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-compile-cabal-build-alt-command |
||||
"cd %s && cabal clean -s && cabal build --ghc-option=-ferror-spans" |
||||
"Alternative build command to use when `haskell-cabal-build' is called with a negative prefix argument. |
||||
The `%s' placeholder is replaced by the cabal package top folder." |
||||
:group 'haskell-compile |
||||
:type 'string) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-compile-command |
||||
"ghc -Wall -ferror-spans -fforce-recomp -c %s" |
||||
"Default build command to use for `haskell-cabal-build' when no cabal file is detected. |
||||
The `%s' placeholder is replaced by the current buffer's filename." |
||||
:group 'haskell-compile |
||||
:type 'string) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-compile-ghc-filter-linker-messages |
||||
t |
||||
"Filter out unremarkable \"Loading package...\" linker messages during compilation." |
||||
:group 'haskell-compile |
||||
:type 'boolean) |
||||
|
||||
(defconst haskell-compilation-error-regexp-alist |
||||
`((,(concat |
||||
"^ *\\(?1:[^ \t\r\n]+?\\):" |
||||
"\\(?:" |
||||
"\\(?2:[0-9]+\\):\\(?4:[0-9]+\\)\\(?:-\\(?5:[0-9]+\\)\\)?" ;; "121:1" & "12:3-5" |
||||
"\\|" |
||||
"(\\(?2:[0-9]+\\),\\(?4:[0-9]+\\))-(\\(?3:[0-9]+\\),\\(?5:[0-9]+\\))" ;; "(289,5)-(291,36)" |
||||
"\\)" |
||||
":\\(?6: Warning:\\)?") |
||||
1 (2 . 3) (4 . 5) (6 . nil)) ;; error/warning locus |
||||
|
||||
;; multiple declarations |
||||
("^ \\(?:Declared at:\\| \\) \\(?1:[^ \t\r\n]+\\):\\(?2:[0-9]+\\):\\(?4:[0-9]+\\)$" |
||||
1 2 4 0) ;; info locus |
||||
|
||||
;; this is the weakest pattern as it's subject to line wrapping et al. |
||||
(" at \\(?1:[^ \t\r\n]+\\):\\(?2:[0-9]+\\):\\(?4:[0-9]+\\)\\(?:-\\(?5:[0-9]+\\)\\)?[)]?$" |
||||
1 2 (4 . 5) 0)) ;; info locus |
||||
"Regexps used for matching GHC compile messages. |
||||
See `compilation-error-regexp-alist' for semantics.") |
||||
|
||||
(defvar haskell-compilation-mode-map |
||||
(let ((map (make-sparse-keymap))) |
||||
(set-keymap-parent map compilation-mode-map)) |
||||
"Keymap for `haskell-compilation-mode' buffers. |
||||
This is a child of `compilation-mode-map'.") |
||||
|
||||
(defun haskell-compilation-filter-hook () |
||||
"Local `compilation-filter-hook' for `haskell-compilation-mode'." |
||||
|
||||
(when haskell-compile-ghc-filter-linker-messages |
||||
(delete-matching-lines "^ *Loading package [^ \t\r\n]+ [.]+ linking [.]+ done\\.$" |
||||
(if (boundp 'compilation-filter-start) ;; available since Emacs 24.2 |
||||
(save-excursion (goto-char compilation-filter-start) |
||||
(line-beginning-position)) |
||||
(point-min)) |
||||
(point)))) |
||||
|
||||
(define-compilation-mode haskell-compilation-mode "HsCompilation" |
||||
"Haskell/GHC specific `compilation-mode' derivative. |
||||
This mode provides support for GHC 7.[46]'s compile |
||||
messages. Specifically, also the `-ferror-spans` source location |
||||
format is supported, as well as info-locations within compile |
||||
messages pointing to additional source locations. |
||||
|
||||
See Info node `(haskell-mode)compilation' for more details." |
||||
(set (make-local-variable 'compilation-error-regexp-alist) |
||||
haskell-compilation-error-regexp-alist) |
||||
|
||||
(add-hook 'compilation-filter-hook |
||||
'haskell-compilation-filter-hook nil t) |
||||
) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-compile (&optional edit-command) |
||||
"Compile the Haskell program including the current buffer. |
||||
Tries to locate the next cabal description in current or parent |
||||
folders via `haskell-cabal-find-dir' and if found, invoke |
||||
`haskell-compile-cabal-build-command' from the cabal package root |
||||
folder. If no cabal package could be detected, |
||||
`haskell-compile-command' is used instead. |
||||
|
||||
If prefix argument EDIT-COMMAND is non-nil (and not a negative |
||||
prefix `-'), `haskell-compile' prompts for custom compile |
||||
command. |
||||
|
||||
If EDIT-COMMAND contains the negative prefix argument `-', |
||||
`haskell-compile' calls the alternative command defined in |
||||
`haskell-compile-cabal-build-alt-command' if a cabal package was |
||||
detected. |
||||
|
||||
`haskell-compile' uses `haskell-compilation-mode' which is |
||||
derived from `compilation-mode'. See Info |
||||
node `(haskell-mode)compilation' for more details." |
||||
(interactive "P") |
||||
(save-some-buffers (not compilation-ask-about-save) |
||||
(if (boundp 'compilation-save-buffers-predicate) ;; since Emacs 24.1(?) |
||||
compilation-save-buffers-predicate)) |
||||
(let* ((cabdir (haskell-cabal-find-dir)) |
||||
(command1 (if (eq edit-command '-) |
||||
haskell-compile-cabal-build-alt-command |
||||
haskell-compile-cabal-build-command)) |
||||
(srcname (buffer-file-name)) |
||||
(command (if cabdir |
||||
(format command1 cabdir) |
||||
(if (and srcname (derived-mode-p 'haskell-mode)) |
||||
(format haskell-compile-command srcname) |
||||
command1)))) |
||||
(when (and edit-command (not (eq edit-command '-))) |
||||
(setq command (compilation-read-command command))) |
||||
|
||||
(compilation-start command 'haskell-compilation-mode))) |
||||
|
||||
(provide 'haskell-compile) |
||||
;;; haskell-compile.el ends here |
@ -1,133 +0,0 @@
|
||||
;;; haskell-complete-module.el --- A fast way to complete Haskell module names -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright (c) 2014 Chris Done. All rights reserved. |
||||
|
||||
;; This file is free software; you can redistribute it and/or modify |
||||
;; it under the terms of the GNU General Public License as published by |
||||
;; the Free Software Foundation; either version 3, or (at your option) |
||||
;; any later version. |
||||
|
||||
;; This file is distributed in the hope that it will be useful, |
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
;; GNU General Public License for more details. |
||||
|
||||
;; You should have received a copy of the GNU General Public License |
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cl-lib) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-complete-module-preferred |
||||
'() |
||||
"Override ordering of module results by specifying preferred modules." |
||||
:group 'haskell |
||||
:type '(repeat string)) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-complete-module-max-display |
||||
10 |
||||
"Maximum items to display in minibuffer." |
||||
:group 'haskell |
||||
:type 'number) |
||||
|
||||
(defun haskell-complete-module-read (prompt candidates) |
||||
"Interactively auto-complete from a list of candidates." |
||||
(let ((stack (list)) |
||||
(pattern "") |
||||
(result nil)) |
||||
(delete-dups candidates) |
||||
(setq candidates |
||||
(sort candidates |
||||
(lambda (a b) |
||||
(let ((a-mem (member a haskell-complete-module-preferred)) |
||||
(b-mem (member b haskell-complete-module-preferred))) |
||||
(cond |
||||
((and a-mem (not b-mem)) |
||||
t) |
||||
((and b-mem (not a-mem)) |
||||
nil) |
||||
(t |
||||
(string< a b))))))) |
||||
(while (not result) |
||||
(let ((key |
||||
(key-description |
||||
(vector |
||||
(read-key |
||||
(concat (propertize prompt 'face 'minibuffer-prompt) |
||||
(propertize pattern 'face 'font-lock-type-face) |
||||
"{" |
||||
(mapconcat #'identity |
||||
(let* ((i 0)) |
||||
(cl-loop for candidate in candidates |
||||
while (<= i haskell-complete-module-max-display) |
||||
do (cl-incf i) |
||||
collect (cond ((> i haskell-complete-module-max-display) |
||||
"...") |
||||
((= i 1) |
||||
(propertize candidate 'face 'ido-first-match-face)) |
||||
(t candidate)))) |
||||
" | ") |
||||
"}")))))) |
||||
(cond |
||||
((string= key "C-g") |
||||
(keyboard-quit)) |
||||
((string= key "DEL") |
||||
(unless (null stack) |
||||
(setq candidates (pop stack))) |
||||
(unless (string= "" pattern) |
||||
(setq pattern (substring pattern 0 -1)))) |
||||
((string= key "RET") |
||||
(setq result (or (car candidates) |
||||
pattern))) |
||||
((string= key "<left>") |
||||
(setq candidates |
||||
(append (last candidates) |
||||
(butlast candidates)))) |
||||
((string= key "<right>") |
||||
(setq candidates |
||||
(append (cdr candidates) |
||||
(list (car candidates))))) |
||||
(t |
||||
(when (string-match "[A-Za-z0-9_'.]+" key) |
||||
(push candidates stack) |
||||
(setq pattern (concat pattern key)) |
||||
(setq candidates (haskell-complete-module pattern candidates))))))) |
||||
result)) |
||||
|
||||
(defun haskell-complete-module (pattern candidates) |
||||
"Filter the CANDIDATES using PATTERN." |
||||
(let ((case-fold-search t)) |
||||
(cl-loop for candidate in candidates |
||||
when (haskell-complete-module-match pattern candidate) |
||||
collect candidate))) |
||||
|
||||
(defun haskell-complete-module-match (pattern text) |
||||
"Match PATTERN against TEXT." |
||||
(string-match (haskell-complete-module-regexp pattern) |
||||
text)) |
||||
|
||||
(defun haskell-complete-module-regexp (pattern) |
||||
"Make a regular expression for the given module pattern. Example: |
||||
|
||||
\"c.m.s\" -> \"^c[^.]*\\.m[^.]*\\.s[^.]*\" |
||||
|
||||
" |
||||
(let ((components (mapcar #'haskell-complete-module-component |
||||
(split-string pattern "\\." t)))) |
||||
(concat "^" |
||||
(mapconcat #'identity |
||||
components |
||||
"\\.")))) |
||||
|
||||
(defun haskell-complete-module-component (component) |
||||
"Make a regular expression for the given component. Example: |
||||
|
||||
\"co\" -> \"c[^.]*o[^.]*\" |
||||
|
||||
" |
||||
(replace-regexp-in-string "\\(.\\)" "\\1[^.]*" component)) |
||||
|
||||
(provide 'haskell-complete-module) |
@ -1,266 +0,0 @@
|
||||
;;; haskell-completions.el --- Haskell Completion package -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright © 2015 Athur Fayzrakhmanov. All rights reserved. |
||||
|
||||
;; This file is part of haskell-mode package. |
||||
;; You can contact with authors using GitHub issue tracker: |
||||
;; https://github.com/haskell/haskell-mode/issues |
||||
|
||||
;; 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: |
||||
|
||||
;; This package provides completions related functionality for |
||||
;; Haskell Mode such grab completion prefix at point, and etc.. |
||||
|
||||
;; Some description |
||||
;; ================ |
||||
;; |
||||
;; For major use function `haskell-completions-grab-prefix' is supposed, and |
||||
;; other prefix grabbing functions are used internally by it. So, only this |
||||
;; funciton have prefix minimal length functionality and invokes predicate |
||||
;; function `haskell-completions-can-grab-prefix'. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'haskell-mode) |
||||
(require 'haskell-process) |
||||
(require 'haskell-interactive-mode) |
||||
|
||||
(defvar haskell-completions-pragma-names |
||||
(list "DEPRECATED" |
||||
"INCLUDE" |
||||
"INCOHERENT" |
||||
"INLINABLE" |
||||
"INLINE" |
||||
"LANGUAGE" |
||||
"LINE" |
||||
"MINIMAL" |
||||
"NOINLINE" |
||||
"NOUNPACK" |
||||
"OPTIONS" |
||||
"OPTIONS_GHC" |
||||
"OVERLAPPABLE" |
||||
"OVERLAPPING" |
||||
"OVERLAPS" |
||||
"RULES" |
||||
"SOURCE" |
||||
"SPECIALIZE" |
||||
"UNPACK" |
||||
"WARNING") |
||||
"A list of supported pragmas. |
||||
This list comes from GHC documentation (URL |
||||
`https://downloads.haskell.org/~ghc/7.10.1/docs/html/users_guide/pragmas.html'. |
||||
") |
||||
|
||||
(defun haskell-completions-can-grab-prefix () |
||||
"Check if the case is appropriate for grabbing completion prefix. |
||||
Returns t if point is either at whitespace character, or at |
||||
punctuation, or at line end and preceeding character is not a |
||||
whitespace or new line, otherwise returns nil. |
||||
|
||||
Returns nil in presense of active region." |
||||
(when (not (region-active-p)) |
||||
(when (looking-at-p (rx (| space line-end punct))) |
||||
(when (not (bobp)) |
||||
(save-excursion |
||||
(backward-char) |
||||
(not (looking-at-p (rx (| space line-end))))))))) |
||||
|
||||
(defun haskell-completions-grab-pragma-prefix () |
||||
"Grab completion prefix for pragma completions. |
||||
Returns a list of form '(prefix-start-position |
||||
prefix-end-position prefix-value prefix-type) for pramga names |
||||
such as WARNING, DEPRECATED, LANGUAGE and etc. Also returns |
||||
completion prefixes for options in case OPTIONS_GHC pragma, or |
||||
language extensions in case of LANGUAGE pragma. Obsolete OPTIONS |
||||
pragma is supported also." |
||||
(when (nth 4 (syntax-ppss)) |
||||
;; We're inside comment |
||||
(let ((p (point)) |
||||
(comment-start (nth 8 (syntax-ppss))) |
||||
(case-fold-search nil) |
||||
prefix-start |
||||
prefix-end |
||||
prefix-type |
||||
prefix-value) |
||||
(save-excursion |
||||
(goto-char comment-start) |
||||
(when (looking-at (rx "{-#" (1+ (| space "\n")))) |
||||
(let ((pragma-start (match-end 0))) |
||||
(when (> p pragma-start) |
||||
;; point stands after `{-#` |
||||
(goto-char pragma-start) |
||||
(when (looking-at (rx (1+ (| upper "_")))) |
||||
;; found suitable sequence for pragma name |
||||
(let ((pragma-end (match-end 0)) |
||||
(pragma-value (match-string-no-properties 0))) |
||||
(if (eq p pragma-end) |
||||
;; point is at the end of (in)complete pragma name |
||||
;; prepare resulting values |
||||
(progn |
||||
(setq prefix-start pragma-start) |
||||
(setq prefix-end pragma-end) |
||||
(setq prefix-value pragma-value) |
||||
(setq prefix-type |
||||
'haskell-completions-pragma-name-prefix)) |
||||
(when (and (> p pragma-end) |
||||
(or (equal "OPTIONS_GHC" pragma-value) |
||||
(equal "OPTIONS" pragma-value) |
||||
(equal "LANGUAGE" pragma-value))) |
||||
;; point is after pragma name, so we need to check |
||||
;; special cases of `OPTIONS_GHC` and `LANGUAGE` pragmas |
||||
;; and provide a completion prefix for possible ghc |
||||
;; option or language extension. |
||||
(goto-char pragma-end) |
||||
(when (re-search-forward |
||||
(rx (* anything) |
||||
(1+ (regexp "\\S-"))) |
||||
p |
||||
t) |
||||
(let* ((str (match-string-no-properties 0)) |
||||
(split (split-string str (rx (| space "\n")) t)) |
||||
(val (car (last split))) |
||||
(end (point))) |
||||
(when (and (equal p end) |
||||
(not (string-match-p "#" val))) |
||||
(setq prefix-value val) |
||||
(backward-char (length val)) |
||||
(setq prefix-start (point)) |
||||
(setq prefix-end end) |
||||
(setq |
||||
prefix-type |
||||
(if (not (equal "LANGUAGE" pragma-value)) |
||||
'haskell-completions-ghc-option-prefix |
||||
'haskell-completions-language-extension-prefix |
||||
))))))))))))) |
||||
(when prefix-value |
||||
(list prefix-start prefix-end prefix-value prefix-type))))) |
||||
|
||||
(defun haskell-completions-grab-identifier-prefix () |
||||
"Grab completion prefix for identifier at point. |
||||
Returns a list of form '(prefix-start-position |
||||
prefix-end-position prefix-value prefix-type) for haskell |
||||
identifier at point depending on result of function |
||||
`haskell-ident-pos-at-point'." |
||||
(let ((pos-at-point (haskell-ident-pos-at-point)) |
||||
(p (point))) |
||||
(when pos-at-point |
||||
(let* ((start (car pos-at-point)) |
||||
(end (cdr pos-at-point)) |
||||
(type 'haskell-completions-identifier-prefix) |
||||
(case-fold-search nil) |
||||
value) |
||||
;; we need end position of result, becase of |
||||
;; `haskell-ident-pos-at-point' ignores trailing whitespace, e.g. the |
||||
;; result will be same for `map|` and `map |` invocations. |
||||
(when (<= p end) |
||||
(setq end p) |
||||
(setq value (buffer-substring-no-properties start end)) |
||||
(when (string-match-p (rx bos upper) value) |
||||
;; we need to check if found identifier is a module name |
||||
(save-excursion |
||||
(goto-char (line-beginning-position)) |
||||
(when (re-search-forward |
||||
(rx "import" |
||||
(? (1+ space) "qualified") |
||||
(1+ space) |
||||
upper |
||||
(1+ (| alnum "."))) |
||||
p ;; bound |
||||
t) ;; no-error |
||||
(if (equal p (point)) |
||||
(setq type 'haskell-completions-module-name-prefix) |
||||
(when (re-search-forward |
||||
(rx (| " as " "(")) |
||||
start |
||||
t) |
||||
;; but uppercase ident could occur after `as` keyword, or in |
||||
;; module imports after opening parenthesis, in this case |
||||
;; restore identifier type again, it's neccessary to |
||||
;; distinguish the means of completions retrieval |
||||
(setq type 'haskell-completions-identifier-prefix)))))) |
||||
(when (nth 8 (syntax-ppss)) |
||||
;; eighth element of syntax-ppss result is string or comment start, |
||||
;; so when it's not nil word at point is inside string or comment, |
||||
;; return special literal prefix type |
||||
(setq type 'haskell-completions-general-prefix)) |
||||
;; finally take in account minlen if given and return the result |
||||
(when value (list start end value type))))))) |
||||
|
||||
(defun haskell-completions-grab-prefix (&optional minlen) |
||||
"Grab prefix at point for possible completion. |
||||
Returns a list of form '(prefix-start-position |
||||
prefix-end-position prefix-value prefix-type) depending on |
||||
situation, e.g. is it needed to complete pragma, module name, |
||||
arbitrary identifier, and etc. Rerurns nil in case it is |
||||
impossible to grab prefix. |
||||
|
||||
If provided optional MINLEN parameter this function will return |
||||
result only if prefix length is not less than MINLEN." |
||||
(when (haskell-completions-can-grab-prefix) |
||||
(let ((prefix (cond |
||||
((haskell-completions-grab-pragma-prefix)) |
||||
((haskell-completions-grab-identifier-prefix))))) |
||||
(cond ((and minlen prefix) |
||||
(when (>= (length (nth 2 prefix)) minlen) |
||||
prefix)) |
||||
(prefix prefix))))) |
||||
|
||||
|
||||
(defun haskell-completions-sync-completions-at-point () |
||||
"A `completion-at-point' function using the current haskell process. |
||||
Returns nil if no completions available." |
||||
(let ((prefix-data (haskell-completions-grab-prefix))) |
||||
(when prefix-data |
||||
(cl-destructuring-bind (beg end pfx typ) prefix-data |
||||
(let ((imp (eql typ 'haskell-completions-module-name-prefix)) |
||||
lst) |
||||
(setq lst |
||||
(cl-case typ |
||||
;; non-interactive completions first |
||||
('haskell-completions-pragma-name-prefix |
||||
haskell-completions-pragma-names) |
||||
('haskell-completions-ghc-option-prefix |
||||
haskell-ghc-supported-options) |
||||
('haskell-completions-language-extension-prefix |
||||
haskell-ghc-supported-extensions) |
||||
(otherwise |
||||
(when (and |
||||
(not (eql typ 'haskell-completions-general-prefix)) |
||||
(haskell-session-maybe) |
||||
(not |
||||
(haskell-process-cmd (haskell-interactive-process)))) |
||||
;; if REPL is available and not busy try to query it |
||||
;; for completions list in case of module name or |
||||
;; identifier prefixes |
||||
(haskell-completions-sync-complete-repl pfx imp))))) |
||||
(when lst |
||||
(list beg end lst))))))) |
||||
|
||||
(defun haskell-completions-sync-complete-repl (prefix &optional import) |
||||
"Return completion list for given PREFIX quering REPL synchronously. |
||||
When optional IMPORT argument is non-nil complete PREFIX |
||||
prepending \"import \" keyword (useful for module names). This |
||||
function is supposed for internal use." |
||||
(haskell-process-get-repl-completions |
||||
(haskell-interactive-process) |
||||
(if import |
||||
(concat "import " prefix) |
||||
prefix))) |
||||
|
||||
(provide 'haskell-completions) |
||||
;;; haskell-completions.el ends here |
@ -1,429 +0,0 @@
|
||||
;;; haskell-customize.el --- Customization settings -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright (c) 2014 Chris Done. All rights reserved. |
||||
|
||||
;; This file is free software; you can redistribute it and/or modify |
||||
;; it under the terms of the GNU General Public License as published by |
||||
;; the Free Software Foundation; either version 3, or (at your option) |
||||
;; any later version. |
||||
|
||||
;; This file is distributed in the hope that it will be useful, |
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
;; GNU General Public License for more details. |
||||
|
||||
;; You should have received a copy of the GNU General Public License |
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cl-lib) |
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
;; Customization variables |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-load-or-reload-prompt nil |
||||
"Nil means there will be no prompts on starting REPL. Defaults will be accepted." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defgroup haskell nil |
||||
"Major mode for editing Haskell programs." |
||||
:link '(custom-manual "(haskell-mode)") |
||||
:group 'languages |
||||
:prefix "haskell-") |
||||
|
||||
(defvar haskell-mode-pkg-base-dir (file-name-directory load-file-name) |
||||
"Package base directory of installed `haskell-mode'. |
||||
Used for locating additional package data files.") |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-completing-read-function 'ido-completing-read |
||||
"Default function to use for completion." |
||||
:group 'haskell |
||||
:type '(choice |
||||
(function-item :tag "ido" :value ido-completing-read) |
||||
(function-item :tag "helm" :value helm--completing-read-default) |
||||
(function-item :tag "completing-read" :value completing-read) |
||||
(function :tag "Custom function"))) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-type |
||||
'auto |
||||
"The inferior Haskell process type to use. |
||||
|
||||
When set to 'auto (the default), the directory contents and |
||||
available programs will be used to make a best guess at the |
||||
process type: |
||||
|
||||
If the project directory or one of its parents contains a |
||||
\"cabal.sandbox.config\" file, then cabal-repl will be used. |
||||
|
||||
If there's a \"stack.yaml\" file and the \"stack\" executable can |
||||
be located, then stack-ghci will be used. |
||||
|
||||
Otherwise if there's a *.cabal file, cabal-repl will be used. |
||||
|
||||
If none of the above apply, ghci will be used." |
||||
:type '(choice (const auto) (const ghci) (const cabal-repl) (const stack-ghci)) |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-wrapper-function |
||||
#'identity |
||||
"Wrap or transform haskell process commands using this function. |
||||
|
||||
Can be set to a custom function which takes a list of arguments |
||||
and returns a possibly-modified list. |
||||
|
||||
The following example function arranges for all haskell process |
||||
commands to be started in the current nix-shell environment: |
||||
|
||||
(lambda (argv) (append (list \"nix-shell\" \"-I\" \".\" \"--command\" ) |
||||
(list (mapconcat 'identity argv \" \")))) |
||||
|
||||
See Info Node `(emacs)Directory Variables' for a way to set this option on |
||||
a per-project basis." |
||||
:group 'haskell-interactive |
||||
:type '(choice |
||||
(function-item :tag "None" :value identity) |
||||
(function :tag "Custom function"))) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-ask-also-kill-buffers |
||||
t |
||||
"Ask whether to kill all associated buffers when a session |
||||
process is killed." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
;; Configuration |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-doc-prettify-types t |
||||
"Replace some parts of types with Unicode characters like \"∷\" |
||||
when showing type information about symbols." |
||||
:group 'haskell-doc |
||||
:type 'boolean |
||||
:safe 'booleanp) |
||||
|
||||
(defvar haskell-process-end-hook nil |
||||
"Hook for when the haskell process ends.") |
||||
|
||||
;;;###autoload |
||||
(defgroup haskell-interactive nil |
||||
"Settings for REPL interaction via `haskell-interactive-mode'" |
||||
:link '(custom-manual "(haskell-mode)haskell-interactive-mode") |
||||
:group 'haskell) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-path-ghci |
||||
"ghci" |
||||
"The path for starting ghci." |
||||
:group 'haskell-interactive |
||||
:type '(choice string (repeat string))) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-path-cabal |
||||
"cabal" |
||||
"Path to the `cabal' executable." |
||||
:group 'haskell-interactive |
||||
:type '(choice string (repeat string))) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-path-stack |
||||
"stack" |
||||
"The path for starting stack." |
||||
:group 'haskell-interactive |
||||
:type '(choice string (repeat string))) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-args-ghci |
||||
'("-ferror-spans") |
||||
"Any arguments for starting ghci." |
||||
:group 'haskell-interactive |
||||
:type '(repeat (string :tag "Argument"))) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-args-cabal-repl |
||||
'("--ghc-option=-ferror-spans") |
||||
"Additional arguments for `cabal repl' invocation. |
||||
Note: The settings in `haskell-process-path-ghci' and |
||||
`haskell-process-args-ghci' are not automatically reused as `cabal repl' |
||||
currently invokes `ghc --interactive'. Use |
||||
`--with-ghc=<path-to-executable>' if you want to use a different |
||||
interactive GHC frontend; use `--ghc-option=<ghc-argument>' to |
||||
pass additional flags to `ghc'." |
||||
:group 'haskell-interactive |
||||
:type '(repeat (string :tag "Argument"))) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-args-stack-ghci |
||||
'("--ghc-options=-ferror-spans") |
||||
"Additional arguments for `stack ghci' invocation." |
||||
:group 'haskell-interactive |
||||
:type '(repeat (string :tag "Argument"))) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-do-cabal-format-string |
||||
":!cd %s && %s" |
||||
"The way to run cabal comands. It takes two arguments -- the directory and the command. |
||||
See `haskell-process-do-cabal' for more details." |
||||
:group 'haskell-interactive |
||||
:type 'string) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-log |
||||
nil |
||||
"Enable debug logging to \"*haskell-process-log*\" buffer." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-show-debug-tips |
||||
t |
||||
"Show debugging tips when starting the process." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-notify-p |
||||
nil |
||||
"Notify using notifications.el (if loaded)?" |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-suggest-no-warn-orphans |
||||
t |
||||
"Suggest adding -fno-warn-orphans pragma to file when getting orphan warnings." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-suggest-hoogle-imports |
||||
nil |
||||
"Suggest to add import statements using Hoogle as a backend." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-suggest-hayoo-imports |
||||
nil |
||||
"Suggest to add import statements using Hayoo as a backend." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-hayoo-query-url |
||||
"http://hayoo.fh-wedel.de/json/?query=%s" |
||||
"Query url for json hayoo results." |
||||
:type 'string |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-suggest-haskell-docs-imports |
||||
nil |
||||
"Suggest to add import statements using haskell-docs as a backend." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-suggest-add-package |
||||
t |
||||
"Suggest to add packages to your .cabal file when Cabal says it |
||||
is a member of the hidden package, blah blah." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-suggest-language-pragmas |
||||
t |
||||
"Suggest adding LANGUAGE pragmas recommended by GHC." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-suggest-remove-import-lines |
||||
nil |
||||
"Suggest removing import lines as warned by GHC." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-suggest-overloaded-strings |
||||
t |
||||
"Suggest adding OverloadedStrings pragma to file when getting type mismatches with [Char]." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-check-cabal-config-on-load |
||||
t |
||||
"Check changes cabal config on loading Haskell files and |
||||
restart the GHCi process if changed.." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-prompt-restart-on-cabal-change |
||||
t |
||||
"Ask whether to restart the GHCi process when the Cabal file |
||||
has changed?" |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-auto-import-loaded-modules |
||||
nil |
||||
"Auto import the modules reported by GHC to have been loaded?" |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-reload-with-fbytecode |
||||
nil |
||||
"When using -fobject-code, auto reload with -fbyte-code (and |
||||
then restore the -fobject-code) so that all module info and |
||||
imports become available?" |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-use-presentation-mode |
||||
nil |
||||
"Use presentation mode to show things like type info instead of |
||||
printing to the message area." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-process-suggest-restart |
||||
t |
||||
"Suggest restarting the process when it has died" |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-interactive-mode-scroll-to-bottom |
||||
nil |
||||
"Scroll to bottom in the REPL always." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-interactive-popup-errors |
||||
t |
||||
"Popup errors in a separate buffer." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-interactive-mode-collapse |
||||
nil |
||||
"Collapse printed results." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-interactive-types-for-show-ambiguous |
||||
t |
||||
"Show types when there's no Show instance or there's an |
||||
ambiguous class constraint." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
(defvar haskell-interactive-prompt "λ> " |
||||
"The prompt to use.") |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-interactive-mode-eval-mode |
||||
nil |
||||
"Use the given mode's font-locking to render some text." |
||||
:type '(choice function (const :tag "None" nil)) |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-interactive-mode-hide-multi-line-errors |
||||
nil |
||||
"Hide collapsible multi-line compile messages by default." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-interactive-mode-delete-superseded-errors |
||||
t |
||||
"Whether to delete compile messages superseded by recompile/reloads." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-interactive-mode-include-file-name |
||||
t |
||||
"Include the file name of the module being compiled when |
||||
printing compilation messages." |
||||
:type 'boolean |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-import-mapping |
||||
'() |
||||
"Support a mapping from module to import lines. |
||||
|
||||
E.g. '((\"Data.Map\" . \"import qualified Data.Map as M |
||||
import Data.Map (Map) |
||||
\")) |
||||
|
||||
This will import |
||||
|
||||
import qualified Data.Map as M |
||||
import Data.Map (Map) |
||||
|
||||
when Data.Map is the candidate. |
||||
|
||||
" |
||||
:type '(repeat (cons (string :tag "Module name") |
||||
(string :tag "Import lines"))) |
||||
:group 'haskell-interactive) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-language-extensions |
||||
'() |
||||
"Language extensions in use. Should be in format: -XFoo, |
||||
-XNoFoo etc. The idea is that various tools written with HSE (or |
||||
any haskell-mode code that needs to be aware of syntactical |
||||
properties; such as an indentation mode) that don't know what |
||||
extensions to use can use this variable. Examples: hlint, |
||||
hindent, structured-haskell-mode, tool-de-jour, etc. |
||||
|
||||
You can set this per-project with a .dir-locals.el file, in the |
||||
same vein as `haskell-indent-spaces'." |
||||
:group 'haskell |
||||
:type '(repeat 'string)) |
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
;; Accessor functions |
||||
|
||||
(defun haskell-process-type () |
||||
"Return `haskell-process-type', or a guess if that variable is 'auto." |
||||
(if (eq 'auto haskell-process-type) |
||||
(cond |
||||
;; User has explicitly initialized this project with cabal |
||||
((locate-dominating-file default-directory "cabal.sandbox.config") |
||||
'cabal-repl) |
||||
((and (locate-dominating-file default-directory "stack.yaml") |
||||
(executable-find "stack")) |
||||
'stack-ghci) |
||||
((locate-dominating-file |
||||
default-directory |
||||
(lambda (d) |
||||
(cl-find-if (lambda (f) (string-match-p ".\\.cabal\\'" f)) (directory-files d)))) |
||||
'cabal-repl) |
||||
(t 'ghci)) |
||||
haskell-process-type)) |
||||
|
||||
(provide 'haskell-customize) |
@ -1,744 +0,0 @@
|
||||
;;; haskell-debug.el --- Debugging mode via GHCi -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright (c) 2014 Chris Done. All rights reserved. |
||||
|
||||
;; This file is free software; you can redistribute it and/or modify |
||||
;; it under the terms of the GNU General Public License as published by |
||||
;; the Free Software Foundation; either version 3, or (at your option) |
||||
;; any later version. |
||||
|
||||
;; This file is distributed in the hope that it will be useful, |
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
;; GNU General Public License for more details. |
||||
|
||||
;; You should have received a copy of the GNU General Public License |
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cl-lib) |
||||
(require 'haskell-session) |
||||
(require 'haskell-process) |
||||
(require 'haskell-interactive-mode) |
||||
(require 'haskell-font-lock) |
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
;; Configuration |
||||
|
||||
;;;###autoload |
||||
(defgroup haskell-debug nil |
||||
"Settings for debugging support." |
||||
:link '(custom-manual "(haskell-mode)haskell-debug") |
||||
:group 'haskell) |
||||
|
||||
;;;###autoload |
||||
(defface haskell-debug-warning-face |
||||
'((t :inherit 'compilation-warning)) |
||||
"Face for warnings." |
||||
:group 'haskell-debug) |
||||
|
||||
;;;###autoload |
||||
(defface haskell-debug-trace-number-face |
||||
'((t :weight bold :background "#f5f5f5")) |
||||
"Face for numbers in backtrace." |
||||
:group 'haskell-debug) |
||||
|
||||
;;;###autoload |
||||
(defface haskell-debug-newline-face |
||||
'((t :weight bold :background "#f0f0f0")) |
||||
"Face for newlines in trace steps." |
||||
:group 'haskell-debug) |
||||
|
||||
;;;###autoload |
||||
(defface haskell-debug-keybinding-face |
||||
'((t :inherit 'font-lock-type-face :weight bold)) |
||||
"Face for keybindings." |
||||
:group 'haskell-debug) |
||||
|
||||
;;;###autoload |
||||
(defface haskell-debug-heading-face |
||||
'((t :inherit 'font-lock-keyword-face)) |
||||
"Face for headings." |
||||
:group 'haskell-debug) |
||||
|
||||
;;;###autoload |
||||
(defface haskell-debug-muted-face |
||||
'((t :foreground "#999")) |
||||
"Face for muteds." |
||||
:group 'haskell-debug) |
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
;; Mode |
||||
|
||||
(define-derived-mode haskell-debug-mode |
||||
text-mode "Debug" |
||||
"Major mode for debugging Haskell via GHCi.") |
||||
|
||||
(define-key haskell-debug-mode-map (kbd "g") 'haskell-debug/refresh) |
||||
(define-key haskell-debug-mode-map (kbd "s") 'haskell-debug/step) |
||||
(define-key haskell-debug-mode-map (kbd "t") 'haskell-debug/trace) |
||||
(define-key haskell-debug-mode-map (kbd "d") 'haskell-debug/delete) |
||||
(define-key haskell-debug-mode-map (kbd "b") 'haskell-debug/break-on-function) |
||||
(define-key haskell-debug-mode-map (kbd "a") 'haskell-debug/abandon) |
||||
(define-key haskell-debug-mode-map (kbd "c") 'haskell-debug/continue) |
||||
(define-key haskell-debug-mode-map (kbd "p") 'haskell-debug/previous) |
||||
(define-key haskell-debug-mode-map (kbd "n") 'haskell-debug/next) |
||||
(define-key haskell-debug-mode-map (kbd "RET") 'haskell-debug/select) |
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
;; Globals |
||||
|
||||
(defvar haskell-debug-history-cache nil |
||||
"Cache of the tracing history.") |
||||
|
||||
(defvar haskell-debug-bindings-cache nil |
||||
"Cache of the current step's bindings.") |
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
;; Macros |
||||
|
||||
(defmacro haskell-debug-with-breakpoints (&rest body) |
||||
"Breakpoints need to exist to start stepping." |
||||
`(if (haskell-debug-get-breakpoints) |
||||
,@body |
||||
(error "No breakpoints to step into!"))) |
||||
|
||||
(defmacro haskell-debug-with-modules (&rest body) |
||||
"Modules need to exist to do debugging stuff." |
||||
`(if (haskell-debug-get-modules) |
||||
,@body |
||||
(error "No modules loaded!"))) |
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
;; Interactive functions |
||||
|
||||
(defun haskell-debug/select () |
||||
"Select whatever is at point." |
||||
(interactive) |
||||
(cond |
||||
((get-text-property (point) 'break) |
||||
(let ((break (get-text-property (point) 'break))) |
||||
(haskell-debug-highlight (plist-get break :path) |
||||
(plist-get break :span)))) |
||||
((get-text-property (point) 'module) |
||||
(let ((break (get-text-property (point) 'module))) |
||||
(haskell-debug-highlight (plist-get break :path)))))) |
||||
|
||||
(defun haskell-debug/abandon () |
||||
"Abandon the current computation." |
||||
(interactive) |
||||
(haskell-debug-with-breakpoints |
||||
(haskell-process-queue-sync-request (haskell-debug-process) ":abandon") |
||||
(message "Computation abandoned.") |
||||
(setq haskell-debug-history-cache nil) |
||||
(setq haskell-debug-bindings-cache nil) |
||||
(haskell-debug/refresh))) |
||||
|
||||
(defun haskell-debug/continue () |
||||
"Continue the current computation." |
||||
(interactive) |
||||
(haskell-debug-with-breakpoints |
||||
(haskell-process-queue-sync-request (haskell-debug-process) ":continue") |
||||
(message "Computation continued.") |
||||
(setq haskell-debug-history-cache nil) |
||||
(setq haskell-debug-bindings-cache nil) |
||||
(haskell-debug/refresh))) |
||||
|
||||
(defun haskell-debug/break-on-function () |
||||
"Break on function IDENT." |
||||
(interactive) |
||||
(haskell-debug-with-modules |
||||
(let ((ident (read-from-minibuffer "Function: " |
||||
(haskell-ident-at-point)))) |
||||
(haskell-process-queue-sync-request |
||||
(haskell-debug-process) |
||||
(concat ":break " |
||||
ident)) |
||||
(message "Breaking on function: %s" ident) |
||||
(haskell-debug/refresh)))) |
||||
|
||||
(defun haskell-debug/start-step (expr) |
||||
"Start stepping EXPR." |
||||
(interactive (list (read-from-minibuffer "Expression to step through: "))) |
||||
(haskell-debug/step expr)) |
||||
|
||||
(defun haskell-debug/breakpoint-numbers () |
||||
"List breakpoint numbers." |
||||
(interactive) |
||||
(let ((breakpoints (mapcar (lambda (breakpoint) |
||||
(number-to-string (plist-get breakpoint :number))) |
||||
(haskell-debug-get-breakpoints)))) |
||||
(if (null breakpoints) |
||||
(message "No breakpoints.") |
||||
(message "Breakpoint(s): %s" |
||||
(mapconcat #'identity |
||||
breakpoints |
||||
", "))))) |
||||
|
||||
(defun haskell-debug/next () |
||||
"Go to next step to inspect bindings." |
||||
(interactive) |
||||
(haskell-debug-with-breakpoints |
||||
(haskell-debug-navigate "forward"))) |
||||
|
||||
(defun haskell-debug/previous () |
||||
"Go to previous step to inspect the bindings." |
||||
(interactive) |
||||
(haskell-debug-with-breakpoints |
||||
(haskell-debug-navigate "back"))) |
||||
|
||||
(defun haskell-debug/refresh () |
||||
"Refresh the debugger buffer." |
||||
(interactive) |
||||
(with-current-buffer (haskell-debug-buffer-name (haskell-debug-session)) |
||||
(cd (haskell-session-current-dir (haskell-debug-session))) |
||||
(let ((inhibit-read-only t) |
||||
(p (point))) |
||||
(erase-buffer) |
||||
(insert (propertize (concat "Debugging " |
||||
(haskell-session-name (haskell-debug-session)) |
||||
"\n\n") |
||||
'face `((:weight bold)))) |
||||
(let ((modules (haskell-debug-get-modules)) |
||||
(breakpoints (haskell-debug-get-breakpoints)) |
||||
(context (haskell-debug-get-context)) |
||||
(history (haskell-debug-get-history))) |
||||
(unless modules |
||||
(insert (propertize "You have to load a module to start debugging." |
||||
'face |
||||
'haskell-debug-warning-face) |
||||
"\n\n")) |
||||
(haskell-debug-insert-bindings modules breakpoints context) |
||||
(when modules |
||||
(haskell-debug-insert-current-context context history) |
||||
(haskell-debug-insert-breakpoints breakpoints)) |
||||
(haskell-debug-insert-modules modules)) |
||||
(insert "\n") |
||||
(goto-char (min (point-max) p))))) |
||||
|
||||
(defun haskell-debug/delete () |
||||
"Delete whatever's at the point." |
||||
(interactive) |
||||
(cond |
||||
((get-text-property (point) 'break) |
||||
(let ((break (get-text-property (point) 'break))) |
||||
(when (y-or-n-p (format "Delete breakpoint #%d?" |
||||
(plist-get break :number))) |
||||
(haskell-process-queue-sync-request |
||||
(haskell-debug-process) |
||||
(format ":delete %d" |
||||
(plist-get break :number))) |
||||
(haskell-debug/refresh)))))) |
||||
|
||||
(defun haskell-debug/trace () |
||||
"Trace the expression." |
||||
(interactive) |
||||
(haskell-debug-with-modules |
||||
(haskell-debug-with-breakpoints |
||||
(let ((expr (read-from-minibuffer "Expression to trace: " |
||||
(haskell-ident-at-point)))) |
||||
(haskell-process-queue-sync-request |
||||
(haskell-debug-process) |
||||
(concat ":trace " expr)) |
||||
(message "Tracing expression: %s" expr) |
||||
(haskell-debug/refresh))))) |
||||
|
||||
(defun haskell-debug/step (&optional expr) |
||||
"Step into the next function." |
||||
(interactive) |
||||
(haskell-debug-with-breakpoints |
||||
(let* ((breakpoints (haskell-debug-get-breakpoints)) |
||||
(context (haskell-debug-get-context)) |
||||
(string |
||||
(haskell-process-queue-sync-request |
||||
(haskell-debug-process) |
||||
(if expr |
||||
(concat ":step " expr) |
||||
":step")))) |
||||
(cond |
||||
((string= string "not stopped at a breakpoint\n") |
||||
(if haskell-debug-bindings-cache |
||||
(progn (setq haskell-debug-bindings-cache nil) |
||||
(haskell-debug/refresh)) |
||||
(call-interactively 'haskell-debug/start-step))) |
||||
(t (let ((maybe-stopped-at (haskell-debug-parse-stopped-at string))) |
||||
(cond |
||||
(maybe-stopped-at |
||||
(setq haskell-debug-bindings-cache |
||||
maybe-stopped-at) |
||||
(message "Computation paused.") |
||||
(haskell-debug/refresh)) |
||||
(t |
||||
(if context |
||||
(message "Computation finished.") |
||||
(when (y-or-n-p "Computation completed without breaking. Reload the module and retry?") |
||||
(message "Reloading and resetting breakpoints...") |
||||
(haskell-interactive-mode-reset-error (haskell-debug-session)) |
||||
(cl-loop for break in breakpoints |
||||
do (haskell-process-queue-sync-request |
||||
(haskell-debug-process) |
||||
(concat ":load " (plist-get break :path)))) |
||||
(cl-loop for break in breakpoints |
||||
do (haskell-debug-break break)) |
||||
(haskell-debug/step expr))))))))) |
||||
(haskell-debug/refresh))) |
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
;; Internal functions |
||||
|
||||
(defun haskell-debug-session () |
||||
"Get the Haskell session." |
||||
(or (haskell-session-maybe) |
||||
(error "No Haskell session associated with this debug |
||||
buffer. Please just close the buffer and start again."))) |
||||
|
||||
(defun haskell-debug-process () |
||||
"Get the Haskell session." |
||||
(or (haskell-session-process (haskell-session-maybe)) |
||||
(error "No Haskell session associated with this debug |
||||
buffer. Please just close the buffer and start again."))) |
||||
|
||||
(defun haskell-debug-buffer-name (session) |
||||
"The debug buffer name for the current session." |
||||
(format "*debug:%s*" |
||||
(haskell-session-name session))) |
||||
|
||||
(defun haskell-debug-get-breakpoints () |
||||
"Get the list of breakpoints currently set." |
||||
(let ((string (haskell-process-queue-sync-request |
||||
(haskell-debug-process) |
||||
":show breaks"))) |
||||
(if (string= string "No active breakpoints.\n") |
||||
(list) |
||||
(mapcar #'haskell-debug-parse-break-point |
||||
(haskell-debug-split-string string))))) |
||||
|
||||
(defun haskell-debug-get-modules () |
||||
"Get the list of modules currently set." |
||||
(let ((string (haskell-process-queue-sync-request |
||||
(haskell-debug-process) |
||||
":show modules"))) |
||||
(if (string= string "") |
||||
(list) |
||||
(mapcar #'haskell-debug-parse-module |
||||
(haskell-debug-split-string string))))) |
||||
|
||||
(defun haskell-debug-get-context () |
||||
"Get the current context." |
||||
(let ((string (haskell-process-queue-sync-request |
||||
(haskell-debug-process) |
||||
":show context"))) |
||||
(if (string= string "") |
||||
nil |
||||
(haskell-debug-parse-context string)))) |
||||
|
||||
(defun haskell-debug-get-history () |
||||
"Get the step history." |
||||
(let ((string (haskell-process-queue-sync-request |
||||
(haskell-debug-process) |
||||
":history"))) |
||||
(if (or (string= string "") |
||||
(string= string "Not stopped at a breakpoint\n")) |
||||
nil |
||||
(if (string= string "Empty history. Perhaps you forgot to use :trace?\n") |
||||
nil |
||||
(let ((entries (mapcar #'haskell-debug-parse-history-entry |
||||
(cl-remove-if (lambda (line) (or (string= "<end of history>" line) |
||||
(string= "..." line))) |
||||
(haskell-debug-split-string string))))) |
||||
(setq haskell-debug-history-cache |
||||
entries) |
||||
entries))))) |
||||
|
||||
(defun haskell-debug-insert-bindings (modules breakpoints context) |
||||
"Insert a list of bindings." |
||||
(if breakpoints |
||||
(progn (haskell-debug-insert-binding "t" "trace an expression") |
||||
(haskell-debug-insert-binding "s" "step into an expression") |
||||
(haskell-debug-insert-binding "b" "breakpoint" t)) |
||||
(progn |
||||
(when modules |
||||
(haskell-debug-insert-binding "b" "breakpoint")) |
||||
(when breakpoints |
||||
(haskell-debug-insert-binding "s" "step into an expression" t)))) |
||||
(when breakpoints |
||||
(haskell-debug-insert-binding "d" "delete breakpoint")) |
||||
(when context |
||||
(haskell-debug-insert-binding "a" "abandon context") |
||||
(haskell-debug-insert-binding "c" "continue" t)) |
||||
(when context |
||||
(haskell-debug-insert-binding "p" "previous step") |
||||
(haskell-debug-insert-binding "n" "next step" t)) |
||||
(haskell-debug-insert-binding "g" "refresh" t) |
||||
(insert "\n")) |
||||
|
||||
(defun haskell-debug-insert-current-context (context history) |
||||
"Insert the current context." |
||||
(haskell-debug-insert-header "Context") |
||||
(if context |
||||
(haskell-debug-insert-context context history) |
||||
(haskell-debug-insert-debug-finished)) |
||||
(insert "\n")) |
||||
|
||||
(defun haskell-debug-insert-breakpoints (breakpoints) |
||||
"insert the list of breakpoints." |
||||
(haskell-debug-insert-header "Breakpoints") |
||||
(if (null breakpoints) |
||||
(haskell-debug-insert-muted "No active breakpoints.") |
||||
(cl-loop for break in breakpoints |
||||
do (insert (propertize (format "%d" |
||||
(plist-get break :number)) |
||||
'face `((:weight bold)) |
||||
'break break) |
||||
(haskell-debug-muted " - ") |
||||
(propertize (plist-get break :module) |
||||
'break break |
||||
'break break) |
||||
(haskell-debug-muted |
||||
(format " (%d:%d)" |
||||
(plist-get (plist-get break :span) :start-line) |
||||
(plist-get (plist-get break :span) :start-col))) |
||||
"\n"))) |
||||
(insert "\n")) |
||||
|
||||
(defun haskell-debug-insert-modules (modules) |
||||
"Insert the list of modules." |
||||
(haskell-debug-insert-header "Modules") |
||||
(if (null modules) |
||||
(haskell-debug-insert-muted "No loaded modules.") |
||||
(progn (cl-loop for module in modules |
||||
do (insert (propertize (plist-get module :module) |
||||
'module module |
||||
'face `((:weight bold))) |
||||
(haskell-debug-muted " - ") |
||||
(propertize (file-name-nondirectory (plist-get module :path)) |
||||
'module module)) |
||||
do (insert "\n"))))) |
||||
|
||||
(defun haskell-debug-split-string (string) |
||||
"Split GHCi's line-based output, stripping the trailing newline." |
||||
(split-string string "\n" t)) |
||||
|
||||
(defun haskell-debug-parse-context (string) |
||||
"Parse the context." |
||||
(cond |
||||
((string-match "^--> \\(.+\\)\n \\(.+\\)" string) |
||||
(let ((name (match-string 1 string)) |
||||
(stopped (haskell-debug-parse-stopped-at (match-string 2 string)))) |
||||
(list :name name |
||||
:path (plist-get stopped :path) |
||||
:span (plist-get stopped :span)))))) |
||||
|
||||
(defun haskell-debug-insert-binding (binding desc &optional end) |
||||
"Insert a helpful keybinding." |
||||
(insert (propertize binding 'face 'haskell-debug-keybinding-face) |
||||
(haskell-debug-muted " - ") |
||||
desc |
||||
(if end |
||||
"\n" |
||||
(haskell-debug-muted ", ")))) |
||||
|
||||
(defun haskell-debug-insert-header (title) |
||||
"Insert a header title." |
||||
(insert (propertize title |
||||
'face 'haskell-debug-heading-face) |
||||
"\n\n")) |
||||
|
||||
(defun haskell-debug-insert-context (context history) |
||||
"Insert the context and history." |
||||
(when context |
||||
(insert (propertize (plist-get context :name) 'face `((:weight bold))) |
||||
(haskell-debug-muted " - ") |
||||
(file-name-nondirectory (plist-get context :path)) |
||||
(haskell-debug-muted " (stopped)") |
||||
"\n")) |
||||
(when haskell-debug-bindings-cache |
||||
(insert "\n") |
||||
(let ((bindings haskell-debug-bindings-cache)) |
||||
(insert |
||||
(haskell-debug-get-span-string |
||||
(plist-get bindings :path) |
||||
(plist-get bindings :span))) |
||||
(insert "\n\n") |
||||
(cl-loop for binding in (plist-get bindings :types) |
||||
do (insert (haskell-fontify-as-mode binding 'haskell-mode) |
||||
"\n")))) |
||||
(let ((history (or history |
||||
(list (haskell-debug-make-fake-history context))))) |
||||
(when history |
||||
(insert "\n") |
||||
(haskell-debug-insert-history history)))) |
||||
|
||||
(defun haskell-debug-insert-debug-finished () |
||||
"Insert message that no debugging is happening, but if there is |
||||
some old history, then display that." |
||||
(if haskell-debug-history-cache |
||||
(progn (haskell-debug-insert-muted "Finished debugging.") |
||||
(insert "\n") |
||||
(haskell-debug-insert-history haskell-debug-history-cache)) |
||||
(haskell-debug-insert-muted "Not debugging right now."))) |
||||
|
||||
(defun haskell-debug-insert-muted (text) |
||||
"Insert some muted text." |
||||
(insert (haskell-debug-muted text) |
||||
"\n")) |
||||
|
||||
(defun haskell-debug-muted (text) |
||||
"Make some muted text." |
||||
(propertize text 'face 'haskell-debug-muted-face)) |
||||
|
||||
(defun haskell-debug-parse-logged (string) |
||||
"Parse the logged breakpoint." |
||||
(cond |
||||
((string= "no more logged breakpoints\n" string) |
||||
nil) |
||||
((string= "already at the beginning of the history\n" string) |
||||
nil) |
||||
(t |
||||
(with-temp-buffer |
||||
(insert string) |
||||
(goto-char (point-min)) |
||||
(list :path (progn (search-forward " at ") |
||||
(buffer-substring-no-properties |
||||
(point) |
||||
(1- (search-forward ":")))) |
||||
:span (haskell-debug-parse-span |
||||
(buffer-substring-no-properties |
||||
(point) |
||||
(line-end-position))) |
||||
:types (progn (forward-line) |
||||
(haskell-debug-split-string |
||||
(buffer-substring-no-properties |
||||
(point) |
||||
(point-max))))))))) |
||||
|
||||
(defun haskell-debug-parse-stopped-at (string) |
||||
"Parse the location stopped at from the given string. |
||||
|
||||
For example: |
||||
|
||||
Stopped at /home/foo/project/src/x.hs:6:25-36 |
||||
|
||||
" |
||||
(let ((index (string-match "Stopped at \\([^:]+\\):\\(.+\\)\n?" |
||||
string))) |
||||
(when index |
||||
(list :path (match-string 1 string) |
||||
:span (haskell-debug-parse-span (match-string 2 string)) |
||||
:types (cdr (haskell-debug-split-string (substring string index))))))) |
||||
|
||||
(defun haskell-debug-get-span-string (path span) |
||||
"Get the string from the PATH and the SPAN." |
||||
(save-window-excursion |
||||
(find-file path) |
||||
(buffer-substring |
||||
(save-excursion |
||||
(goto-char (point-min)) |
||||
(forward-line (1- (plist-get span :start-line))) |
||||
(forward-char (1- (plist-get span :start-col))) |
||||
(point)) |
||||
(save-excursion |
||||
(goto-char (point-min)) |
||||
(forward-line (1- (plist-get span :end-line))) |
||||
(forward-char (plist-get span :end-col)) |
||||
(point))))) |
||||
|
||||
(defun haskell-debug-make-fake-history (context) |
||||
"Make a fake history item." |
||||
(list :index -1 |
||||
:path (plist-get context :path) |
||||
:span (plist-get context :span))) |
||||
|
||||
(defun haskell-debug-insert-history (history) |
||||
"Insert tracing HISTORY." |
||||
(let ((i (length history))) |
||||
(cl-loop for span in history |
||||
do (let ((string (haskell-debug-get-span-string |
||||
(plist-get span :path) |
||||
(plist-get span :span)))) |
||||
(insert (propertize (format "%4d" i) |
||||
'face 'haskell-debug-trace-number-face) |
||||
" " |
||||
(haskell-debug-preview-span |
||||
(plist-get span :span) |
||||
string |
||||
t) |
||||
"\n") |
||||
(setq i (1- i)))))) |
||||
|
||||
(defun haskell-debug-parse-span (string) |
||||
"Parse a source span from a string. |
||||
|
||||
Examples: |
||||
|
||||
(5,1)-(6,37) |
||||
6:25-36 |
||||
5:20 |
||||
|
||||
People like to make other people's lives interesting by making |
||||
variances in source span notation." |
||||
(cond |
||||
((string-match "\\([0-9]+\\):\\([0-9]+\\)-\\([0-9]+\\)" |
||||
string) |
||||
(list :start-line (string-to-number (match-string 1 string)) |
||||
:start-col (string-to-number (match-string 2 string)) |
||||
:end-line (string-to-number (match-string 1 string)) |
||||
:end-col (string-to-number (match-string 3 string)))) |
||||
((string-match "\\([0-9]+\\):\\([0-9]+\\)" |
||||
string) |
||||
(list :start-line (string-to-number (match-string 1 string)) |
||||
:start-col (string-to-number (match-string 2 string)) |
||||
:end-line (string-to-number (match-string 1 string)) |
||||
:end-col (string-to-number (match-string 2 string)))) |
||||
((string-match "(\\([0-9]+\\),\\([0-9]+\\))-(\\([0-9]+\\),\\([0-9]+\\))" |
||||
string) |
||||
(list :start-line (string-to-number (match-string 1 string)) |
||||
:start-col (string-to-number (match-string 2 string)) |
||||
:end-line (string-to-number (match-string 3 string)) |
||||
:end-col (string-to-number (match-string 4 string)))) |
||||
(t (error "Unable to parse source span from string: %s" |
||||
string)))) |
||||
|
||||
(defun haskell-debug-preview-span (span string &optional collapsed) |
||||
"Make a one-line preview of the given expression." |
||||
(with-temp-buffer |
||||
(haskell-mode) |
||||
(insert string) |
||||
(when (/= 0 (plist-get span :start-col)) |
||||
(indent-rigidly (point-min) |
||||
(point-max) |
||||
1)) |
||||
(if (fboundp 'font-lock-ensure) |
||||
(font-lock-ensure) |
||||
(with-no-warnings (font-lock-fontify-buffer))) |
||||
(when (/= 0 (plist-get span :start-col)) |
||||
(indent-rigidly (point-min) |
||||
(point-max) |
||||
-1)) |
||||
(goto-char (point-min)) |
||||
(if collapsed |
||||
(replace-regexp-in-string |
||||
"\n[ ]*" |
||||
(propertize " " 'face 'haskell-debug-newline-face) |
||||
(buffer-substring (point-min) |
||||
(point-max))) |
||||
(buffer-string)))) |
||||
|
||||
(defun haskell-debug-start (session) |
||||
"Start the debug mode." |
||||
(setq buffer-read-only t) |
||||
(haskell-session-assign session) |
||||
(haskell-debug/refresh)) |
||||
|
||||
(defun haskell-debug () |
||||
"Start the debugger for the current Haskell (GHCi) session." |
||||
(interactive) |
||||
(let ((session (haskell-debug-session))) |
||||
(switch-to-buffer-other-window (haskell-debug-buffer-name session)) |
||||
(unless (eq major-mode 'haskell-debug-mode) |
||||
(haskell-debug-mode) |
||||
(haskell-debug-start session)))) |
||||
|
||||
(defun haskell-debug-break (break) |
||||
"Set BREAK breakpoint in module at line/col." |
||||
(haskell-process-queue-without-filters |
||||
(haskell-debug-process) |
||||
(format ":break %s %s %d" |
||||
(plist-get break :module) |
||||
(plist-get (plist-get break :span) :start-line) |
||||
(plist-get (plist-get break :span) :start-col)))) |
||||
|
||||
(defun haskell-debug-navigate (direction) |
||||
"Navigate in DIRECTION \"back\" or \"forward\"." |
||||
(let ((string (haskell-process-queue-sync-request |
||||
(haskell-debug-process) |
||||
(concat ":" direction)))) |
||||
(let ((bindings (haskell-debug-parse-logged string))) |
||||
(setq haskell-debug-bindings-cache |
||||
bindings) |
||||
(when (not bindings) |
||||
(message "No more %s results!" direction))) |
||||
(haskell-debug/refresh))) |
||||
|
||||
(defun haskell-debug-session-debugging-p (session) |
||||
"Does the session have a debugging buffer open?" |
||||
(not (not (get-buffer (haskell-debug-buffer-name session))))) |
||||
|
||||
(defun haskell-debug-highlight (path &optional span) |
||||
"Highlight the file at span." |
||||
(let ((p (make-overlay |
||||
(line-beginning-position) |
||||
(line-end-position)))) |
||||
(overlay-put p 'face `((:background "#eee"))) |
||||
(with-current-buffer |
||||
(if span |
||||
(save-window-excursion |
||||
(find-file path) |
||||
(current-buffer)) |
||||
(find-file path) |
||||
(current-buffer)) |
||||
(let ((o (when span |
||||
(make-overlay |
||||
(save-excursion |
||||
(goto-char (point-min)) |
||||
(forward-line (1- (plist-get span :start-line))) |
||||
(forward-char (1- (plist-get span :start-col))) |
||||
(point)) |
||||
(save-excursion |
||||
(goto-char (point-min)) |
||||
(forward-line (1- (plist-get span :end-line))) |
||||
(forward-char (plist-get span :end-col)) |
||||
(point)))))) |
||||
(when o |
||||
(overlay-put o 'face `((:background "#eee")))) |
||||
(sit-for 0.5) |
||||
(when o |
||||
(delete-overlay o)) |
||||
(delete-overlay p))))) |
||||
|
||||
(defun haskell-debug-parse-history-entry (string) |
||||
"Parse a history entry." |
||||
(if (string-match "^\\([-0-9]+\\)[ ]+:[ ]+\\([A-Za-z0-9_':]+\\)[ ]+(\\([^:]+\\):\\(.+?\\))$" |
||||
string) |
||||
(list :index (string-to-number (match-string 1 string)) |
||||
:name (match-string 2 string) |
||||
:path (match-string 3 string) |
||||
:span (haskell-debug-parse-span (match-string 4 string))) |
||||
(error "Unable to parse history entry: %s" string))) |
||||
|
||||
(defun haskell-debug-parse-module (string) |
||||
"Parse a module and path. |
||||
|
||||
For example: |
||||
|
||||
X ( /home/foo/X.hs, interpreted ) |
||||
|
||||
" |
||||
(if (string-match "^\\([^ ]+\\)[ ]+( \\([^ ]+?\\), [a-z]+ )$" |
||||
string) |
||||
(list :module (match-string 1 string) |
||||
:path (match-string 2 string)) |
||||
(error "Unable to parse module from string: %s" |
||||
string))) |
||||
|
||||
(defun haskell-debug-parse-break-point (string) |
||||
"Parse a breakpoint number, module and location from a string. |
||||
|
||||
For example: |
||||
|
||||
[13] Main /home/foo/src/x.hs:(5,1)-(6,37) |
||||
|
||||
" |
||||
(if (string-match "^\\[\\([0-9]+\\)\\] \\([^ ]+\\) \\([^:]+\\):\\(.+\\)$" |
||||
string) |
||||
(list :number (string-to-number (match-string 1 string)) |
||||
:module (match-string 2 string) |
||||
:path (match-string 3 string) |
||||
:span (haskell-debug-parse-span (match-string 4 string))) |
||||
(error "Unable to parse breakpoint from string: %s" |
||||
string))) |
||||
|
||||
(provide 'haskell-debug) |
||||
|
||||
;;; haskell-debug.el ends here |
@ -1,619 +0,0 @@
|
||||
;;; haskell-decl-scan.el --- Declaration scanning module for Haskell Mode -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright (C) 2004, 2005, 2007, 2009 Free Software Foundation, Inc. |
||||
;; Copyright (C) 1997-1998 Graeme E Moss |
||||
|
||||
;; Author: 1997-1998 Graeme E Moss <gem@cs.york.ac.uk> |
||||
;; Maintainer: Stefan Monnier <monnier@gnu.org> |
||||
;; Keywords: declarations menu files Haskell |
||||
;; URL: http://cvs.haskell.org/cgi-bin/cvsweb.cgi/fptools/CONTRIB/haskell-modes/emacs/haskell-decl-scan.el?rev=HEAD |
||||
|
||||
;; This file is not part of GNU Emacs. |
||||
|
||||
;; This file is free software; you can redistribute it and/or modify |
||||
;; it under the terms of the GNU General Public License as published by |
||||
;; the Free Software Foundation; either version 3, or (at your option) |
||||
;; any later version. |
||||
|
||||
;; This file is distributed in the hope that it will be useful, |
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
;; GNU General Public License for more details. |
||||
|
||||
;; You should have received a copy of the GNU General Public License |
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
;;; Commentary: |
||||
|
||||
;; Purpose: |
||||
;; |
||||
;; Top-level declarations are scanned and placed in a menu. Supports |
||||
;; full Latin1 Haskell 1.4 as well as literate scripts. |
||||
;; |
||||
;; |
||||
;; Installation: |
||||
;; |
||||
;; To turn declaration scanning on for all Haskell buffers under the |
||||
;; Haskell mode of Moss&Thorn, add this to .emacs: |
||||
;; |
||||
;; (add-hook 'haskell-mode-hook 'haskell-decl-scan-mode) |
||||
;; |
||||
;; Otherwise, call `haskell-decl-scan-mode'. |
||||
;; |
||||
;; |
||||
;; Customisation: |
||||
;; |
||||
;; M-x customize-group haskell-decl-scan |
||||
;; |
||||
;; |
||||
;; History: |
||||
;; |
||||
;; If you have any problems or suggestions, after consulting the list |
||||
;; below, email gem@cs.york.ac.uk quoting the version of the library |
||||
;; you are using, the version of Emacs you are using, and a small |
||||
;; example of the problem or suggestion. Note that this library |
||||
;; requires a reasonably recent version of Emacs. |
||||
;; |
||||
;; Uses `imenu' under Emacs. |
||||
;; |
||||
;; Version 1.2: |
||||
;; Added support for LaTeX-style literate scripts. |
||||
;; |
||||
;; Version 1.1: |
||||
;; Use own syntax table. Fixed bug for very small buffers. Use |
||||
;; markers instead of pointers (markers move with the text). |
||||
;; |
||||
;; Version 1.0: |
||||
;; Brought over from Haskell mode v1.1. |
||||
;; |
||||
;; |
||||
;; Present Limitations/Future Work (contributions are most welcome!): |
||||
;; |
||||
;; . Declarations requiring information extending beyond starting line |
||||
;; don't get scanned properly, eg. |
||||
;; > class Eq a => |
||||
;; > Test a |
||||
;; |
||||
;; . Comments placed in the midst of the first few lexemes of a |
||||
;; declaration will cause havoc, eg. |
||||
;; > infixWithComments :: Int -> Int -> Int |
||||
;; > x {-nastyComment-} `infixWithComments` y = x + y |
||||
;; but are not worth worrying about. |
||||
;; |
||||
;; . Would be nice to scan other top-level declarations such as |
||||
;; methods of a class, datatype field labels... any more? |
||||
;; |
||||
;; . Support for GreenCard? |
||||
;; |
||||
;; . Re-running (literate-)haskell-imenu should not cause the problems |
||||
;; that it does. The ability to turn off scanning would also be |
||||
;; useful. (Note that re-running (literate-)haskell-mode seems to |
||||
;; cause no problems.) |
||||
|
||||
;; All functions/variables start with |
||||
;; `(turn-(on/off)-)haskell-decl-scan' or `haskell-ds-'. |
||||
|
||||
;; The imenu support is based on code taken from `hugs-mode', |
||||
;; thanks go to Chris Van Humbeeck. |
||||
|
||||
;; Version. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cl-lib) |
||||
(require 'haskell-mode) |
||||
(require 'syntax) |
||||
(require 'imenu) |
||||
|
||||
;;;###autoload |
||||
(defgroup haskell-decl-scan nil |
||||
"Haskell declaration scanning (`imenu' support)." |
||||
:link '(custom-manual "(haskell-mode)haskell-decl-scan-mode") |
||||
:group 'haskell |
||||
:prefix "haskell-decl-scan-") |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-decl-scan-bindings-as-variables nil |
||||
"Whether to put top-level value bindings into a \"Variables\" category." |
||||
:group 'haskell-decl-scan |
||||
:type 'boolean) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-decl-scan-add-to-menubar t |
||||
"Whether to add a \"Declarations\" menu entry to menu bar." |
||||
:group 'haskell-decl-scan |
||||
:type 'boolean) |
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
;; General declaration scanning functions. |
||||
|
||||
(defvar haskell-ds-start-keywords-re |
||||
(concat "\\(\\<" |
||||
"class\\|data\\|i\\(mport\\|n\\(fix\\(\\|[lr]\\)\\|stance\\)\\)\\|" |
||||
"module\\|primitive\\|type\\|newtype" |
||||
"\\)\\>") |
||||
"Keywords that may start a declaration.") |
||||
|
||||
(defvar haskell-ds-syntax-table |
||||
(let ((table (copy-syntax-table haskell-mode-syntax-table))) |
||||
(modify-syntax-entry ?\' "w" table) |
||||
(modify-syntax-entry ?_ "w" table) |
||||
(modify-syntax-entry ?\\ "_" table) |
||||
table) |
||||
"Syntax table used for Haskell declaration scanning.") |
||||
|
||||
|
||||
(defun haskell-ds-get-variable (prefix) |
||||
"Return variable involved in value binding or type signature. |
||||
Assumes point is looking at the regexp PREFIX followed by the |
||||
start of a declaration (perhaps in the middle of a series of |
||||
declarations concerning a single variable). Otherwise return nil. |
||||
Point is not changed." |
||||
;; I think I can now handle all declarations bar those with comments |
||||
;; nested before the second lexeme. |
||||
(save-excursion |
||||
(with-syntax-table haskell-ds-syntax-table |
||||
(if (looking-at prefix) (goto-char (match-end 0))) |
||||
;; Keyword. |
||||
(if (looking-at haskell-ds-start-keywords-re) |
||||
nil |
||||
(or ;; Parenthesized symbolic variable. |
||||
(and (looking-at "(\\(\\s_+\\))") (match-string-no-properties 1)) |
||||
;; General case. |
||||
(if (looking-at |
||||
(if (eq ?\( (char-after)) |
||||
;; Skip paranthesised expression. |
||||
(progn |
||||
(forward-sexp) |
||||
;; Repeating this code and avoiding moving point if |
||||
;; possible speeds things up. |
||||
"\\(\\'\\)?\\s-*\\(\\s_+\\|`\\(\\sw+\\)`\\)") |
||||
"\\(\\sw+\\)?\\s-*\\(\\s_+\\|`\\(\\sw+\\)`\\)")) |
||||
(let ((match2 (match-string-no-properties 2))) |
||||
;; Weed out `::', `∷',`=' and `|' from potential infix |
||||
;; symbolic variable. |
||||
(if (member match2 '("::" "∷" "=" "|")) |
||||
;; Variable identifier. |
||||
(match-string-no-properties 1) |
||||
(if (eq (aref match2 0) ?\`) |
||||
;; Infix variable identifier. |
||||
(match-string-no-properties 3) |
||||
;; Infix symbolic variable. |
||||
match2)))) |
||||
;; Variable identifier. |
||||
(and (looking-at "\\sw+") (match-string-no-properties 0))))))) |
||||
|
||||
(defun haskell-ds-move-to-start-regexp (inc regexp) |
||||
"Move to beginning of line that succeeds/precedes (INC = 1/-1) |
||||
current line that starts with REGEXP and is not in `font-lock-comment-face'." |
||||
;; Making this defsubst instead of defun appears to have little or |
||||
;; no effect on efficiency. It is probably not called enough to do |
||||
;; so. |
||||
(while (and (= (forward-line inc) 0) |
||||
(or (not (looking-at regexp)) |
||||
(eq (get-text-property (point) 'face) |
||||
'font-lock-comment-face))))) |
||||
|
||||
(defun haskell-ds-move-to-start-regexp-skipping-comments (inc regexp) |
||||
"Like haskell-ds-move-to-start-regexp, but uses syntax-ppss to |
||||
skip comments" |
||||
(let (p) |
||||
(cl-loop |
||||
do (setq p (point)) |
||||
(haskell-ds-move-to-start-regexp inc regexp) |
||||
while (and (nth 4 (syntax-ppss)) |
||||
(/= p (point)))))) |
||||
|
||||
(defvar literate-haskell-ds-line-prefix "> ?" |
||||
"Regexp matching start of a line of Bird-style literate code. |
||||
Current value is \"> \" as we assume top-level declarations start |
||||
at column 3. Must not contain the special \"^\" regexp as we may |
||||
not use the regexp at the start of a regexp string. Note this is |
||||
only for `imenu' support.") |
||||
|
||||
(defvar haskell-ds-start-decl-re "\\(\\sw\\|(\\)" |
||||
"The regexp that starts a Haskell declaration.") |
||||
|
||||
(defvar literate-haskell-ds-start-decl-re |
||||
(concat literate-haskell-ds-line-prefix haskell-ds-start-decl-re) |
||||
"The regexp that starts a Bird-style literate Haskell declaration.") |
||||
|
||||
(defun haskell-ds-move-to-decl (direction bird-literate fix) |
||||
"General function for moving to the start of a declaration, |
||||
either forwards or backwards from point, with normal or with Bird-style |
||||
literate scripts. If DIRECTION is t, then forward, else backward. If |
||||
BIRD-LITERATE is t, then treat as Bird-style literate scripts, else |
||||
normal scripts. Returns point if point is left at the start of a |
||||
declaration, and nil otherwise, ie. because point is at the beginning |
||||
or end of the buffer and no declaration starts there. If FIX is t, |
||||
then point does not move if already at the start of a declaration." |
||||
;; As `haskell-ds-get-variable' cannot separate an infix variable |
||||
;; identifier out of a value binding with non-alphanumeric first |
||||
;; argument, this function will treat such value bindings as |
||||
;; separate from the declarations surrounding it. |
||||
(let ( ;; The variable typed or bound in the current series of |
||||
;; declarations. |
||||
name |
||||
;; The variable typed or bound in the new declaration. |
||||
newname |
||||
;; Hack to solve hard problem for Bird-style literate scripts |
||||
;; that start with a declaration. We are in the abyss if |
||||
;; point is before start of this declaration. |
||||
abyss |
||||
(line-prefix (if bird-literate literate-haskell-ds-line-prefix "")) |
||||
;; The regexp to match for the start of a declaration. |
||||
(start-decl-re (if bird-literate |
||||
literate-haskell-ds-start-decl-re |
||||
haskell-ds-start-decl-re)) |
||||
(increment (if direction 1 -1)) |
||||
(bound (if direction (point-max) (point-min)))) |
||||
;; Change syntax table. |
||||
(with-syntax-table haskell-ds-syntax-table |
||||
;; move to beginning of line that starts the "current |
||||
;; declaration" (dependent on DIRECTION and FIX), and then get |
||||
;; the variable typed or bound by this declaration, if any. |
||||
(let ( ;; Where point was at call of function. |
||||
(here (point)) |
||||
;; Where the declaration on this line (if any) starts. |
||||
(start (progn |
||||
(beginning-of-line) |
||||
;; Checking the face to ensure a declaration starts |
||||
;; here seems to be the only addition to make this |
||||
;; module support LaTeX-style literate scripts. |
||||
(if (and (looking-at start-decl-re) |
||||
(not (elt (syntax-ppss) 4))) |
||||
(match-beginning 1))))) |
||||
(if (and start |
||||
;; This complicated boolean determines whether we |
||||
;; should include the declaration that starts on the |
||||
;; current line as the "current declaration" or not. |
||||
(or (and (or (and direction (not fix)) |
||||
(and (not direction) fix)) |
||||
(>= here start)) |
||||
(and (or (and direction fix) |
||||
(and (not direction) (not fix))) |
||||
(> here start)))) |
||||
;; If so, we are already at start of the current line, so |
||||
;; do nothing. |
||||
() |
||||
;; If point was before start of a declaration on the first |
||||
;; line of the buffer (possible for Bird-style literate |
||||
;; scripts) then we are in the abyss. |
||||
(if (and start (bobp)) |
||||
(setq abyss t) |
||||
;; Otherwise we move to the start of the first declaration |
||||
;; on a line preceding the current one, skipping comments |
||||
(haskell-ds-move-to-start-regexp-skipping-comments -1 start-decl-re)))) |
||||
;; If we are in the abyss, position and return as appropriate. |
||||
(if abyss |
||||
(if (not direction) |
||||
nil |
||||
(re-search-forward (concat "\\=" line-prefix) nil t) |
||||
(point)) |
||||
;; Get the variable typed or bound by this declaration, if any. |
||||
(setq name (haskell-ds-get-variable line-prefix)) |
||||
(if (not name) |
||||
;; If no such variable, stop at the start of this |
||||
;; declaration if moving backward, or move to the next |
||||
;; declaration if moving forward. |
||||
(if direction |
||||
(haskell-ds-move-to-start-regexp-skipping-comments 1 start-decl-re)) |
||||
;; If there is a variable, find the first |
||||
;; succeeding/preceding declaration that does not type or |
||||
;; bind it. Check for reaching start/end of buffer and |
||||
;; comments. |
||||
(haskell-ds-move-to-start-regexp-skipping-comments increment start-decl-re) |
||||
(while (and (/= (point) bound) |
||||
(and (setq newname (haskell-ds-get-variable line-prefix)) |
||||
(string= name newname))) |
||||
(setq name newname) |
||||
(haskell-ds-move-to-start-regexp-skipping-comments increment start-decl-re)) |
||||
;; If we are going backward, and have either reached a new |
||||
;; declaration or the beginning of a buffer that does not |
||||
;; start with a declaration, move forward to start of next |
||||
;; declaration (which must exist). Otherwise, we are done. |
||||
(if (and (not direction) |
||||
(or (and (looking-at start-decl-re) |
||||
(not (string= name |
||||
;; Note we must not use |
||||
;; newname here as this may |
||||
;; not have been set if we |
||||
;; have reached the beginning |
||||
;; of the buffer. |
||||
(haskell-ds-get-variable |
||||
line-prefix)))) |
||||
(and (not (looking-at start-decl-re)) |
||||
(bobp)))) |
||||
(haskell-ds-move-to-start-regexp-skipping-comments 1 start-decl-re))) |
||||
;; Store whether we are at the start of a declaration or not. |
||||
;; Used to calculate final result. |
||||
(let ((at-start-decl (looking-at start-decl-re))) |
||||
;; If we are at the beginning of a line, move over |
||||
;; line-prefix, if present at point. |
||||
(if (bolp) |
||||
(re-search-forward (concat "\\=" line-prefix) (point-max) t)) |
||||
;; Return point if at the start of a declaration and nil |
||||
;; otherwise. |
||||
(if at-start-decl (point) nil)))))) |
||||
|
||||
(defun haskell-ds-bird-p () |
||||
(and (boundp 'haskell-literate) (eq haskell-literate 'bird))) |
||||
|
||||
(defun haskell-ds-backward-decl () |
||||
"Move backward to the first character that starts a top-level declaration. |
||||
A series of declarations concerning one variable is treated as one |
||||
declaration by this function. So, if point is within a top-level |
||||
declaration then move it to the start of that declaration. If point |
||||
is already at the start of a top-level declaration, then move it to |
||||
the start of the preceding declaration. Returns point if point is |
||||
left at the start of a declaration, and nil otherwise, ie. because |
||||
point is at the beginning of the buffer and no declaration starts |
||||
there." |
||||
(interactive) |
||||
(haskell-ds-move-to-decl nil (haskell-ds-bird-p) nil)) |
||||
|
||||
(defun haskell-ds-forward-decl () |
||||
"As `haskell-ds-backward-decl' but forward." |
||||
(interactive) |
||||
(haskell-ds-move-to-decl t (haskell-ds-bird-p) nil)) |
||||
|
||||
(defun haskell-ds-generic-find-next-decl (bird-literate) |
||||
"Find the name, position and type of the declaration at or after point. |
||||
Return ((NAME . (START-POSITION . NAME-POSITION)) . TYPE) |
||||
if one exists and nil otherwise. The start-position is at the start |
||||
of the declaration, and the name-position is at the start of the name |
||||
of the declaration. The name is a string, the positions are buffer |
||||
positions and the type is one of the symbols \"variable\", \"datatype\", |
||||
\"class\", \"import\" and \"instance\"." |
||||
(let ( ;; The name, type and name-position of the declaration to |
||||
;; return. |
||||
name |
||||
type |
||||
name-pos |
||||
;; Buffer positions marking the start and end of the space |
||||
;; containing a declaration. |
||||
start |
||||
end) |
||||
;; Change to declaration scanning syntax. |
||||
(with-syntax-table haskell-ds-syntax-table |
||||
;; Stop when we are at the end of the buffer or when a valid |
||||
;; declaration is grabbed. |
||||
(while (not (or (eobp) name)) |
||||
;; Move forward to next declaration at or after point. |
||||
(haskell-ds-move-to-decl t bird-literate t) |
||||
;; Start and end of search space is currently just the starting |
||||
;; line of the declaration. |
||||
(setq start (point) |
||||
end (line-end-position)) |
||||
(cond |
||||
;; If the start of the top-level declaration does not begin |
||||
;; with a starting keyword, then (if legal) must be a type |
||||
;; signature or value binding, and the variable concerned is |
||||
;; grabbed. |
||||
((not (looking-at haskell-ds-start-keywords-re)) |
||||
(setq name (haskell-ds-get-variable "")) |
||||
(if name |
||||
(progn |
||||
(setq type 'variable) |
||||
(re-search-forward (regexp-quote name) end t) |
||||
(setq name-pos (match-beginning 0))))) |
||||
;; User-defined datatype declaration. |
||||
((re-search-forward "\\=\\(data\\|newtype\\|type\\)\\>" end t) |
||||
(re-search-forward "=>" end t) |
||||
(if (looking-at "[ \t]*\\(\\sw+\\)") |
||||
(progn |
||||
(setq name (match-string-no-properties 1)) |
||||
(setq name-pos (match-beginning 1)) |
||||
(setq type 'datatype)))) |
||||
;; Class declaration. |
||||
((re-search-forward "\\=class\\>" end t) |
||||
(re-search-forward "=>" end t) |
||||
(if (looking-at "[ \t]*\\(\\sw+\\)") |
||||
(progn |
||||
(setq name (match-string-no-properties 1)) |
||||
(setq name-pos (match-beginning 1)) |
||||
(setq type 'class)))) |
||||
;; Import declaration. |
||||
((looking-at "import[ \t]+\\(?:safe[\t ]+\\)?\\(?:qualified[ \t]+\\)?\\(?:\"[^\"]*\"[\t ]+\\)?\\(\\(?:\\sw\\|.\\)+\\)") |
||||
(setq name (match-string-no-properties 1)) |
||||
(setq name-pos (match-beginning 1)) |
||||
(setq type 'import)) |
||||
;; Instance declaration. |
||||
((re-search-forward "\\=instance[ \t]+" end t) |
||||
(re-search-forward "=>[ \t]+" end t) |
||||
;; The instance "title" starts just after the `instance' (and |
||||
;; any context) and finishes just before the _first_ `where' |
||||
;; if one exists. This solution is ugly, but I can't find a |
||||
;; nicer one---a simple regexp will pick up the last `where', |
||||
;; which may be rare but nevertheless... |
||||
(setq name-pos (point)) |
||||
(setq name (buffer-substring-no-properties |
||||
(point) |
||||
(progn |
||||
;; Look for a `where'. |
||||
(if (re-search-forward "\\<where\\>" end t) |
||||
;; Move back to just before the `where'. |
||||
(progn |
||||
(re-search-backward "\\s-where") |
||||
(point)) |
||||
;; No `where' so move to last non-whitespace |
||||
;; before `end'. |
||||
(progn |
||||
(goto-char end) |
||||
(skip-chars-backward " \t") |
||||
(point)))))) |
||||
;; If we did not manage to extract a name, cancel this |
||||
;; declaration (eg. when line ends in "=> "). |
||||
(if (string-match "^[ \t]*$" name) (setq name nil)) |
||||
(setq type 'instance))) |
||||
;; Move past start of current declaration. |
||||
(goto-char end)) |
||||
;; If we have a valid declaration then return it, otherwise return |
||||
;; nil. |
||||
(if name |
||||
(cons (cons name (cons (copy-marker start t) (copy-marker name-pos t))) |
||||
type) |
||||
nil)))) |
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
;; Declaration scanning via `imenu'. |
||||
|
||||
;;;###autoload |
||||
(defun haskell-ds-create-imenu-index () |
||||
"Function for finding `imenu' declarations in Haskell mode. |
||||
Finds all declarations (classes, variables, imports, instances and |
||||
datatypes) in a Haskell file for the `imenu' package." |
||||
;; Each list has elements of the form `(INDEX-NAME . INDEX-POSITION)'. |
||||
;; These lists are nested using `(INDEX-TITLE . INDEX-ALIST)'. |
||||
(let* ((bird-literate (haskell-ds-bird-p)) |
||||
(index-alist '()) |
||||
(index-class-alist '()) ;; Classes |
||||
(index-var-alist '()) ;; Variables |
||||
(index-imp-alist '()) ;; Imports |
||||
(index-inst-alist '()) ;; Instances |
||||
(index-type-alist '()) ;; Datatypes |
||||
;; Variables for showing progress. |
||||
(bufname (buffer-name)) |
||||
(divisor-of-progress (max 1 (/ (buffer-size) 100))) |
||||
;; The result we wish to return. |
||||
result) |
||||
(goto-char (point-min)) |
||||
;; Loop forwards from the beginning of the buffer through the |
||||
;; starts of the top-level declarations. |
||||
(while (< (point) (point-max)) |
||||
(message "Scanning declarations in %s... (%3d%%)" bufname |
||||
(/ (- (point) (point-min)) divisor-of-progress)) |
||||
;; Grab the next declaration. |
||||
(setq result (haskell-ds-generic-find-next-decl bird-literate)) |
||||
(if result |
||||
;; If valid, extract the components of the result. |
||||
(let* ((name-posns (car result)) |
||||
(name (car name-posns)) |
||||
(posns (cdr name-posns)) |
||||
(start-pos (car posns)) |
||||
(type (cdr result))) |
||||
;; Place `(name . start-pos)' in the correct alist. |
||||
(cl-case type |
||||
(variable |
||||
(setq index-var-alist |
||||
(cl-acons name start-pos index-var-alist))) |
||||
(datatype |
||||
(setq index-type-alist |
||||
(cl-acons name start-pos index-type-alist))) |
||||
(class |
||||
(setq index-class-alist |
||||
(cl-acons name start-pos index-class-alist))) |
||||
(import |
||||
(setq index-imp-alist |
||||
(cl-acons name start-pos index-imp-alist))) |
||||
(instance |
||||
(setq index-inst-alist |
||||
(cl-acons name start-pos index-inst-alist))))))) |
||||
;; Now sort all the lists, label them, and place them in one list. |
||||
(message "Sorting declarations in %s..." bufname) |
||||
(when index-type-alist |
||||
(push (cons "Datatypes" |
||||
(sort index-type-alist 'haskell-ds-imenu-label-cmp)) |
||||
index-alist)) |
||||
(when index-inst-alist |
||||
(push (cons "Instances" |
||||
(sort index-inst-alist 'haskell-ds-imenu-label-cmp)) |
||||
index-alist)) |
||||
(when index-imp-alist |
||||
(push (cons "Imports" |
||||
(sort index-imp-alist 'haskell-ds-imenu-label-cmp)) |
||||
index-alist)) |
||||
(when index-class-alist |
||||
(push (cons "Classes" |
||||
(sort index-class-alist 'haskell-ds-imenu-label-cmp)) |
||||
index-alist)) |
||||
(when index-var-alist |
||||
(if haskell-decl-scan-bindings-as-variables |
||||
(push (cons "Variables" |
||||
(sort index-var-alist 'haskell-ds-imenu-label-cmp)) |
||||
index-alist) |
||||
(setq index-alist (append index-alist |
||||
(sort index-var-alist 'haskell-ds-imenu-label-cmp))))) |
||||
(message "Sorting declarations in %s...done" bufname) |
||||
;; Return the alist. |
||||
index-alist)) |
||||
|
||||
(defun haskell-ds-imenu-label-cmp (el1 el2) |
||||
"Predicate to compare labels in lists from `haskell-ds-create-imenu-index'." |
||||
(string< (car el1) (car el2))) |
||||
|
||||
(defun haskell-ds-imenu () |
||||
"Install `imenu' for Haskell scripts." |
||||
(setq imenu-create-index-function 'haskell-ds-create-imenu-index) |
||||
(when haskell-decl-scan-add-to-menubar |
||||
(imenu-add-to-menubar "Declarations"))) |
||||
|
||||
;; The main functions to turn on declaration scanning. |
||||
;;;###autoload |
||||
(defun turn-on-haskell-decl-scan () |
||||
"Unconditionally activate `haskell-decl-scan-mode'." |
||||
(interactive) |
||||
(haskell-decl-scan-mode)) |
||||
(make-obsolete 'turn-on-haskell-decl-scan |
||||
'haskell-decl-scan-mode |
||||
"2015-07-23") |
||||
|
||||
;;;###autoload |
||||
(define-minor-mode haskell-decl-scan-mode |
||||
"Toggle Haskell declaration scanning minor mode on or off. |
||||
With a prefix argument ARG, enable minor mode if ARG is |
||||
positive, and disable it otherwise. If called from Lisp, enable |
||||
the mode if ARG is omitted or nil, and toggle it if ARG is `toggle'. |
||||
|
||||
See also info node `(haskell-mode)haskell-decl-scan-mode' for |
||||
more details about this minor mode. |
||||
|
||||
Top-level declarations are scanned and listed in the menu item |
||||
\"Declarations\" (if enabled via option |
||||
`haskell-decl-scan-add-to-menubar'). Selecting an item from this |
||||
menu will take point to the start of the declaration. |
||||
|
||||
\\[beginning-of-defun] and \\[end-of-defun] move forward and backward to the start of a declaration. |
||||
|
||||
This may link with `haskell-doc-mode'. |
||||
|
||||
For non-literate and LaTeX-style literate scripts, we assume the |
||||
common convention that top-level declarations start at the first |
||||
column. For Bird-style literate scripts, we assume the common |
||||
convention that top-level declarations start at the third column, |
||||
ie. after \"> \". |
||||
|
||||
Anything in `font-lock-comment-face' is not considered for a |
||||
declaration. Therefore, using Haskell font locking with comments |
||||
coloured in `font-lock-comment-face' improves declaration scanning. |
||||
|
||||
Literate Haskell scripts are supported: If the value of |
||||
`haskell-literate' (set automatically by `literate-haskell-mode') |
||||
is `bird', a Bird-style literate script is assumed. If it is nil |
||||
or `tex', a non-literate or LaTeX-style literate script is |
||||
assumed, respectively. |
||||
|
||||
Invokes `haskell-decl-scan-mode-hook' on activation." |
||||
:group 'haskell-decl-scan |
||||
|
||||
(kill-local-variable 'beginning-of-defun-function) |
||||
(kill-local-variable 'end-of-defun-function) |
||||
(kill-local-variable 'imenu-create-index-function) |
||||
(unless haskell-decl-scan-mode |
||||
;; How can we cleanly remove the "Declarations" menu? |
||||
(when haskell-decl-scan-add-to-menubar |
||||
(local-set-key [menu-bar index] nil))) |
||||
|
||||
(when haskell-decl-scan-mode |
||||
(set (make-local-variable 'beginning-of-defun-function) |
||||
'haskell-ds-backward-decl) |
||||
(set (make-local-variable 'end-of-defun-function) |
||||
'haskell-ds-forward-decl) |
||||
(haskell-ds-imenu))) |
||||
|
||||
|
||||
;; Provide ourselves: |
||||
|
||||
(provide 'haskell-decl-scan) |
||||
|
||||
;;; haskell-decl-scan.el ends here |
File diff suppressed because it is too large
Load Diff
@ -1,589 +0,0 @@
|
||||
;;; haskell-font-lock.el --- Font locking module for Haskell Mode -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. |
||||
;; Copyright 1997-1998 Graeme E Moss, and Tommy Thorn |
||||
|
||||
;; Author: 1997-1998 Graeme E Moss <gem@cs.york.ac.uk> |
||||
;; 1997-1998 Tommy Thorn <thorn@irisa.fr> |
||||
;; 2003 Dave Love <fx@gnu.org> |
||||
;; Keywords: faces files Haskell |
||||
|
||||
;; This file is not part of GNU Emacs. |
||||
|
||||
;; This file is free software; you can redistribute it and/or modify |
||||
;; it under the terms of the GNU General Public License as published by |
||||
;; the Free Software Foundation; either version 3, or (at your option) |
||||
;; any later version. |
||||
|
||||
;; This file is distributed in the hope that it will be useful, |
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
;; GNU General Public License for more details. |
||||
|
||||
;; You should have received a copy of the GNU General Public License |
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cl-lib) |
||||
(require 'haskell-mode) |
||||
(require 'font-lock) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-font-lock-symbols nil |
||||
"Display \\ and -> and such using symbols in fonts. |
||||
|
||||
This may sound like a neat trick, but be extra careful: it changes the |
||||
alignment and can thus lead to nasty surprises w.r.t layout." |
||||
:group 'haskell |
||||
:type 'boolean) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-font-lock-symbols-alist |
||||
'(("\\" . "λ") |
||||
("not" . "¬") |
||||
("->" . "→") |
||||
("<-" . "←") |
||||
("=>" . "⇒") |
||||
("()" . "∅") |
||||
("==" . "≡") |
||||
("/=" . "≢") |
||||
(">=" . "≥") |
||||
("<=" . "≤") |
||||
("!!" . "‼") |
||||
("&&" . "∧") |
||||
("||" . "∨") |
||||
("sqrt" . "√") |
||||
("undefined" . "⊥") |
||||
("pi" . "π") |
||||
("~>" . "⇝") ;; Omega language |
||||
;; ("~>" "↝") ;; less desirable |
||||
("-<" . "↢") ;; Paterson's arrow syntax |
||||
;; ("-<" "⤙") ;; nicer but uncommon |
||||
("::" . "∷") |
||||
("." "∘" ; "○" |
||||
;; Need a predicate here to distinguish the . used by |
||||
;; forall <foo> . <bar>. |
||||
haskell-font-lock-dot-is-not-composition) |
||||
("forall" . "∀")) |
||||
"Alist mapping Haskell symbols to chars. |
||||
|
||||
Each element has the form (STRING . COMPONENTS) or (STRING |
||||
COMPONENTS PREDICATE). |
||||
|
||||
STRING is the Haskell symbol. |
||||
COMPONENTS is a representation specification suitable as an argument to |
||||
`compose-region'. |
||||
PREDICATE if present is a function of one argument (the start position |
||||
of the symbol) which should return non-nil if this mapping should |
||||
be disabled at that position." |
||||
:type '(alist string string) |
||||
:group 'haskell) |
||||
|
||||
(defun haskell-font-lock-dot-is-not-composition (start) |
||||
"Return non-nil if the \".\" at START is not a composition operator. |
||||
This is the case if the \".\" is part of a \"forall <tvar> . <type>\"." |
||||
(save-excursion |
||||
(goto-char start) |
||||
(or (re-search-backward "\\<forall\\>[^.\"]*\\=" |
||||
(line-beginning-position) t) |
||||
(not (or |
||||
(string= " " (string (char-after start))) |
||||
(string= " " (string (char-before start)))))))) |
||||
|
||||
;;;###autoload |
||||
(defface haskell-keyword-face |
||||
'((t :inherit font-lock-keyword-face)) |
||||
"Face used to highlight Haskell keywords." |
||||
:group 'haskell) |
||||
|
||||
;;;###autoload |
||||
(defface haskell-constructor-face |
||||
'((t :inherit font-lock-type-face)) |
||||
"Face used to highlight Haskell constructors." |
||||
:group 'haskell) |
||||
|
||||
;; This used to be `font-lock-variable-name-face' but it doesn't result in |
||||
;; a highlighting that's consistent with other modes (it's mostly used |
||||
;; for function defintions). |
||||
(defface haskell-definition-face |
||||
'((t :inherit font-lock-function-name-face)) |
||||
"Face used to highlight Haskell definitions." |
||||
:group 'haskell) |
||||
|
||||
;; This is probably just wrong, but it used to use |
||||
;; `font-lock-function-name-face' with a result that was not consistent with |
||||
;; other major modes, so I just exchanged with `haskell-definition-face'. |
||||
;;;###autoload |
||||
(defface haskell-operator-face |
||||
'((t :inherit font-lock-variable-name-face)) |
||||
"Face used to highlight Haskell operators." |
||||
:group 'haskell) |
||||
|
||||
;;;###autoload |
||||
(defface haskell-pragma-face |
||||
'((t :inherit font-lock-preprocessor-face)) |
||||
"Face used to highlight Haskell pragmas." |
||||
:group 'haskell) |
||||
|
||||
;;;###autoload |
||||
(defface haskell-literate-comment-face |
||||
'((t :inherit font-lock-doc-face)) |
||||
"Face with which to fontify literate comments. |
||||
Inherit from `default' to avoid fontification of them." |
||||
:group 'haskell) |
||||
|
||||
(defun haskell-font-lock-compose-symbol (alist) |
||||
"Compose a sequence of ascii chars into a symbol. |
||||
Regexp match data 0 points to the chars." |
||||
;; Check that the chars should really be composed into a symbol. |
||||
(let* ((start (match-beginning 0)) |
||||
(end (match-end 0)) |
||||
(syntaxes (cond |
||||
((eq (char-syntax (char-after start)) ?w) '(?w)) |
||||
((eq (char-syntax (char-after start)) ?.) '(?.)) |
||||
;; Special case for the . used for qualified names. |
||||
((and (eq (char-after start) ?\.) (= end (1+ start))) |
||||
'(?_ ?\\ ?w)) |
||||
(t '(?_ ?\\)))) |
||||
sym-data) |
||||
(if (or (memq (char-syntax (or (char-before start) ?\ )) syntaxes) |
||||
(memq (char-syntax (or (char-after end) ?\ )) syntaxes) |
||||
(or (elt (syntax-ppss) 3) (elt (syntax-ppss) 4)) |
||||
(and (consp (setq sym-data (cdr (assoc (match-string 0) alist)))) |
||||
(let ((pred (cadr sym-data))) |
||||
(setq sym-data (car sym-data)) |
||||
(funcall pred start)))) |
||||
;; No composition for you. Let's actually remove any composition |
||||
;; we may have added earlier and which is now incorrect. |
||||
(remove-text-properties start end '(composition)) |
||||
;; That's a symbol alright, so add the composition. |
||||
(compose-region start end sym-data))) |
||||
;; Return nil because we're not adding any face property. |
||||
nil) |
||||
|
||||
(defun haskell-font-lock-symbols-keywords () |
||||
(when (and haskell-font-lock-symbols |
||||
haskell-font-lock-symbols-alist |
||||
(fboundp 'compose-region)) |
||||
`((,(regexp-opt (mapcar 'car haskell-font-lock-symbols-alist) t) |
||||
(0 (haskell-font-lock-compose-symbol ',haskell-font-lock-symbols-alist) |
||||
;; In Emacs-21, if the `override' field is nil, the face |
||||
;; expressions is only evaluated if the text has currently |
||||
;; no face. So force evaluation by using `keep'. |
||||
keep))))) |
||||
|
||||
;; The font lock regular expressions. |
||||
(defun haskell-font-lock-keywords-create (literate) |
||||
"Create fontification definitions for Haskell scripts. |
||||
Returns keywords suitable for `font-lock-keywords'." |
||||
(let* (;; Bird-style literate scripts start a line of code with |
||||
;; "^>", otherwise a line of code starts with "^". |
||||
(line-prefix (if (eq literate 'bird) "^> ?" "^")) |
||||
|
||||
(varid "\\b[[:lower:]_][[:alnum:]'_]*\\b") |
||||
;; We allow ' preceding conids because of DataKinds/PolyKinds |
||||
(conid "\\b'?[[:upper:]][[:alnum:]'_]*\\b") |
||||
(modid (concat "\\b" conid "\\(\\." conid "\\)*\\b")) |
||||
(qvarid (concat modid "\\." varid)) |
||||
(qconid (concat modid "\\." conid)) |
||||
(sym "\\s.+") |
||||
|
||||
;; Reserved identifiers |
||||
(reservedid |
||||
(concat "\\<" |
||||
;; `as', `hiding', and `qualified' are part of the import |
||||
;; spec syntax, but they are not reserved. |
||||
;; `_' can go in here since it has temporary word syntax. |
||||
;; (regexp-opt |
||||
;; '("case" "class" "data" "default" "deriving" "do" |
||||
;; "else" "if" "import" "in" "infix" "infixl" |
||||
;; "infixr" "instance" "let" "module" "newtype" "of" |
||||
;; "then" "type" "where" "_") t) |
||||
"\\(_\\|c\\(ase\\|lass\\)\\|d\\(ata\\|e\\(fault\\|riving\\)\\|o\\)\\|else\\|i\\(mport\\|n\\(fix[lr]?\\|stance\\)\\|[fn]\\)\\|let\\|module\\|mdo\\|newtype\\|of\\|rec\\|proc\\|t\\(hen\\|ype\\)\\|where\\)" |
||||
"\\>")) |
||||
|
||||
;; Top-level declarations |
||||
(topdecl-var |
||||
(concat line-prefix "\\(" varid "\\(?:\\s-*,\\s-*" varid "\\)*" "\\)\\s-*" |
||||
;; optionally allow for a single newline after identifier |
||||
;; NOTE: not supported for bird-style .lhs files |
||||
(if (eq literate 'bird) nil "\\([\n]\\s-+\\)?") |
||||
;; A toplevel declaration can be followed by a definition |
||||
;; (=), a type (::) or (∷), a guard, or a pattern which can |
||||
;; either be a variable, a constructor, a parenthesized |
||||
;; thingy, or an integer or a string. |
||||
"\\(" varid "\\|" conid "\\|::\\|∷\\|=\\||\\|\\s(\\|[0-9\"']\\)")) |
||||
(topdecl-var2 |
||||
(concat line-prefix "\\(" varid "\\|" conid "\\)\\s-*`\\(" varid "\\)`")) |
||||
(topdecl-bangpat |
||||
(concat line-prefix "\\(" varid "\\)\\s-*!")) |
||||
(topdecl-sym |
||||
(concat line-prefix "\\(" varid "\\|" conid "\\)\\s-*\\(" sym "\\)")) |
||||
(topdecl-sym2 (concat line-prefix "(\\(" sym "\\))")) |
||||
|
||||
keywords) |
||||
|
||||
(setq keywords |
||||
`(;; NOTICE the ordering below is significant |
||||
;; |
||||
("^#.*$" 0 'font-lock-preprocessor-face t) |
||||
|
||||
,@(haskell-font-lock-symbols-keywords) |
||||
|
||||
(,reservedid 1 'haskell-keyword-face) |
||||
|
||||
;; Special case for `as', `hiding', `safe' and `qualified', which are |
||||
;; keywords in import statements but are not otherwise reserved. |
||||
("\\<import[ \t]+\\(?:\\(safe\\>\\)[ \t]*\\)?\\(?:\\(qualified\\>\\)[ \t]*\\)?\\(?:\"[^\"]*\"[\t ]*\\)?[^ \t\n()]+[ \t]*\\(?:\\(\\<as\\>\\)[ \t]*[^ \t\n()]+[ \t]*\\)?\\(\\<hiding\\>\\)?" |
||||
(1 'haskell-keyword-face nil lax) |
||||
(2 'haskell-keyword-face nil lax) |
||||
(3 'haskell-keyword-face nil lax) |
||||
(4 'haskell-keyword-face nil lax)) |
||||
|
||||
;; Special case for `foreign import' |
||||
;; keywords in foreign import statements but are not otherwise reserved. |
||||
("\\<\\(foreign\\)[ \t]+\\(import\\)[ \t]+\\(?:\\(ccall\\|stdcall\\|cplusplus\\|jvm\\|dotnet\\)[ \t]+\\)?\\(?:\\(safe\\|unsafe\\|interruptible\\)[ \t]+\\)?" |
||||
(1 'haskell-keyword-face nil lax) |
||||
(2 'haskell-keyword-face nil lax) |
||||
(3 'haskell-keyword-face nil lax) |
||||
(4 'haskell-keyword-face nil lax)) |
||||
|
||||
;; Special case for `foreign export' |
||||
;; keywords in foreign export statements but are not otherwise reserved. |
||||
("\\<\\(foreign\\)[ \t]+\\(export\\)[ \t]+\\(?:\\(ccall\\|stdcall\\|cplusplus\\|jvm\\|dotnet\\)[ \t]+\\)?" |
||||
(1 'haskell-keyword-face nil lax) |
||||
(2 'haskell-keyword-face nil lax) |
||||
(3 'haskell-keyword-face nil lax)) |
||||
|
||||
;; Toplevel Declarations. |
||||
;; Place them *before* generic id-and-op highlighting. |
||||
(,topdecl-var (1 'haskell-definition-face)) |
||||
(,topdecl-var2 (2 'haskell-definition-face)) |
||||
(,topdecl-bangpat (1 'haskell-definition-face)) |
||||
(,topdecl-sym (2 (unless (member (match-string 2) '("\\" "=" "->" "→" "<-" "←" "::" "∷" "," ";" "`")) |
||||
'haskell-definition-face))) |
||||
(,topdecl-sym2 (1 (unless (member (match-string 1) '("\\" "=" "->" "→" "<-" "←" "::" "∷" "," ";" "`")) |
||||
'haskell-definition-face))) |
||||
|
||||
;; These four are debatable... |
||||
("(\\(,*\\|->\\))" 0 'haskell-constructor-face) |
||||
("\\[\\]" 0 'haskell-constructor-face) |
||||
|
||||
(,(concat "`" varid "`") 0 'haskell-operator-face) |
||||
(,(concat "`" conid "`") 0 'haskell-operator-face) |
||||
(,(concat "`" qvarid "`") 0 'haskell-operator-face) |
||||
(,(concat "`" qconid "`") 0 'haskell-operator-face) |
||||
|
||||
(,qconid 0 'haskell-constructor-face) |
||||
|
||||
(,conid 0 'haskell-constructor-face) |
||||
|
||||
(,sym 0 (if (and (eq (char-after (match-beginning 0)) ?:) |
||||
(not (member (match-string 0) '("::" "∷")))) |
||||
'haskell-constructor-face |
||||
'haskell-operator-face)))) |
||||
keywords)) |
||||
|
||||
(defvar haskell-font-lock-latex-cache-pos nil |
||||
"Position of cache point used by `haskell-font-lock-latex-cache-in-comment'. |
||||
Should be at the start of a line.") |
||||
(make-variable-buffer-local 'haskell-font-lock-latex-cache-pos) |
||||
|
||||
(defvar haskell-font-lock-latex-cache-in-comment nil |
||||
"If `haskell-font-lock-latex-cache-pos' is outside a |
||||
\\begin{code}..\\end{code} block (and therefore inside a comment), |
||||
this variable is set to t, otherwise nil.") |
||||
(make-variable-buffer-local 'haskell-font-lock-latex-cache-in-comment) |
||||
|
||||
(defun haskell-font-lock-latex-comments (end) |
||||
"Sets `match-data' according to the region of the buffer before end |
||||
that should be commented under LaTeX-style literate scripts." |
||||
(let ((start (point))) |
||||
(if (= start end) |
||||
;; We're at the end. No more to fontify. |
||||
nil |
||||
(if (not (eq start haskell-font-lock-latex-cache-pos)) |
||||
;; If the start position is not cached, calculate the state |
||||
;; of the start. |
||||
(progn |
||||
(setq haskell-font-lock-latex-cache-pos start) |
||||
;; If the previous \begin{code} or \end{code} is a |
||||
;; \begin{code}, then start is not in a comment, otherwise |
||||
;; it is in a comment. |
||||
(setq haskell-font-lock-latex-cache-in-comment |
||||
(if (and |
||||
(re-search-backward |
||||
"^\\(\\(\\\\begin{code}\\)\\|\\(\\\\end{code}\\)\\)$" |
||||
(point-min) t) |
||||
(match-end 2)) |
||||
nil t)) |
||||
;; Restore position. |
||||
(goto-char start))) |
||||
(if haskell-font-lock-latex-cache-in-comment |
||||
(progn |
||||
;; If start is inside a comment, search for next \begin{code}. |
||||
(re-search-forward "^\\\\begin{code}$" end 'move) |
||||
;; Mark start to end of \begin{code} (if present, till end |
||||
;; otherwise), as a comment. |
||||
(set-match-data (list start (point))) |
||||
;; Return point, as a normal regexp would. |
||||
(point)) |
||||
;; If start is inside a code block, search for next \end{code}. |
||||
(if (re-search-forward "^\\\\end{code}$" end t) |
||||
;; If one found, mark it as a comment, otherwise finish. |
||||
(point)))))) |
||||
|
||||
(defconst haskell-basic-syntactic-keywords |
||||
'(;; Character constants (since apostrophe can't have string syntax). |
||||
;; Beware: do not match something like 's-}' or '\n"+' since the first ' |
||||
;; might be inside a comment or a string. |
||||
;; This still gets fooled with "'"'"'"'"'"', but ... oh well. |
||||
("\\Sw\\('\\)\\([^\\'\n]\\|\\\\.[^\\'\n \"}]*\\)\\('\\)" (1 "\"") (3 "\"")) |
||||
;; Deal with instances of `--' which don't form a comment |
||||
("[!#$%&*+./:<=>?@^|~\\]*--[!#$%&*+./:<=>?@^|~\\-]*" (0 (cond ((or (nth 3 (syntax-ppss)) (numberp (nth 4 (syntax-ppss)))) |
||||
;; There are no such instances inside |
||||
;; nestable comments or strings |
||||
nil) |
||||
((string-match "\\`-*\\'" (match-string 0)) |
||||
;; Sequence of hyphens. Do nothing in |
||||
;; case of things like `{---'. |
||||
nil) |
||||
((string-match "\\`[^-]+--.*" (match-string 0)) |
||||
;; Extra characters before comment starts |
||||
".") |
||||
(t ".")))) ; other symbol sequence |
||||
|
||||
;; Implement Haskell Report 'escape' and 'gap' rules. Backslash |
||||
;; inside of a string is escaping unless it is preceeded by |
||||
;; another escaping backslash. There can be whitespace between |
||||
;; those two. |
||||
;; |
||||
;; Backslashes outside of string never escape. |
||||
;; |
||||
;; Note that (> 0 (skip-syntax-backward ".")) this skips over *escaping* |
||||
;; backslashes only. |
||||
("\\\\" (0 (when (save-excursion (and (nth 3 (syntax-ppss)) |
||||
(goto-char (match-beginning 0)) |
||||
(skip-syntax-backward "->") |
||||
(or (not (eq ?\\ (char-before))) |
||||
(> 0 (skip-syntax-backward "."))))) |
||||
"\\"))) |
||||
|
||||
;; QuasiQuotes syntax: [quoter| string |], quoter is unqualified |
||||
;; name, no spaces, string is arbitrary (including newlines), |
||||
;; finishes at the first occurence of |], no escaping is provided. |
||||
;; |
||||
;; The quoter cannot be "e", "t", "d", or "p", since those overlap |
||||
;; with Template Haskell quotations. |
||||
;; |
||||
;; QuasiQuotes opens only when outside of a string or a comment |
||||
;; and closes only when inside a quasiquote. |
||||
;; |
||||
;; (syntax-ppss) returns list with two interesting elements: |
||||
;; nth 3. non-nil if inside a string. (it is the character that will |
||||
;; terminate the string, or t if the string should be terminated |
||||
;; by a generic string delimiter.) |
||||
;; nth 4. nil if outside a comment, t if inside a non-nestable comment, |
||||
;; else an integer (the current comment nesting). |
||||
;; |
||||
;; Note also that we need to do in in a single pass, hence a regex |
||||
;; that covers both the opening and the ending of a quasiquote. |
||||
|
||||
("\\(\\[[[:alnum:]]+\\)?\\(|\\)\\(]\\)?" |
||||
(2 (save-excursion |
||||
(goto-char (match-beginning 0)) |
||||
(if (eq ?\[ (char-after)) |
||||
;; opening case |
||||
(unless (or (nth 3 (syntax-ppss)) |
||||
(nth 4 (syntax-ppss)) |
||||
(member (match-string 1) |
||||
'("[e" "[t" "[d" "[p"))) |
||||
"\"") |
||||
;; closing case |
||||
(when (and (eq ?| (nth 3 (syntax-ppss))) |
||||
(equal "]" (match-string 3)) |
||||
) |
||||
"\""))))) |
||||
)) |
||||
|
||||
(defconst haskell-bird-syntactic-keywords |
||||
(cons '("^[^\n>]" (0 "<")) |
||||
haskell-basic-syntactic-keywords)) |
||||
|
||||
(defconst haskell-latex-syntactic-keywords |
||||
(append |
||||
'(("^\\\\begin{code}\\(\n\\)" 1 "!") |
||||
;; Note: buffer is widened during font-locking. |
||||
("\\`\\(.\\|\n\\)" (1 "!")) ; start comment at buffer start |
||||
("^\\(\\\\\\)end{code}$" 1 "!")) |
||||
haskell-basic-syntactic-keywords)) |
||||
|
||||
(defun haskell-syntactic-face-function (state) |
||||
"`font-lock-syntactic-face-function' for Haskell." |
||||
(cond |
||||
((nth 3 state) 'font-lock-string-face) ; as normal |
||||
;; Else comment. If it's from syntax table, use default face. |
||||
((or (eq 'syntax-table (nth 7 state)) |
||||
(and (eq haskell-literate 'bird) |
||||
(memq (char-before (nth 8 state)) '(nil ?\n)))) |
||||
'haskell-literate-comment-face) |
||||
;; Detect pragmas. A pragma is enclosed in special comment |
||||
;; delimeters {-# .. #-}. |
||||
((save-excursion |
||||
(goto-char (nth 8 state)) |
||||
(and (looking-at "{-#") |
||||
(forward-comment 1) |
||||
(goto-char (- (point) 3)) |
||||
(looking-at "#-}"))) |
||||
'haskell-pragma-face) |
||||
;; Haddock comment start with either "-- [|^*$]" or "{- ?[|^*$]" |
||||
;; (note space optional for nested comments and mandatory for |
||||
;; double dash comments). |
||||
;; |
||||
;; Haddock comment will also continue on next line, provided: |
||||
;; - current line is a double dash haddock comment |
||||
;; - next line is also double dash comment |
||||
;; - there is only whitespace between |
||||
;; |
||||
;; We recognize double dash haddock comments by property |
||||
;; 'font-lock-doc-face attached to newline. In case of bounded |
||||
;; comments newline is outside of comment. |
||||
((save-excursion |
||||
(goto-char (nth 8 state)) |
||||
(or (looking-at "\\(?:{- ?\\|-- \\)[|^*$]") |
||||
(and (looking-at "--") ; are we at double dash comment |
||||
(forward-line -1) ; this is nil on first line |
||||
(eq (get-text-property (line-end-position) 'face) |
||||
'font-lock-doc-face) ; is a doc face |
||||
(forward-line) |
||||
(skip-syntax-forward "-") ; see if there is only whitespace |
||||
(eq (point) (nth 8 state))))) ; we are back in position |
||||
'font-lock-doc-face) |
||||
(t 'font-lock-comment-face))) |
||||
|
||||
(defconst haskell-font-lock-keywords |
||||
(haskell-font-lock-keywords-create nil) |
||||
"Font lock definitions for non-literate Haskell.") |
||||
|
||||
(defconst haskell-font-lock-bird-literate-keywords |
||||
(haskell-font-lock-keywords-create 'bird) |
||||
"Font lock definitions for Bird-style literate Haskell.") |
||||
|
||||
(defconst haskell-font-lock-latex-literate-keywords |
||||
(haskell-font-lock-keywords-create 'latex) |
||||
"Font lock definitions for LaTeX-style literate Haskell.") |
||||
|
||||
;;;###autoload |
||||
(defun haskell-font-lock-choose-keywords () |
||||
(let ((literate (if (boundp 'haskell-literate) haskell-literate))) |
||||
(cl-case literate |
||||
(bird haskell-font-lock-bird-literate-keywords) |
||||
((latex tex) haskell-font-lock-latex-literate-keywords) |
||||
(t haskell-font-lock-keywords)))) |
||||
|
||||
(defun haskell-font-lock-choose-syntactic-keywords () |
||||
(let ((literate (if (boundp 'haskell-literate) haskell-literate))) |
||||
(cl-case literate |
||||
(bird haskell-bird-syntactic-keywords) |
||||
((latex tex) haskell-latex-syntactic-keywords) |
||||
(t haskell-basic-syntactic-keywords)))) |
||||
|
||||
(defun haskell-font-lock-defaults-create () |
||||
"Locally set `font-lock-defaults' for Haskell." |
||||
(set (make-local-variable 'font-lock-defaults) |
||||
'(haskell-font-lock-choose-keywords |
||||
nil nil ((?\' . "w") (?_ . "w")) nil |
||||
(font-lock-syntactic-keywords |
||||
. haskell-font-lock-choose-syntactic-keywords) |
||||
(font-lock-syntactic-face-function |
||||
. haskell-syntactic-face-function) |
||||
;; Get help from font-lock-syntactic-keywords. |
||||
(parse-sexp-lookup-properties . t)))) |
||||
|
||||
;; The main functions. |
||||
(defun turn-on-haskell-font-lock () |
||||
"Turns on font locking in current buffer for Haskell 1.4 scripts. |
||||
|
||||
Changes the current buffer's `font-lock-defaults', and adds the |
||||
following variables: |
||||
|
||||
`haskell-keyword-face' for reserved keywords and syntax, |
||||
`haskell-constructor-face' for data- and type-constructors, class names, |
||||
and module names, |
||||
`haskell-operator-face' for symbolic and alphanumeric operators, |
||||
`haskell-default-face' for ordinary code. |
||||
|
||||
The variables are initialised to the following font lock default faces: |
||||
|
||||
`haskell-keyword-face' `font-lock-keyword-face' |
||||
`haskell-constructor-face' `font-lock-type-face' |
||||
`haskell-operator-face' `font-lock-function-name-face' |
||||
`haskell-default-face' <default face> |
||||
|
||||
Two levels of fontification are defined: level one (the default) |
||||
and level two (more colour). The former does not colour operators. |
||||
Use the variable `font-lock-maximum-decoration' to choose |
||||
non-default levels of fontification. For example, adding this to |
||||
.emacs: |
||||
|
||||
(setq font-lock-maximum-decoration '((haskell-mode . 2) (t . 0))) |
||||
|
||||
uses level two fontification for `haskell-mode' and default level for |
||||
all other modes. See documentation on this variable for further |
||||
details. |
||||
|
||||
To alter an attribute of a face, add a hook. For example, to change |
||||
the foreground colour of comments to brown, add the following line to |
||||
.emacs: |
||||
|
||||
(add-hook 'haskell-font-lock-hook |
||||
(lambda () |
||||
(set-face-foreground 'haskell-comment-face \"brown\"))) |
||||
|
||||
Note that the colours available vary from system to system. To see |
||||
what colours are available on your system, call |
||||
`list-colors-display' from emacs. |
||||
|
||||
To turn font locking on for all Haskell buffers, add this to .emacs: |
||||
|
||||
(add-hook 'haskell-mode-hook 'turn-on-haskell-font-lock) |
||||
|
||||
To turn font locking on for the current buffer, call |
||||
`turn-on-haskell-font-lock'. To turn font locking off in the current |
||||
buffer, call `turn-off-haskell-font-lock'. |
||||
|
||||
Bird-style literate Haskell scripts are supported: If the value of |
||||
`haskell-literate-bird-style' (automatically set by the Haskell mode |
||||
of Moss&Thorn) is non-nil, a Bird-style literate script is assumed. |
||||
|
||||
Invokes `haskell-font-lock-hook' if not nil." |
||||
(haskell-font-lock-defaults-create) |
||||
(run-hooks 'haskell-font-lock-hook) |
||||
(turn-on-font-lock)) |
||||
|
||||
(defun turn-off-haskell-font-lock () |
||||
"Turns off font locking in current buffer." |
||||
(font-lock-mode -1)) |
||||
|
||||
(defun haskell-fontify-as-mode (text mode) |
||||
"Fontify TEXT as MODE, returning the fontified text." |
||||
(with-temp-buffer |
||||
(funcall mode) |
||||
(insert text) |
||||
(if (fboundp 'font-lock-ensure) |
||||
(font-lock-ensure) |
||||
(with-no-warnings (font-lock-fontify-buffer))) |
||||
(buffer-substring (point-min) (point-max)))) |
||||
|
||||
;; Provide ourselves: |
||||
|
||||
(provide 'haskell-font-lock) |
||||
|
||||
;; Local Variables: |
||||
;; coding: utf-8-unix |
||||
;; tab-width: 8 |
||||
;; End: |
||||
|
||||
;;; haskell-font-lock.el ends here |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,223 +0,0 @@
|
||||
;;; haskell-lexeme.el --- haskell lexical tokens -*- coding: utf-8; lexical-binding: t -*- |
||||
|
||||
;; Copyright (C) 2015 Gracjan Polak |
||||
|
||||
;; This file is free software; you can redistribute it and/or modify |
||||
;; it under the terms of the GNU General Public License as published by |
||||
;; the Free Software Foundation; either version 3, or (at your option) |
||||
;; any later version. |
||||
|
||||
;; This file is distributed in the hope that it will be useful, |
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
;; GNU General Public License for more details. |
||||
|
||||
;; You should have received a copy of the GNU General Public License |
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
;;; Commentary: |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'rx) |
||||
|
||||
(unless (category-docstring ?P) |
||||
(define-category ?P "Haskell symbol constituent characters") |
||||
(map-char-table |
||||
#'(lambda (key val) |
||||
(if (or |
||||
(and (consp key) (> (car key) 128)) |
||||
(and (numberp key) (> key 128))) |
||||
(if (member val '(Pc Pd Po Sm Sc Sk So)) |
||||
(modify-category-entry key ?P)))) |
||||
unicode-category-table) |
||||
|
||||
(dolist (key (string-to-list "!#$%&*+./<=>?@^|~\\-")) |
||||
(modify-category-entry key ?P))) |
||||
|
||||
(defconst haskell-lexeme-modid |
||||
"[[:upper:]][[:alnum:]'_]*" |
||||
"Regexp matching a valid Haskell module identifier. |
||||
|
||||
Note that GHC accepts Unicode category UppercaseLetter as a first |
||||
character. Following letters are from Unicode categories |
||||
UppercaseLetter, LowercaseLetter, OtherLetter, TitlecaseLetter, |
||||
ModifierLetter, DecimalNumber, OtherNumber, backslash or |
||||
underscore. |
||||
|
||||
Note that this differs from constructor identifier as the latter |
||||
one can have any number of hash character at the end to |
||||
accommodate MagicHash extension.") |
||||
|
||||
(defconst haskell-lexeme-id |
||||
"[[:alpha:]_][[:alnum:]'_]*#*" |
||||
"Regexp matching a valid Haskell identifier. |
||||
|
||||
GHC accepts a string starting with any alphabetic character or |
||||
underscore followed by any alphanumeric character or underscore |
||||
or apostrophe.") |
||||
|
||||
(defconst haskell-lexeme-sym |
||||
"\\(:?\\cP\\|:\\)+" |
||||
"Regexp matching a valid Haskell variable or constructor symbol. |
||||
|
||||
GHC accepts a string of chars from the set |
||||
[:!#$%&*+./<=>?@^|~\\-] or Unicode category Symbol for chars with |
||||
codes larger than 128 only.") |
||||
|
||||
(defconst haskell-lexeme-modid-opt-prefix |
||||
(concat "\\(?:" haskell-lexeme-modid "\\.\\)*") |
||||
"Regexp matching a valid Haskell module prefix, potentially empty. |
||||
|
||||
Module path prefix is separated by dots and finishes with a |
||||
dot. For path component syntax see `haskell-lexeme-modid'.") |
||||
|
||||
(defconst haskell-lexeme-qid-or-qsym |
||||
(rx-to-string `(: (regexp ,haskell-lexeme-modid-opt-prefix) |
||||
(group (| (regexp ,haskell-lexeme-id) (regexp ,haskell-lexeme-sym) |
||||
)))) |
||||
"Regexp matching a valid qualified identifier or symbol. |
||||
|
||||
Note that (match-string 1) returns the unqualified part.") |
||||
|
||||
(defconst haskell-lexeme-qid |
||||
(rx-to-string `(: (regexp "'*") |
||||
(regexp ,haskell-lexeme-modid-opt-prefix) |
||||
(group (regexp ,haskell-lexeme-id)))) |
||||
"Regexp matching a valid qualified identifier. |
||||
|
||||
Note that (match-string 1) returns the unqualified part.") |
||||
|
||||
(defconst haskell-lexeme-qsym |
||||
(rx-to-string `(: (regexp "'*") |
||||
(regexp ,haskell-lexeme-modid-opt-prefix) |
||||
(group (regexp ,haskell-lexeme-id)))) |
||||
"Regexp matching a valid qualified symbol. |
||||
|
||||
Note that (match-string 1) returns the unqualified part.") |
||||
|
||||
(defconst haskell-lexeme-number |
||||
(rx (| (: (regexp "[0-9]+\\.[0-9]+") (opt (regexp "[eE][-+]?[0-9]+"))) |
||||
(regexp "[0-9]+[eE][-+]?[0-9]+") |
||||
(regexp "0[xX][0-9a-fA-F]+") |
||||
(regexp "0[oO][0-7]+") |
||||
(regexp "[0-9]+"))) |
||||
"Regexp matching a floating point, decimal, octal or hexadecimal number. |
||||
|
||||
Note that negative sign char is not part of a number.") |
||||
|
||||
(defconst haskell-lexeme-char-literal-inside |
||||
(rx (| (regexp "[^\n'\\]") |
||||
(: "\\" |
||||
(| "a" "b" "f" "n" "r" "t" "v" "\\" "\"" "'" |
||||
"NUL" "SOH" "STX" "ETX" "EOT" "ENQ" "ACK" |
||||
"BEL" "BS" "HT" "LF" "VT" "FF" "CR" "SO" "SI" "DLE" |
||||
"DC1" "DC2" "DC3" "DC4" "NAK" "SYN" "ETB" "CAN" |
||||
"EM" "SUB" "ESC" "FS" "GS" "RS" "US" "SP" "DEL" |
||||
(: "^" (regexp "[]A-Z@^_\\[]")))))) |
||||
"Regexp matching an inside of a character literal.") |
||||
|
||||
(defconst haskell-lexeme-char-literal |
||||
(rx-to-string `(: "'" (regexp ,haskell-lexeme-char-literal-inside) "'")) |
||||
"Regexp matching a character literal.") |
||||
|
||||
(defconst haskell-lexeme-string-literal-inside |
||||
(rx (* (| (regexp "[^\n\"\\]") |
||||
(: "\\" |
||||
(| "a" "b" "f" "n" "r" "t" "v" "\\" "\"" "'" "&" |
||||
"NUL" "SOH" "STX" "ETX" "EOT" "ENQ" "ACK" |
||||
"BEL" "BS" "HT" "LF" "VT" "FF" "CR" "SO" "SI" "DLE" |
||||
"DC1" "DC2" "DC3" "DC4" "NAK" "SYN" "ETB" "CAN" |
||||
"EM" "SUB" "ESC" "FS" "GS" "RS" "US" "SP" "DEL" |
||||
(: "^" (regexp "[]A-Z@^_\\[]")) |
||||
(regexp "[ \t\n\r\v\f]*\\\\")))))) |
||||
"Regexp matching an inside of a string literal.") |
||||
|
||||
(defconst haskell-lexeme-string-literal |
||||
(rx-to-string `(: "\"" (regexp ,haskell-lexeme-string-literal-inside) "\"")) |
||||
"Regexp matching a string literal.") |
||||
|
||||
(defun haskell-lexeme-classify-by-first-char (char) |
||||
"Classify token by CHAR. |
||||
|
||||
CHAR is a chararacter that is assumed to be first character of a token." |
||||
(let ((category (get-char-code-property char 'general-category))) |
||||
|
||||
(cond |
||||
((or (member char '(?! ?# ?$ ?% ?& ?* ?+ ?. ?/ ?< ?= ?> ?? ?@ ?^ ?| ?~ ?\\ ?-)) |
||||
(and (> char 127) |
||||
(member category '(Pc Pd Po Sm Sc Sk So)))) |
||||
'varsym) |
||||
((equal char ?:) |
||||
'consym) |
||||
((equal char ?\') |
||||
'char) |
||||
((equal char ?\") |
||||
'string) |
||||
((member category '(Lu Lt)) |
||||
'conid) |
||||
((or (equal char ?_) |
||||
(member category '(Ll Lo))) |
||||
'varsym) |
||||
((and (>= char ?0) (<= char 9)) |
||||
'number) |
||||
((member char '(?\] ?\[ ?\( ?\) ?\{ ?\} ?\` ?\, ?\;)) |
||||
'special)))) |
||||
|
||||
(defun haskell-lexeme-looking-at-token () |
||||
"Like `looking-at' but understands Haskell lexemes. |
||||
|
||||
Moves point forward over whitespace. Returns a symbol describing |
||||
type of Haskell token recognized. Use `match-string', |
||||
`match-beginning' and `match-end' with argument 0 to query match |
||||
result. |
||||
|
||||
Possible results are: |
||||
- 'special: for chars [](){}`,; |
||||
- 'comment: for single line comments |
||||
- 'nested-comment: for multiline comments |
||||
- 'qsymid: for qualified identifiers or symbols |
||||
- 'string: for strings literals |
||||
- 'char: for char literals |
||||
- 'decimal: for decimal, float, hexadecimal and octal number literals |
||||
- 'template-haskell-quote: for a string of apostrophes for template haskell |
||||
|
||||
Note that for qualified symbols (match-string 1) returns the |
||||
unqualified identifier or symbol. Further qualification for |
||||
symbol or identifier can be done with: |
||||
|
||||
(haskell-lexeme-classify-by-first-char (char-after (match-beginning 1))) |
||||
|
||||
See `haskell-lexeme-classify-by-first-char' for details." |
||||
(skip-syntax-forward "->") |
||||
(let |
||||
((case-fold-search nil) |
||||
(point (point-marker))) |
||||
(or |
||||
(and (looking-at "{-") |
||||
(progn |
||||
(save-excursion |
||||
(forward-comment 1) |
||||
(set-match-data (list point (point-marker)))) |
||||
'nested-comment)) |
||||
(and (looking-at haskell-lexeme-char-literal) |
||||
'char) |
||||
(and (looking-at haskell-lexeme-string-literal) |
||||
'string) |
||||
(and (looking-at "[][(){}`,;]") |
||||
'special) |
||||
(and (looking-at haskell-lexeme-qid-or-qsym) |
||||
(if (and (eq (- (match-end 0) (match-beginning 0)) 2) |
||||
(equal (match-string 0) "--")) |
||||
(progn |
||||
(set-match-data (list point (set-marker (make-marker) (line-end-position)))) |
||||
'comment) |
||||
'qsymid)) |
||||
(and (looking-at haskell-lexeme-number) |
||||
'number) |
||||
(and (looking-at "'+") |
||||
'template-haskell-quote)))) |
||||
|
||||
(provide 'haskell-lexeme) |
||||
|
||||
;;; haskell-lexeme.el ends here |
@ -1,529 +0,0 @@
|
||||
;;; haskell-load.el --- Compiling and loading modules in the GHCi process -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright (c) 2014 Chris Done. All rights reserved. |
||||
|
||||
;; This file is free software; you can redistribute it and/or modify |
||||
;; it under the terms of the GNU General Public License as published by |
||||
;; the Free Software Foundation; either version 3, or (at your option) |
||||
;; any later version. |
||||
|
||||
;; This file is distributed in the hope that it will be useful, |
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
;; GNU General Public License for more details. |
||||
|
||||
;; You should have received a copy of the GNU General Public License |
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cl-lib) |
||||
(require 'haskell-process) |
||||
(require 'haskell-interactive-mode) |
||||
(require 'haskell-modules) |
||||
(require 'haskell-commands) |
||||
(require 'haskell-session) |
||||
|
||||
(defun haskell-process-look-config-changes (session) |
||||
"Checks whether a cabal configuration file has |
||||
changed. Restarts the process if that is the case." |
||||
(let ((current-checksum (haskell-session-get session 'cabal-checksum)) |
||||
(new-checksum (haskell-cabal-compute-checksum |
||||
(haskell-session-get session 'cabal-dir)))) |
||||
(when (not (string= current-checksum new-checksum)) |
||||
(haskell-interactive-mode-echo session (format "Cabal file changed: %s" new-checksum)) |
||||
(haskell-session-set-cabal-checksum session |
||||
(haskell-session-get session 'cabal-dir)) |
||||
(unless (and haskell-process-prompt-restart-on-cabal-change |
||||
(not (y-or-n-p "Cabal file changed; restart GHCi process? "))) |
||||
(haskell-process-start (haskell-interactive-session)))))) |
||||
|
||||
(defun haskell-process-live-build (process buffer echo-in-repl) |
||||
"Show live updates for loading files." |
||||
(cond ((haskell-process-consume |
||||
process |
||||
(concat "\\[[ ]*\\([0-9]+\\) of \\([0-9]+\\)\\]" |
||||
" Compiling \\([^ ]+\\)[ ]+" |
||||
"( \\([^ ]+\\), \\([^ ]+\\) )[^\r\n]*[\r\n]+")) |
||||
(haskell-process-echo-load-message process buffer echo-in-repl nil) |
||||
t) |
||||
((haskell-process-consume |
||||
process |
||||
(concat "\\[[ ]*\\([0-9]+\\) of \\([0-9]+\\)\\]" |
||||
" Compiling \\[TH\\] \\([^ ]+\\)[ ]+" |
||||
"( \\([^ ]+\\), \\([^ ]+\\) )[^\r\n]*[\r\n]+")) |
||||
(haskell-process-echo-load-message process buffer echo-in-repl t) |
||||
t) |
||||
((haskell-process-consume process "Loading package \\([^ ]+\\) ... linking ... done.\n") |
||||
(haskell-mode-message-line |
||||
(format "Loading: %s" |
||||
(match-string 1 buffer))) |
||||
t) |
||||
((haskell-process-consume |
||||
process |
||||
"^Preprocessing executables for \\(.+?\\)\\.\\.\\.") |
||||
(let ((msg (format "Preprocessing: %s" (match-string 1 buffer)))) |
||||
(haskell-interactive-mode-echo |
||||
(haskell-process-session process) |
||||
msg) |
||||
(haskell-mode-message-line msg))) |
||||
((haskell-process-consume process "Linking \\(.+?\\) \\.\\.\\.") |
||||
(let ((msg (format "Linking: %s" (match-string 1 buffer)))) |
||||
(haskell-interactive-mode-echo (haskell-process-session process) msg) |
||||
(haskell-mode-message-line msg))) |
||||
((haskell-process-consume process "\nBuilding \\(.+?\\)\\.\\.\\.") |
||||
(let ((msg (format "Building: %s" (match-string 1 buffer)))) |
||||
(haskell-interactive-mode-echo |
||||
(haskell-process-session process) |
||||
msg) |
||||
(haskell-mode-message-line msg))))) |
||||
|
||||
(defun haskell-process-load-complete (session process buffer reload module-buffer &optional cont) |
||||
"Handle the complete loading response. BUFFER is the string of |
||||
text being sent over the process pipe. MODULE-BUFFER is the |
||||
actual Emacs buffer of the module being loaded." |
||||
(when (get-buffer (format "*%s:splices*" (haskell-session-name session))) |
||||
(with-current-buffer (haskell-interactive-mode-splices-buffer session) |
||||
(erase-buffer))) |
||||
(let* ((ok (cond ((haskell-process-consume process "Ok, modules loaded: \\(.+\\)\\.$") t) |
||||
((haskell-process-consume process "Failed, modules loaded: \\(.+\\)\\.$") nil) |
||||
(t (error (message "Unexpected response from haskell process."))))) |
||||
(modules (haskell-process-extract-modules buffer)) |
||||
(cursor (haskell-process-response-cursor process)) |
||||
(warning-count 0)) |
||||
(haskell-process-set-response-cursor process 0) |
||||
(haskell-check-remove-overlays module-buffer) |
||||
(while (haskell-process-errors-warnings module-buffer session process buffer) |
||||
(setq warning-count (1+ warning-count))) |
||||
(haskell-process-set-response-cursor process cursor) |
||||
(if (and (not reload) |
||||
haskell-process-reload-with-fbytecode) |
||||
(haskell-process-reload-with-fbytecode process module-buffer) |
||||
(haskell-process-import-modules process (car modules))) |
||||
(if ok |
||||
(haskell-mode-message-line (if reload "Reloaded OK." "OK.")) |
||||
(haskell-interactive-mode-compile-error session "Compilation failed.")) |
||||
(when cont |
||||
(condition-case e |
||||
(funcall cont ok) |
||||
(error (message "%S" e)) |
||||
(quit nil))))) |
||||
|
||||
(defun haskell-process-suggest-imports (session file modules ident) |
||||
"Given a list of MODULES, suggest adding them to the import section." |
||||
(cl-assert session) |
||||
(cl-assert file) |
||||
(cl-assert ident) |
||||
(let* ((process (haskell-session-process session)) |
||||
(suggested-already (haskell-process-suggested-imports process)) |
||||
(module (cond ((> (length modules) 1) |
||||
(when (y-or-n-p (format "Identifier `%s' not in scope, choose module to import?" |
||||
ident)) |
||||
(haskell-complete-module-read "Module: " modules))) |
||||
((= (length modules) 1) |
||||
(let ((module (car modules))) |
||||
(unless (member module suggested-already) |
||||
(haskell-process-set-suggested-imports process (cons module suggested-already)) |
||||
(when (y-or-n-p (format "Identifier `%s' not in scope, import `%s'?" |
||||
ident |
||||
module)) |
||||
module))))))) |
||||
(when module |
||||
(haskell-process-find-file session file) |
||||
(haskell-add-import module)))) |
||||
|
||||
(defun haskell-process-trigger-suggestions (session msg file line) |
||||
"Trigger prompting to add any extension suggestions." |
||||
(cond ((let ((case-fold-search nil)) |
||||
(or (and (string-match " -X\\([A-Z][A-Za-z]+\\)" msg) |
||||
(not (string-match "\\([A-Z][A-Za-z]+\\) is deprecated" msg))) |
||||
(string-match "Use \\([A-Z][A-Za-z]+\\) to permit this" msg) |
||||
(string-match "Use \\([A-Z][A-Za-z]+\\) to allow" msg) |
||||
(string-match "use \\([A-Z][A-Za-z]+\\)" msg) |
||||
(string-match "You need \\([A-Z][A-Za-z]+\\)" msg))) |
||||
(when haskell-process-suggest-language-pragmas |
||||
(haskell-process-suggest-pragma session "LANGUAGE" (match-string 1 msg) file))) |
||||
((string-match " The \\(qualified \\)?import of[ ][‘`‛]\\([^ ]+\\)['’] is redundant" msg) |
||||
(when haskell-process-suggest-remove-import-lines |
||||
(haskell-process-suggest-remove-import session |
||||
file |
||||
(match-string 2 msg) |
||||
line))) |
||||
((string-match "Warning: orphan instance: " msg) |
||||
(when haskell-process-suggest-no-warn-orphans |
||||
(haskell-process-suggest-pragma session "OPTIONS" "-fno-warn-orphans" file))) |
||||
((or (string-match "against inferred type [‘`‛]\\[Char\\]['’]" msg) |
||||
(string-match "with actual type [‘`‛]\\[Char\\]['’]" msg)) |
||||
(when haskell-process-suggest-overloaded-strings |
||||
(haskell-process-suggest-pragma session "LANGUAGE" "OverloadedStrings" file))) |
||||
((string-match "^Not in scope: .*[‘`‛]\\(.+\\)['’]$" msg) |
||||
(let* ((match1 (match-string 1 msg)) |
||||
(ident (if (string-match "^[A-Za-z0-9_'.]+\\.\\(.+\\)$" match1) |
||||
;; Skip qualification. |
||||
(match-string 1 match1) |
||||
match1))) |
||||
(when haskell-process-suggest-hoogle-imports |
||||
(let ((modules (haskell-process-hoogle-ident ident))) |
||||
(haskell-process-suggest-imports session file modules ident))) |
||||
(when haskell-process-suggest-haskell-docs-imports |
||||
(let ((modules (haskell-process-haskell-docs-ident ident))) |
||||
(haskell-process-suggest-imports session file modules ident))) |
||||
(when haskell-process-suggest-hayoo-imports |
||||
(let ((modules (haskell-process-hayoo-ident ident))) |
||||
(haskell-process-suggest-imports session file modules ident))))) |
||||
((string-match "^[ ]+It is a member of the hidden package [‘`‛]\\([^@\r\n]+\\).*['’].$" msg) |
||||
(when haskell-process-suggest-add-package |
||||
(haskell-process-suggest-add-package session msg))))) |
||||
|
||||
(defun haskell-process-do-cabal (command) |
||||
"Run a Cabal command." |
||||
(let ((process (haskell-interactive-process))) |
||||
(cond |
||||
((let ((child (haskell-process-process process))) |
||||
(not (equal 'run (process-status child)))) |
||||
(message "Process is not running, so running directly.") |
||||
(shell-command (concat "cabal " command) |
||||
(get-buffer-create "*haskell-process-log*") |
||||
(get-buffer-create "*haskell-process-log*")) |
||||
(switch-to-buffer-other-window (get-buffer "*haskell-process-log*"))) |
||||
(t (haskell-process-queue-command |
||||
process |
||||
(make-haskell-command |
||||
:state (list (haskell-interactive-session) process command 0) |
||||
|
||||
:go |
||||
(lambda (state) |
||||
(haskell-process-send-string |
||||
(cadr state) |
||||
(format haskell-process-do-cabal-format-string |
||||
(haskell-session-cabal-dir (car state)) |
||||
(format "%s %s" |
||||
(cl-ecase (haskell-process-type) |
||||
('ghci haskell-process-path-cabal) |
||||
('cabal-repl haskell-process-path-cabal) |
||||
('cabal-ghci haskell-process-path-cabal) |
||||
('stack-ghci haskell-process-path-stack)) |
||||
(cl-caddr state))))) |
||||
|
||||
:live |
||||
(lambda (state buffer) |
||||
(let ((cmd (replace-regexp-in-string "^\\([a-z]+\\).*" |
||||
"\\1" |
||||
(cl-caddr state)))) |
||||
(cond ((or (string= cmd "build") |
||||
(string= cmd "install")) |
||||
(haskell-process-live-build (cadr state) buffer t)) |
||||
(t |
||||
(haskell-process-cabal-live state buffer))))) |
||||
|
||||
:complete |
||||
(lambda (state response) |
||||
(let* ((process (cadr state)) |
||||
(session (haskell-process-session process)) |
||||
(message-count 0) |
||||
(cursor (haskell-process-response-cursor process))) |
||||
;; XXX: what the hell about the rampant code duplication? |
||||
(haskell-process-set-response-cursor process 0) |
||||
(while (haskell-process-errors-warnings nil session process response) |
||||
(setq message-count (1+ message-count))) |
||||
(haskell-process-set-response-cursor process cursor) |
||||
(let ((msg (format "Complete: cabal %s (%s compiler messages)" |
||||
(cl-caddr state) |
||||
message-count))) |
||||
(haskell-interactive-mode-echo session msg) |
||||
(when (= message-count 0) |
||||
(haskell-interactive-mode-echo |
||||
session |
||||
"No compiler messages, dumping complete output:") |
||||
(haskell-interactive-mode-echo session response)) |
||||
(haskell-mode-message-line msg) |
||||
(when (and haskell-notify-p |
||||
(fboundp 'notifications-notify)) |
||||
(notifications-notify |
||||
:title (format "*%s*" (haskell-session-name (car state))) |
||||
:body msg |
||||
:app-name (cl-ecase (haskell-process-type) |
||||
('ghci haskell-process-path-cabal) |
||||
('cabal-repl haskell-process-path-cabal) |
||||
('cabal-ghci haskell-process-path-cabal) |
||||
('stack-ghci haskell-process-path-stack)) |
||||
:app-icon haskell-process-logo))))))))))) |
||||
|
||||
(defun haskell-process-echo-load-message (process buffer echo-in-repl th) |
||||
"Echo a load message." |
||||
(let ((session (haskell-process-session process)) |
||||
(module-name (match-string 3 buffer)) |
||||
(file-name (match-string 4 buffer))) |
||||
(haskell-interactive-show-load-message |
||||
session |
||||
'compiling |
||||
module-name |
||||
(haskell-session-strip-dir session file-name) |
||||
echo-in-repl |
||||
th))) |
||||
|
||||
(defun haskell-process-extract-modules (buffer) |
||||
"Extract the modules from the process buffer." |
||||
(let* ((modules-string (match-string 1 buffer)) |
||||
(modules (split-string modules-string ", "))) |
||||
(cons modules modules-string))) |
||||
|
||||
;;;###autoload |
||||
(defface haskell-error-face |
||||
'((((supports :underline (:style wave))) |
||||
:underline (:style wave :color "#dc322f")) |
||||
(t |
||||
:inherit error)) |
||||
"Face used for marking error lines." |
||||
:group 'haskell-mode) |
||||
|
||||
;;;###autoload |
||||
(defface haskell-warning-face |
||||
'((((supports :underline (:style wave))) |
||||
:underline (:style wave :color "#b58900")) |
||||
(t |
||||
:inherit warning)) |
||||
"Face used for marking warning lines." |
||||
:group 'haskell-mode) |
||||
|
||||
;;;###autoload |
||||
(defface haskell-hole-face |
||||
'((((supports :underline (:style wave))) |
||||
:underline (:style wave :color "#6c71c4")) |
||||
(t |
||||
:inherit warning)) |
||||
"Face used for marking hole lines." |
||||
:group 'haskell-mode) |
||||
|
||||
(defvar haskell-check-error-fringe (propertize "!" 'display '(left-fringe exclamation-mark))) |
||||
(defvar haskell-check-warning-fringe (propertize "?" 'display '(left-fringe question-mark))) |
||||
(defvar haskell-check-hole-fringe (propertize "_" 'display '(left-fringe horizontal-bar))) |
||||
|
||||
(defun haskell-check-overlay-p (ovl) |
||||
(overlay-get ovl 'haskell-check)) |
||||
|
||||
(defun haskell-check-filter-overlays (xs) |
||||
(cl-remove-if-not 'haskell-check-overlay-p xs)) |
||||
|
||||
(defun haskell-check-remove-overlays (buffer) |
||||
(with-current-buffer buffer |
||||
(remove-overlays (point-min) (point-max) 'haskell-check t))) |
||||
|
||||
(defmacro with-overlay-properties (proplist ovl &rest body) |
||||
"Evaluate BODY with names in PROPLIST bound to the values of |
||||
correspondingly-named overlay properties of OVL." |
||||
(let ((ovlvar (cl-gensym "OVL-"))) |
||||
`(let* ((,ovlvar ,ovl) |
||||
,@(mapcar (lambda (p) `(,p (overlay-get ,ovlvar ',p))) proplist)) |
||||
,@body))) |
||||
|
||||
(defun overlay-start> (o1 o2) |
||||
(> (overlay-start o1) (overlay-start o2))) |
||||
(defun overlay-start< (o1 o2) |
||||
(< (overlay-start o1) (overlay-start o2))) |
||||
|
||||
(defun first-overlay-in-if (test beg end) |
||||
(let ((ovls (cl-remove-if-not test (overlays-in beg end)))) |
||||
(cl-first (sort (cl-copy-list ovls) 'overlay-start<)))) |
||||
|
||||
(defun last-overlay-in-if (test beg end) |
||||
(let ((ovls (cl-remove-if-not test (overlays-in beg end)))) |
||||
(cl-first (sort (cl-copy-list ovls) 'overlay-start>)))) |
||||
|
||||
(defun haskell-error-overlay-briefly (ovl) |
||||
(with-overlay-properties (haskell-msg haskell-msg-type) ovl |
||||
(cond ((not (eq haskell-msg-type 'warning)) |
||||
haskell-msg) |
||||
((string-prefix-p "Warning:\n " haskell-msg) |
||||
(cl-subseq haskell-msg 13)) |
||||
(t (error "Invariant failed: a warning message from GHC has unexpected form: %s." haskell-msg))))) |
||||
|
||||
(defun haskell-goto-error-overlay (ovl) |
||||
(cond (ovl |
||||
(goto-char (overlay-start ovl)) |
||||
(haskell-mode-message-line (haskell-error-overlay-briefly ovl))) |
||||
(t |
||||
(message "No further notes from Haskell compiler.")))) |
||||
|
||||
(defun haskell-goto-prev-error () |
||||
(interactive) |
||||
(haskell-goto-error-overlay |
||||
(let ((ovl-at (cl-first (haskell-check-filter-overlays (overlays-at (point)))))) |
||||
(or (last-overlay-in-if 'haskell-check-overlay-p |
||||
(point-min) (if ovl-at (overlay-start ovl-at) (point))) |
||||
ovl-at)))) |
||||
|
||||
(defun haskell-goto-next-error () |
||||
(interactive) |
||||
(haskell-goto-error-overlay |
||||
(let ((ovl-at (cl-first (haskell-check-filter-overlays (overlays-at (point)))))) |
||||
(or (first-overlay-in-if 'haskell-check-overlay-p |
||||
(if ovl-at (overlay-end ovl-at) (point)) (point-max)) |
||||
ovl-at)))) |
||||
|
||||
(defun haskell-check-paint-overlay (buffer error-from-this-file-p line msg file type hole coln) |
||||
(with-current-buffer buffer |
||||
(let (beg end) |
||||
(goto-char (point-min)) |
||||
;; XXX: we can avoid excess buffer walking by relying on the maybe-fact that |
||||
;; GHC sorts error messages by line number, maybe. |
||||
(cond |
||||
(error-from-this-file-p |
||||
(forward-line (1- line)) |
||||
(forward-char (1- coln)) |
||||
(setq beg (point)) |
||||
(if (eq type 'hole) |
||||
(forward-char (length hole)) |
||||
(skip-chars-forward "^[:space:]" (line-end-position))) |
||||
(setq end (point))) |
||||
(t |
||||
(setq beg (point)) |
||||
(forward-line) |
||||
(setq end (point)))) |
||||
(let ((ovl (make-overlay beg end))) |
||||
(overlay-put ovl 'haskell-check t) |
||||
(overlay-put ovl 'haskell-file file) |
||||
(overlay-put ovl 'haskell-msg msg) |
||||
(overlay-put ovl 'haskell-msg-type type) |
||||
(overlay-put ovl 'help-echo msg) |
||||
(overlay-put ovl 'haskell-hole hole) |
||||
(cl-destructuring-bind (face fringe) (cl-case type |
||||
(warning (list 'haskell-warning-face haskell-check-warning-fringe)) |
||||
(hole (list 'haskell-hole-face haskell-check-hole-fringe)) |
||||
(error (list 'haskell-error-face haskell-check-error-fringe))) |
||||
(overlay-put ovl 'before-string fringe) |
||||
(overlay-put ovl 'face face)))))) |
||||
|
||||
(defun haskell-process-errors-warnings (module-buffer session process buffer &optional return-only) |
||||
"Trigger handling type errors or warnings. Either prints the |
||||
messages in the interactive buffer or if CONT is specified, |
||||
passes the error onto that. |
||||
|
||||
When MODULE-BUFFER is non-NIL, paint error overlays." |
||||
(save-excursion |
||||
(cond |
||||
((haskell-process-consume |
||||
process |
||||
"\\(Module imports form a cycle:[ \n]+module [^ ]+ ([^)]+)[[:unibyte:][:nonascii:]]+?\\)\nFailed") |
||||
(let ((err (match-string 1 buffer))) |
||||
(if (string-match "module [`'‘‛]\\([^ ]+\\)['’`] (\\([^)]+\\))" err) |
||||
(let* ((default-directory (haskell-session-current-dir session)) |
||||
(module (match-string 1 err)) |
||||
(file (match-string 2 err)) |
||||
(relative-file-name (file-relative-name file))) |
||||
(unless return-only |
||||
(haskell-interactive-show-load-message |
||||
session |
||||
'import-cycle |
||||
module |
||||
relative-file-name |
||||
nil |
||||
nil) |
||||
(haskell-interactive-mode-compile-error |
||||
session |
||||
(format "%s:1:0: %s" |
||||
relative-file-name |
||||
err))) |
||||
(list :file file :line 1 :col 0 :msg err :type 'error)) |
||||
t))) |
||||
((haskell-process-consume |
||||
process |
||||
(concat "[\r\n]\\([A-Z]?:?[^ \r\n:][^:\n\r]+\\):\\([0-9()-:]+\\):" |
||||
"[ \n\r]+\\([[:unibyte:][:nonascii:]]+?\\)\n[^ ]")) |
||||
(haskell-process-set-response-cursor process |
||||
(- (haskell-process-response-cursor process) 1)) |
||||
(let* ((buffer (haskell-process-response process)) |
||||
(file (match-string 1 buffer)) |
||||
(location-raw (match-string 2 buffer)) |
||||
(error-msg (match-string 3 buffer)) |
||||
(type (cond ((string-match "^Warning:" error-msg) 'warning) |
||||
((string-match "^Splicing " error-msg) 'splice) |
||||
(t 'error))) |
||||
(critical (not (eq type 'warning))) |
||||
;; XXX: extract hole information, pass down to `haskell-check-paint-overlay' |
||||
(final-msg (format "%s:%s: %s" |
||||
(haskell-session-strip-dir session file) |
||||
location-raw |
||||
error-msg)) |
||||
(location (haskell-process-parse-error (concat file ":" location-raw ": x"))) |
||||
(line (plist-get location :line)) |
||||
(col1 (plist-get location :col))) |
||||
(when module-buffer |
||||
(haskell-check-paint-overlay module-buffer (string= (file-truename (buffer-file-name module-buffer)) (file-truename file)) |
||||
line error-msg file type nil col1)) |
||||
(if return-only |
||||
(list :file file :line line :col col1 :msg error-msg :type type) |
||||
(progn (funcall (cl-case type |
||||
(warning 'haskell-interactive-mode-compile-warning) |
||||
(splice 'haskell-interactive-mode-compile-splice) |
||||
(error 'haskell-interactive-mode-compile-error)) |
||||
session final-msg) |
||||
(when critical |
||||
(haskell-mode-message-line final-msg)) |
||||
(haskell-process-trigger-suggestions |
||||
session |
||||
error-msg |
||||
file |
||||
(plist-get (haskell-process-parse-error final-msg) :line)) |
||||
t))))))) |
||||
|
||||
(defun haskell-interactive-show-load-message (session type module-name file-name echo th) |
||||
"Show the '(Compiling|Loading) X' message." |
||||
(let ((msg (concat |
||||
(cl-ecase type |
||||
('compiling |
||||
(if haskell-interactive-mode-include-file-name |
||||
(format "Compiling: %s (%s)" module-name file-name) |
||||
(format "Compiling: %s" module-name))) |
||||
('loading (format "Loading: %s" module-name)) |
||||
('import-cycle (format "Module has an import cycle: %s" module-name))) |
||||
(if th " [TH]" "")))) |
||||
(haskell-mode-message-line msg) |
||||
(when haskell-interactive-mode-delete-superseded-errors |
||||
(haskell-interactive-mode-delete-compile-messages session file-name)) |
||||
(when echo |
||||
(haskell-interactive-mode-echo session msg)))) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-process-reload-devel-main () |
||||
"Reload the module `DevelMain' and then run |
||||
`DevelMain.update'. This is for doing live update of the code of |
||||
servers or GUI applications. Put your development version of the |
||||
program in `DevelMain', and define `update' to auto-start the |
||||
program on a new thread, and use the `foreign-store' package to |
||||
access the running context across :load/:reloads in GHCi." |
||||
(interactive) |
||||
(with-current-buffer (or (get-buffer "DevelMain.hs") |
||||
(if (y-or-n-p "You need to open a buffer named DevelMain.hs. Find now?") |
||||
(ido-find-file) |
||||
(error "No DevelMain.hs buffer."))) |
||||
(let ((session (haskell-interactive-session))) |
||||
(let ((process (haskell-interactive-process))) |
||||
(haskell-process-queue-command |
||||
process |
||||
(make-haskell-command |
||||
:state (list :session session |
||||
:process process |
||||
:buffer (current-buffer)) |
||||
:go (lambda (state) |
||||
(haskell-process-send-string (plist-get state ':process) |
||||
":l DevelMain")) |
||||
:live (lambda (state buffer) |
||||
(haskell-process-live-build (plist-get state ':process) |
||||
buffer |
||||
nil)) |
||||
:complete (lambda (state response) |
||||
(haskell-process-load-complete |
||||
(plist-get state ':session) |
||||
(plist-get state ':process) |
||||
response |
||||
nil |
||||
(plist-get state ':buffer) |
||||
(lambda (ok) |
||||
(when ok |
||||
(haskell-process-queue-without-filters |
||||
(haskell-interactive-process) |
||||
"DevelMain.update") |
||||
(message "DevelMain updated."))))))))))) |
||||
|
||||
(provide 'haskell-load) |
@ -1,159 +0,0 @@
|
||||
;;; haskell-menu.el --- A Haskell sessions menu -*- lexical-binding: t -*- |
||||
|
||||
;; Copyright (C) 2013 Chris Done |
||||
|
||||
;; Author: Chris Done <chrisdone@gmail.com> |
||||
|
||||
;; 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: |
||||
|
||||
;;; Code: |
||||
|
||||
(require 'cl-lib) |
||||
(require 'haskell-compat) |
||||
(require 'haskell-session) |
||||
(require 'haskell-process) |
||||
(require 'haskell-interactive-mode) |
||||
|
||||
;;;###autoload |
||||
(defcustom haskell-menu-buffer-name "*haskell-menu*" |
||||
"The name of the Haskell session menu buffer" |
||||
:group 'haskell-interactive |
||||
:type 'string) |
||||
|
||||
;;;###autoload |
||||
(defun haskell-menu () |
||||
"Launch the Haskell sessions menu." |
||||
(interactive) |
||||
(or (get-buffer haskell-menu-buffer-name) |
||||
(with-current-buffer (get-buffer-create haskell-menu-buffer-name) |
||||
(haskell-menu-mode))) |
||||
(switch-to-buffer-other-window (get-buffer haskell-menu-buffer-name)) |
||||
(haskell-menu-revert-function nil nil)) |
||||
|
||||
(define-derived-mode haskell-menu-mode special-mode "Haskell Session Menu" |
||||
"Major mode for managing Haskell sessions. |
||||
Each line describes one session. |
||||
Letters do not insert themselves; instead, they are commands." |
||||
(setq buffer-read-only t) |
||||
(set (make-local-variable 'revert-buffer-function) |
||||
'haskell-menu-revert-function) |
||||
(setq truncate-lines t) |
||||
(haskell-menu-revert-function nil t)) |
||||
|
||||
(suppress-keymap haskell-menu-mode-map t) |
||||
(define-key haskell-menu-mode-map (kbd "n") 'next-line) |
||||
(define-key haskell-menu-mode-map (kbd "p") 'previous-line) |
||||
(define-key haskell-menu-mode-map (kbd "RET") 'haskell-menu-mode-ret) |
||||
|
||||
(defun haskell-menu-revert-function (_arg1 _arg2) |
||||
"Function to refresh the display." |
||||
(let ((buffer-read-only nil) |
||||
(orig-line (line-number-at-pos)) |
||||
(orig-col (current-column))) |
||||
(or (eq buffer-undo-list t) |
||||
(setq buffer-undo-list nil)) |
||||
(erase-buffer) |
||||
(haskell-menu-insert-menu) |
||||
(goto-char (point-min)) |
||||
(forward-line (1- orig-line)) |
||||
(forward-char orig-col))) |
||||
|
||||
(defun haskell-menu-insert-menu () |
||||
"Insert the Haskell sessions menu to the current buffer." |
||||
(if (null haskell-sessions) |
||||
(insert "No Haskell sessions.") |
||||
(haskell-menu-tabulate |
||||
(list "Name" "PID" "Time" "RSS" "Cabal directory" "Working directory" "Command") |
||||
(mapcar (lambda (session) |
||||
(let ((process (haskell-process-process (haskell-session-process session)))) |
||||
(cond |
||||
(process |
||||
(let ((id (process-id process))) |
||||
(list (propertize (haskell-session-name session) 'face 'buffer-menu-buffer) |
||||
(if (process-live-p process) (number-to-string id) "-") |
||||
(if (process-live-p process) |
||||
(format-time-string "%H:%M:%S" |
||||
(encode-time (cl-caddr (assoc 'etime (process-attributes id))) |
||||
0 0 0 0 0)) |
||||
"-") |
||||
(if (process-live-p process) |
||||
(concat (number-to-string (/ (cdr (assoc 'rss (process-attributes id))) |
||||
1024)) |
||||
"MB") |
||||
"-") |
||||
(haskell-session-cabal-dir session) |
||||
(haskell-session-current-dir session) |
||||
(mapconcat 'identity (process-command process) " ")))) |
||||
(t (list (propertize (haskell-session-name session) 'face 'buffer-menu-buffer) |
||||
"—" |
||||
"—" |
||||
"—" |
||||
(haskell-session-cabal-dir session) |
||||
(haskell-session-current-dir session)))))) |
||||
haskell-sessions)))) |
||||
|
||||
(defun haskell-menu-tabulate (headings rows) |
||||
"Prints a list of lists as a formatted table to the current buffer." |
||||
(let* ((columns (length headings)) |
||||
(widths (make-list columns 0))) |
||||
;; Calculate column widths. This is kind of hideous. |
||||
(dolist (row rows) |
||||
(setq widths |
||||
(let ((list (list))) |
||||
(dotimes (i columns) |
||||
(setq list (cons (max (nth i widths) |
||||
(1+ (length (nth i row))) |
||||
(1+ (length (nth i headings)))) |
||||
list))) |
||||
(reverse list)))) |
||||
;; Print headings. |
||||
(let ((heading (propertize " " 'display '(space :align-to 0)))) |
||||
(dotimes (i columns) |
||||
(setq heading (concat heading |
||||
(format (concat "%-" (number-to-string (nth i widths)) "s") |
||||
(nth i headings))))) |
||||
(setq header-line-format heading)) |
||||
;; Print tabulated rows. |
||||
(dolist (row rows) |
||||
(dotimes (i columns) |
||||
(insert (format (concat "%-" (number-to-string (nth i widths)) "s") |
||||
(nth i row)))) |
||||
(insert "\n")))) |
||||
|
||||
(defun haskell-menu-mode-ret () |
||||
"Handle RET key." |
||||
(interactive) |
||||
(let* ((name (save-excursion |
||||
(goto-char (line-beginning-position)) |
||||
(buffer-substring-no-properties (point) |
||||
(progn (search-forward " ") |
||||
(forward-char -1) |
||||
(point))))) |
||||
(session (car (cl-remove-if-not (lambda (session) |
||||
(string= (haskell-session-name session) |
||||
name)) |
||||
haskell-sessions)))) |
||||
(switch-to-buffer (haskell-session-interactive-buffer session)))) |
||||
|
||||
(provide 'haskell-menu) |
||||
|
||||
;;; haskell-menu.el ends here |
File diff suppressed because it is too large
Load Diff
@ -1,5 +0,0 @@
|
||||
(define-package "haskell-mode" "20151022.340" "A Haskell editing mode" |
||||
'((cl-lib "0.5"))) |
||||
;; Local Variables: |
||||
;; no-byte-compile: t |
||||
;; End: |
File diff suppressed because it is too large
Load Diff