diff options
Diffstat (limited to 'configs/shared/emacs/.emacs.d/elpa/haskell-mode-20180913.348/haskell-commands.el')
-rw-r--r-- | configs/shared/emacs/.emacs.d/elpa/haskell-mode-20180913.348/haskell-commands.el | 961 |
1 files changed, 961 insertions, 0 deletions
diff --git a/configs/shared/emacs/.emacs.d/elpa/haskell-mode-20180913.348/haskell-commands.el b/configs/shared/emacs/.emacs.d/elpa/haskell-mode-20180913.348/haskell-commands.el new file mode 100644 index 000000000000..b0898553d206 --- /dev/null +++ b/configs/shared/emacs/.emacs.d/elpa/haskell-mode-20180913.348/haskell-commands.el @@ -0,0 +1,961 @@ +;;; 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 © 2014 Chris Done. All rights reserved. +;; 2016 Arthur Fayzrakhmanov + +;; 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-mode) +(require 'haskell-compat) +(require 'haskell-process) +(require 'haskell-font-lock) +(require 'haskell-interactive-mode) +(require 'haskell-session) +(require 'haskell-string) +(require 'haskell-presentation-mode) +(require 'haskell-utils) +(require 'highlight-uses-mode) +(require 'haskell-cabal) + +(defcustom haskell-mode-stylish-haskell-path "stylish-haskell" + "Path to `stylish-haskell' executable." + :group 'haskell + :type 'string) + +(defcustom haskell-interactive-set-+c + t + "Issue ':set +c' in interactive session to support type introspection." + :group 'haskell-interactive + :type 'boolean) + +;;;###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 'cabal-new-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 + (mapconcat #'identity + (append '("Prelude.putStrLn \"\"" + ":set -v1") + (when haskell-interactive-set-+c + '(":set +c"))) ; :type-at in GHC 8+ + "\n")) + (haskell-process-send-string process ":set prompt \"\\4\"") + (haskell-process-send-string process (format ":set prompt2 \"%s\"" + haskell-interactive-prompt2))) + + :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"))) + (with-current-buffer (haskell-interactive-buffer) + (goto-char haskell-interactive-mode-prompt-start)))))) + +(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) + "Hayoo for IDENT, return a list of modules" + ;; 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 *.lhs *.hsc *.chs *.hs-boot *.lhs-boot" + (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) + "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") + (if (haskell-session-maybe) + (let ((initial-loc (point-marker)) + (loc (haskell-mode-find-def (haskell-ident-at-point)))) + (haskell-mode-handle-generic-loc loc) + (unless (equal initial-loc (point-marker)) + (xref-push-marker-stack initial-loc))) + (call-interactively 'haskell-mode-tag-find))) + +;;;###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 identifier 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 "%s" 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-string-drop-qualifier + (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 (progn + (find-file (elt loc 1)) + (goto-char (point-min)) + (forward-line (1- (elt loc 2))) + (goto-char (+ (line-beginning-position) + (1- (elt loc 3)))))) + (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 8+ or 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-file' bound to +\\[haskell-process-load-file]. + +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-repl-response-error-status sig))) + + (cl-case res-type + ;; neither popup presentation buffer + ;; nor insert response in error case + ('unknown-command + (message "This command requires GHCi 8+ or GHCi-ng. Please read command description for details.")) + ('option-missing + (message "Could not infer type signature. You need to load file first. Also :set +c is required, see customization `haskell-interactive-set-+c'. 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 + (message "Type signature insertion was prevented. These commands were registered: %s" + (cdr (reverse haskell-utils-async-post-command-flag)))) + ;; 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)))))))) + +(make-obsolete 'haskell-process-generate-tags + 'haskell-mode-generate-tags + "2016-03-14") +(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) + (let* ((process (car state)) + (cabal-dir (haskell-session-cabal-dir + (haskell-process-session process))) + (command (haskell-cabal--compose-hasktags-command cabal-dir))) + (haskell-process-send-string process command))) + :complete (lambda (state _response) + (when (cdr state) + (let ((tags-file-name + (haskell-session-tags-filename + (haskell-process-session (car state))))) + (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 (or (eq 'cabal-repl (haskell-process-type)) + (eq 'cabal-new-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 + (haskell-mode-toggle-interactive-prompt-state) + (unwind-protect + (progn + (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))) + ;; unwind + (haskell-mode-toggle-interactive-prompt-state t)))))) + +;;;###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 (haskell-process-type)) + 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 (not (string= old-target target)) + (haskell-mode-toggle-interactive-prompt-state) + (unwind-protect + (when (y-or-n-p "Target changed, restart haskell process?") + (haskell-process-start session))) + (haskell-mode-toggle-interactive-prompt-state t))))) + +;;;###autoload +(defun haskell-mode-stylish-buffer () + "Apply stylish-haskell to the current buffer. + +Use `haskell-mode-stylish-haskell-path' to know where to find +stylish-haskell executable. This function tries to preserve +cursor position and markers by using +`haskell-mode-buffer-apply-command'." + (interactive) + (haskell-mode-buffer-apply-command haskell-mode-stylish-haskell-path)) + +(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* ((out-file (make-temp-file "stylish-output")) + (err-file (make-temp-file "stylish-error"))) + (unwind-protect + (let* ((_errcode + (call-process-region (point-min) (point-max) cmd nil + `((:file ,out-file) ,err-file) + nil)) + (err-file-empty-p + (equal 0 (nth 7 (file-attributes err-file)))) + (out-file-empty-p + (equal 0 (nth 7 (file-attributes out-file))))) + (if err-file-empty-p + (if out-file-empty-p + (message "Error: %s produced no output and no error information, leaving buffer alone" cmd) + ;; Command successful, insert file with replacement to preserve + ;; markers. + (insert-file-contents out-file nil nil nil t)) + (progn + ;; non-null stderr, command must have failed + (with-current-buffer + (get-buffer-create "*haskell-mode*") + (insert-file-contents err-file) + (buffer-string)) + (message "Error: %s ended with errors, leaving buffer alone, see *haskell-mode* buffer for stderr" cmd) + (with-temp-buffer + (insert-file-contents err-file) + ;; use (warning-minimum-level :debug) to see this + (display-warning cmd + (buffer-substring-no-properties (point-min) (point-max)) + :debug))))) + (ignore-errors + (delete-file err-file)) + (ignore-errors + (delete-file out-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 "%s" 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 |