# HG changeset patch # User Luke Hoersten # Date 1442440387 18000 # Node ID e848f1863d6b19c1c15b767908f7c630073dbeeb # Parent f3334abf432266dee2877ac00c0615dd15efa94c Trying stack-ide-mode. diff -r f3334abf4322 -r e848f1863d6b elisp/haskell-init.el --- a/elisp/haskell-init.el Tue Sep 15 14:40:04 2015 -0500 +++ b/elisp/haskell-init.el Wed Sep 16 16:53:07 2015 -0500 @@ -5,13 +5,15 @@ (require 'package-require) (package-require '(haskell-mode yasnippet haskell-snippets flycheck flycheck-haskell shm)) +(add-to-list 'load-path "~/.emacs.d/elisp/stack-mode") + ;; Load haskell-mode from source ;; (add-to-list 'load-path "~/Code/elisp/haskell-mode/") ;; (require 'haskell-mode-autoloads) (require 'haskell) (require 'haskell-mode) -(require 'haskell-process) +(require 'stack-mode) (require 'haskell-interactive-mode) (require 'haskell-snippets) (require 'shm) @@ -33,23 +35,6 @@ (rename-buffer "*who-calls*") (switch-to-buffer-other-window buffer))))) -(defun haskell-process-all-types () - "List all types in a grep-mode buffer." - (interactive) - (let ((session (haskell-session))) - (switch-to-buffer (get-buffer-create (format "*%s:all-types*" - (haskell-session-name (haskell-session))))) - (setq haskell-session session) - (cd (haskell-session-current-dir session)) - (let ((inhibit-read-only t)) - (erase-buffer) - (let ((haskell-process-log nil)) - (insert (haskell-process-queue-sync-request (haskell-process) ":all-types"))) - (unless (eq major-mode 'compilation-mode) - (compilation-mode) - (setq compilation-error-regexp-alist - haskell-compilation-error-regexp-alist))))) - ;;; haskell-mode (add-hook 'haskell-mode-hook @@ -57,7 +42,9 @@ ;; (imenu-add-menubar-index) (flycheck-mode) (flycheck-haskell-setup) + (flycheck-disable-checker 'haskell-ghc) ;; (haskell-indentation-mode t) + (stack-mode) (subword-mode) (electric-indent-mode 0) (structured-haskell-mode t) @@ -71,66 +58,69 @@ '(haskell-indentation-layout-offset 4) '(haskell-indentation-left-offset 4) - '(shm-use-presentation-mode t) - '(shm-auto-insert-skeletons t) - '(shm-auto-insert-bangs t) - - '(haskell-process-type 'cabal-repl) - ;; '(haskell-process-args-cabal-repl '("--ghc-option=-ferror-spans" "--with-ghc=ghci-ng")) ;; ghci-ng - ;; '(haskell-process-path-ghci "ghci-ng") ;; ghci-ng - '(haskell-process-args-ghci "-ferror-spans") - '(haskell-process-suggest-remove-import-lines t) - '(haskell-process-auto-import-loaded-modules t) - '(haskell-process-log t) - '(haskell-process-reload-with-fbytecode nil) - '(haskell-process-use-presentation-mode t) - '(haskell-process-suggest-haskell-docs-imports t) - '(haskell-process-suggest-hoogle-imports t) - '(haskell-process-generate-tags nil) - '(haskell-process-show-debug-tips nil) + '(haskell-interactive-mode-eval-pretty t) + '(haskell-interactive-mode-scroll-to-bottom t) + '(haskell-interactive-mode-eval-mode 'haskell-mode) '(haskell-notify-p t) '(haskell-align-imports-pad-after-name t) '(haskell-ask-also-kill-buffers nil) '(haskell-import-mapping t) - '(haskell-interactive-mode-eval-pretty t) - '(haskell-interactive-mode-scroll-to-bottom t) - '(haskell-interactive-mode-eval-mode 'haskell-mode)) + '(shm-use-presentation-mode t) + '(shm-auto-insert-skeletons t) + '(shm-auto-insert-bangs t)) + -;; haskell-interactive-mode keybindings -(define-key interactive-haskell-mode-map (kbd "C-c C-l") 'haskell-process-load-or-reload) -(define-key interactive-haskell-mode-map (kbd "M-,") 'haskell-who-calls) -(define-key interactive-haskell-mode-map (kbd "M-.") 'haskell-mode-goto-loc) -(define-key interactive-haskell-mode-map (kbd "C-?") 'haskell-mode-find-uses) -(define-key interactive-haskell-mode-map (kbd "C-c C-c") 'haskell-process-cabal-build) -(define-key interactive-haskell-mode-map (kbd "C-c C-t") 'haskell-mode-show-type-at) -(define-key interactive-haskell-mode-map (kbd "C-`") 'haskell-interactive-bring) -(define-key interactive-haskell-mode-map (kbd "C-c C-k") 'haskell-process-clear) -(define-key interactive-haskell-mode-map (kbd "C-c c") 'haskell-process-cabal) +;; ;; '(haskell-process-type 'cabal-repl) +;; ;; ;; '(haskell-process-args-cabal-repl '("--ghc-option=-ferror-spans" "--with-ghc=ghci-ng")) ;; ghci-ng +;; ;; ;; '(haskell-process-path-ghci "ghci-ng") ;; ghci-ng +;; ;; '(haskell-process-args-ghci "-ferror-spans") +;; ;; '(haskell-process-suggest-remove-import-lines t) +;; ;; '(haskell-process-auto-import-loaded-modules t) +;; ;; '(haskell-process-log t) +;; ;; '(haskell-process-reload-with-fbytecode nil) +;; ;; '(haskell-process-use-presentation-mode t) +;; ;; '(haskell-process-suggest-haskell-docs-imports t) +;; ;; '(haskell-process-suggest-hoogle-imports t) +;; ;; '(haskell-process-generate-tags nil) +;; ;; '(haskell-process-show-debug-tips nil) + + -(define-key haskell-interactive-mode-map (kbd "C-c C-i") 'haskell-process-do-info) -(define-key haskell-interactive-mode-map (kbd "C-") 'haskell-interactive-mode-error-backward) -(define-key haskell-interactive-mode-map (kbd "C-") 'haskell-interactive-mode-error-forward) -(define-key haskell-interactive-mode-map (kbd "C-c c") 'haskell-process-cabal) +;; ;; haskell-interactive-mode keybindings +;; (define-key interactive-haskell-mode-map (kbd "C-c C-l") 'haskell-process-load-or-reload) +;; (define-key interactive-haskell-mode-map (kbd "M-,") 'haskell-who-calls) +;; (define-key interactive-haskell-mode-map (kbd "M-.") 'haskell-mode-goto-loc) +;; (define-key interactive-haskell-mode-map (kbd "C-?") 'haskell-mode-find-uses) +;; (define-key interactive-haskell-mode-map (kbd "C-c C-c") 'haskell-process-cabal-build) +;; (define-key interactive-haskell-mode-map (kbd "C-c C-t") 'haskell-mode-show-type-at) +;; (define-key interactive-haskell-mode-map (kbd "C-`") 'haskell-interactive-bring) +;; (define-key interactive-haskell-mode-map (kbd "C-c C-k") 'haskell-process-clear) +;; (define-key interactive-haskell-mode-map (kbd "C-c c") 'haskell-process-cabal) + +;; (define-key haskell-interactive-mode-map (kbd "C-c C-i") 'haskell-process-do-info) +;; (define-key haskell-interactive-mode-map (kbd "C-") 'haskell-interactive-mode-error-backward) +;; (define-key haskell-interactive-mode-map (kbd "C-") 'haskell-interactive-mode-error-forward) +;; (define-key haskell-interactive-mode-map (kbd "C-c c") 'haskell-process-cabal) -;; haskell-mode -(define-key haskell-mode-map (kbd "C-c C-l") 'haskell-process-load-or-reload) -(define-key haskell-mode-map (kbd "C-`") 'haskell-interactive-bring) -(define-key haskell-mode-map (kbd "C-c C-t") 'haskell-process-do-type) -(define-key haskell-mode-map (kbd "C-c i") 'haskell-navigate-imports) -(define-key haskell-mode-map (kbd "C-c C-i") 'haskell-process-do-info) -(define-key haskell-mode-map (kbd "C-c C-c") 'haskell-process-cabal-build) -(define-key haskell-mode-map (kbd "C-c C-d") 'haskell-describe) -(define-key haskell-mode-map (kbd "C-c C-k") 'haskell-process-clear) -(define-key haskell-mode-map (kbd "C-c c") 'haskell-process-cabal) -(define-key haskell-mode-map (kbd "SPC") 'haskell-mode-contextual-space) +;; ;; haskell-mode +;; (define-key haskell-mode-map (kbd "C-c C-l") 'haskell-process-load-or-reload) +;; (define-key haskell-mode-map (kbd "C-`") 'haskell-interactive-bring) +;; (define-key haskell-mode-map (kbd "C-c C-t") 'haskell-process-do-type) +;; (define-key haskell-mode-map (kbd "C-c i") 'haskell-navigate-imports) +;; (define-key haskell-mode-map (kbd "C-c C-i") 'haskell-process-do-info) +;; (define-key haskell-mode-map (kbd "C-c C-c") 'haskell-process-cabal-build) +;; (define-key haskell-mode-map (kbd "C-c C-d") 'haskell-describe) +;; (define-key haskell-mode-map (kbd "C-c C-k") 'haskell-process-clear) +;; (define-key haskell-mode-map (kbd "C-c c") 'haskell-process-cabal) +;; (define-key haskell-mode-map (kbd "SPC") 'haskell-mode-contextual-space) -;; cabal -(define-key haskell-cabal-mode-map (kbd "C-`") 'haskell-interactive-bring) -(define-key haskell-cabal-mode-map (kbd "C-c C-k") 'haskell-process-clear) -(define-key haskell-cabal-mode-map (kbd "C-c C-c") 'haskell-process-cabal-build) -(define-key haskell-cabal-mode-map (kbd "C-c c") 'haskell-process-cabal) +;; ;; cabal +;; (define-key haskell-cabal-mode-map (kbd "C-`") 'haskell-interactive-bring) +;; (define-key haskell-cabal-mode-map (kbd "C-c C-k") 'haskell-process-clear) +;; (define-key haskell-cabal-mode-map (kbd "C-c C-c") 'haskell-process-cabal-build) +;; (define-key haskell-cabal-mode-map (kbd "C-c c") 'haskell-process-cabal) ;; shm (define-key shm-map (kbd "C-c C-p") 'shm/expand-pattern) diff -r f3334abf4322 -r e848f1863d6b elisp/stack-mode/LICENSE --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/elisp/stack-mode/LICENSE Wed Sep 16 16:53:07 2015 -0500 @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff -r f3334abf4322 -r e848f1863d6b elisp/stack-mode/README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/elisp/stack-mode/README.md Wed Sep 16 16:53:07 2015 -0500 @@ -0,0 +1,1 @@ +# stack-mode diff -r f3334abf4322 -r e848f1863d6b elisp/stack-mode/checklist.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/elisp/stack-mode/checklist.el Wed Sep 16 16:53:07 2015 -0500 @@ -0,0 +1,94 @@ +;;; checklist.el --- Simple checklist UI functionality + +;; Copyright (c) 2015 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 . + +;;; Code: + +(defvar checklist-read-checklist-result nil) +(make-variable-buffer-local 'checklist-read-checklist-result) + +(defun checklist-read-checklist (prompt choices) + "Prompt with PROMPT to choose one or more from the list of CHOICES." + (switch-to-buffer (generate-new-buffer-name "checklist")) + (let ((inhibit-read-only t)) + (special-mode) + (setq buffer-read-only t) + (setq cursor-type 'box) + (insert (concat prompt "\n\n")) + (use-local-map (copy-keymap special-mode-map)) + (local-set-key "n" 'next-line) + (local-set-key "p" 'previous-line) + (local-set-key (kbd "RET") 'checklist-checklist-choose) + (local-set-key (kbd "SPC") 'checklist-checklist-choose) + (local-set-key (kbd "C-c C-c") 'checklist-checklist-done) + (save-excursion + (cl-loop for choice in choices + do (insert (propertize (concat "[ ] " (cdr choice) "\n") + 'checklist-check-item (car choice)))) + (insert "\n") + (insert "Hit " (propertize "C-c C-c" 'face 'bold) " to finish.")) + (add-hook 'post-command-hook #'checklist-highlight-section t t) + (setq checklist-read-checklist-result nil) + (recursive-edit) + (let ((result checklist-read-checklist-result) + (kill-buffer-query-functions nil)) + (kill-buffer (current-buffer)) + result))) + +(defun checklist-checklist-done () + "Done with the checklist, return the result." + (interactive) + (throw 'exit nil)) + +(defun checklist-highlight-section () + "Highlight the item at point." + (remove-overlays) + (when (get-text-property (point) 'checklist-check-item) + (let ((o (make-overlay (line-beginning-position) + (1+ (line-end-position))))) + (overlay-put o 'face 'secondary-selection)))) + +(defun checklist-checklist-choose () + "Choose the current item at point." + (interactive) + (let ((inhibit-read-only t) + (checked (get-text-property (point) 'checklist-item-checked))) + (when (get-text-property (point) 'checklist-check-item) + (if checked + (setq checklist-read-checklist-result + (delete (get-text-property (point) 'checklist-check-item) + checklist-read-checklist-result)) + (add-to-list 'checklist-read-checklist-result + (get-text-property (point) 'checklist-check-item) + t)) + (save-excursion (goto-char (line-beginning-position)) + (delete-region (line-beginning-position) + (+ (line-beginning-position) (length "[ ]"))) + (if checked + (insert "[ ]") + (insert "[x]"))) + (put-text-property (line-beginning-position) + (1+ (line-end-position)) + 'checklist-item-checked (not checked)) + (put-text-property (line-beginning-position) + (1+ (line-end-position)) + 'face (if checked nil 'bold)) + (put-text-property (line-beginning-position) + (1+ (line-end-position)) + 'checklist-check-item t) + (checklist-highlight-section)))) + +(provide 'checklist) diff -r f3334abf4322 -r e848f1863d6b elisp/stack-mode/fifo.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/elisp/stack-mode/fifo.el Wed Sep 16 16:53:07 2015 -0500 @@ -0,0 +1,55 @@ +;;; fifo.el --- FIFO queue. + +;; Copyright (c) 2015 Chris Done. + +;; 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 . + +;;; Code: + +(require 'cl-lib) + +(defun fifo-make () + "Make a fifo queue." + (cons 0 nil)) + +(defun fifo-push (q a) + "Push a new item onto the queue." + (cl-assert (consp q) nil "Must be a queue.") + (setcar q (1+ (car q))) + (let ((next q) + (continue t)) + (while (not (null (cdr next))) + (setq next (cdr next))) + (setcdr next (cons a nil))) + q) + +(defun fifo-pop (q) + "Pop the next item on the queue." + (cl-assert (consp q) nil "Must be a queue.") + (cl-assert (consp (cdr q)) nil "No items to pop from queue.") + (setcar q (1- (car q))) + (let ((a (car (cdr q)))) + (setcdr q (cdr (cdr q))) + a)) + +(defun fifo-size (q) + "Get the size of the queue." + (cl-assert (consp q) nil "Must be a queue.") + (car q)) + +(defun fifo-null-p (q) + "Is the queue empty?" + (= (fifo-size q) 0)) + +(provide 'fifo) diff -r f3334abf4322 -r e848f1863d6b elisp/stack-mode/stack-mode.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/elisp/stack-mode/stack-mode.el Wed Sep 16 16:53:07 2015 -0500 @@ -0,0 +1,927 @@ +;;; stack-mode.el --- A minor mode enabling various features based on +;;; stack-ide. + +;; Copyright (c) 2015 Chris Done. + +;; 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 . + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Imports + +(require 'haskell-mode) +(require 'haskell-cabal) +(require 'cl-lib) +(require 'fifo) +(require 'flycheck) +(require 'json) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Modes + +(define-minor-mode stack-mode + "A minor mode enabling various features based on stack-ide. + +Automatically starts and stops flycheck-mode when you +enable/disable it. It makes this assumption in the interest of +easier user experience. Disable with `stack-mode-manage-flycheck'." + :lighter " Stack" + :keymap (let ((map (make-sparse-keymap))) + (define-key map (kbd "M-.") 'stack-mode-goto) + (define-key map (kbd "C-c C-k") 'stack-mode-clear) + (define-key map (kbd "C-c C-t") 'stack-mode-type) + (define-key map (kbd "C-c C-i") 'stack-mode-info) + (define-key map (kbd "C-c C-l") 'stack-mode-load) + map) + (when (buffer-file-name) + (if stack-mode + (progn (when (bound-and-true-p interactive-haskell-mode) + (when (y-or-n-p "interactive-haskell-mode is enabled. Disable it?") + (interactive-haskell-mode -1))) + (when stack-mode-manage-flycheck + (flycheck-mode 1) + (flycheck-select-checker 'stack-ide) + (flycheck-buffer))) + (when stack-mode-manage-flycheck + (flycheck-mode -1))))) + +(define-derived-mode inferior-stack-mode fundamental-mode "Inferior-Stack-IDE" + "Major mode for interacting with an inferior stack-ide process.") + +(define-key inferior-stack-mode-map (kbd "C-c C-c") 'stack-mode-stop) +(define-key inferior-stack-mode-map (kbd "C-c C-k") 'stack-mode-clear) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Customization + +(defgroup stack-mode nil + "IDE backend support for Haskell." + :group 'haskell) + +(defcustom stack-mode-proc-path + "stack" + "Path to the stack executable." + :type 'string + :group 'stack-mode) + +(defcustom stack-mode-manage-flycheck + t + "Automatically start and stop flycheck when the minor mode is +enabled/disabled." + :type 'boolean + :group 'stack-mode) + +(defcustom stack-mode-print-error-messages + nil + "Print error messages after loading the project?" + :type 'boolean + :group 'stack-mode) + +(defcustom stack-mode-show-popup + nil + "Show type and info messages in a popup?" + :type 'boolean + :group 'stack-mode) + +(defvar stack-mode-queue nil) +(make-variable-buffer-local 'stack-mode-queue) + +(defvar stack-mode-back-queue nil) +(make-variable-buffer-local 'stack-mode-back-queue) + +(defvar stack-mode-buffer nil) +(make-variable-buffer-local 'stack-mode-buffer) + +(defvar stack-mode-name nil) +(make-variable-buffer-local 'stack-mode-name) + +(defvar stack-mode-tried-to-start nil) +(make-variable-buffer-local 'stack-mode-tried-to-start) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Interactive functions + +(defun stack-mode-status () + "Print the status of the current stack process." + (interactive) + (if (stack-mode-buffer) + (if (stack-mode-process) + (if (process-live-p (stack-mode-process)) + (message "The process is live.") + (message "There is a Stack process, but it's dead.")) + (message "There is a stack buffer, but no Stack process.")) + (message "There is no Stack buffer."))) + +(defun stack-mode-start () + "Start an inferior process and buffer." + (interactive) + (if (stack-mode-live-p) + (switch-to-buffer (stack-mode-buffer)) + (with-current-buffer (stack-mode-buffer) + (setq buffer-read-only t) + (inferior-stack-mode) + (stack-mode-set-initial-command) + (setq stack-mode-buffer "") + (let* ((project-name (stack-mode-name)) + (name (stack-mode-process-name project-name)) + (args (append (list name + nil + stack-mode-proc-path + "ide" + "start") + (list project-name))) + (process (or (get-process name) + (progn (stack-mode-log "Starting: %S" args) + (apply #'start-process + args))))) + (set-process-sentinel process 'stack-mode-sentinel) + (set-process-filter process 'stack-mode-filter))))) + +(defun stack-mode-set-initial-command () + "Set the initial command callback. The `stack ide` command will +reload targets on start-up, so that's the default command we'll +start with." + (setq stack-mode-current-command + (list :json nil + :data nil + :cont 'stack-mode-loading-callback + :label nil)) + (setq stack-mode-queue (fifo-make)) + (stack-mode-log "Set initial command.")) + +(defun stack-mode-stop () + "Stop the process." + (interactive) + (with-current-buffer (stack-mode-buffer) + (when (stack-mode-process) + (setq stack-mode-current-command nil) + (setq stack-mode-buffer "") + (kill-process (stack-mode-process)) + (delete-process (stack-mode-process))))) + +(defun stack-mode-reset () + "Reset the process." + (interactive) + (with-current-buffer (stack-mode-buffer) + (when (stack-mode-process) + (setq stack-mode-current-command nil) + (setq stack-mode-buffer "") + (setq stack-mode-queue (fifo-make))))) + +(defun stack-mode-restart () + "Restart the process with a fresh command queue." + (interactive) + (stack-mode-stop) + (stack-mode-start)) + +(defun stack-mode-live-p () + "Is the process alive?" + (and (stack-mode-process) + (process-live-p (stack-mode-process)))) + +(defun stack-mode-clear () + "Clear the interaction buffer." + (interactive) + (with-current-buffer (stack-mode-buffer) + (let ((inhibit-read-only t)) + (erase-buffer)))) + +(defun stack-mode-load () + "Load the current buffer's file." + (interactive) + (save-buffer) + (with-current-buffer (stack-mode-buffer) + (stack-mode-reload))) + +(defun stack-mode-goto () + "Go to definition of thing at point." + (interactive) + (let ((filename (buffer-file-name)) + (module-name (haskell-guess-module-name)) + (span (stack-mode-span))) + (let* ((span-info + (stack-mode-get-span-info + module-name + (with-current-buffer (stack-mode-buffer) + (file-relative-name filename default-directory)) + span)) + (infos + (stack-contents + span-info)) + (_ (when (and (vectorp infos) (= 0 (length infos))) + (error "Couldn't find location for this. Is the module loaded in the backend? +Run `M-x stack-mode-list-loaded-modules' to see what's loaded."))) + (parts (mapcar #'identity (elt infos 0))) + (info (stack-contents (elt parts 0))) + (span (elt parts 1)) + (scope (stack-lookup 'tag (stack-lookup 'idScope info))) + (def-span (stack-lookup-contents + 'idDefSpan + (stack-lookup 'idProp info)))) + (cond + ((listp def-span) + (stack-mode-goto-span def-span)) + (t + (let* ((imported-from + (stack-lookup + 'idImportedFrom + (stack-lookup 'idScope info))) + (imported-module (stack-lookup 'moduleName imported-from)) + (defined-in (stack-lookup + 'idDefinedIn + (stack-lookup 'idProp info))) + (package (stack-lookup 'modulePackage defined-in)) + (package-name (stack-lookup 'packageName package)) + (package-ver (stack-lookup 'packageVersion package)) + (module (stack-lookup 'moduleName defined-in))) + (message "Imported via %s, defined in %s (%s-%s)" + (haskell-fontify-as-mode imported-module 'haskell-mode) + (haskell-fontify-as-mode module 'haskell-mode) + package-name + package-ver))))))) + +(defun stack-mode-list-loaded-modules () + "List the loaded modules in the backend." + (interactive) + (let ((modules + (stack-contents + (with-current-buffer (stack-mode-buffer) + (stack-mode-call + `((tag . "RequestGetLoadedModules") + (contents + . []))))))) + (pop-to-buffer (stack-mode-buffer)) + (stack-mode-log "Loaded modules: %s" + (mapconcat #'identity + (sort (mapcar #'identity modules) #'string<) + "\n")))) + +(defun stack-mode-info () + "Display the info of the thing at point." + (interactive) + (let* ((filename (buffer-file-name)) + (module-name (haskell-guess-module-name)) + (points (stack-mode-points)) + (orig (point)) + (span (stack-mode-span-from-points (car points) + (cdr points))) + (info (stack-mode-get-span-info + module-name + (with-current-buffer (stack-mode-buffer) + (file-relative-name filename default-directory)) + span)) + (info-contents (stack-contents (elt (elt (stack-contents info) 0) 0))) + (scope (stack-lookup 'idScope info-contents)) + (prop (stack-lookup 'idProp info-contents)) + (qual (stack-lookup 'idImportQual scope)) + (from (stack-lookup 'idImportedFrom scope)) + (span (stack-lookup 'idImportSpan scope)) + + (space (stack-lookup 'idSpace prop)) + (idDefSpan (stack-lookup 'idDefSpan prop)) + (idDefinedIn (stack-lookup 'idDefinedIn prop)) + (modulePackage (stack-lookup 'modulePackage idDefinedIn)) + (moduleName (stack-lookup 'moduleName idDefinedIn)) + (packageVersion (stack-lookup 'packageVersion modulePackage)) + (packageKey (stack-lookup 'packageKey modulePackage)) + (packageName (stack-lookup 'packageKey modulePackage)) + (idType (stack-lookup 'idType prop)) + (idName (stack-lookup 'idName prop))) + (let ((info-string (concat + "Identifier: " (haskell-fontify-as-mode idName 'haskell-mode) "\n" + "Type: " (haskell-fontify-as-mode idType 'haskell-mode) "\n" + "Module: " (haskell-fontify-as-mode moduleName 'haskell-mode) "\n" + "Package: " (if (string= "main" packageName) + "(this one)" + packageName)))) + (cond (stack-mode-show-popup + (when (boundp popup-tip) + (popup-tip info-string))) + (t (message info-string)))))) + +(defun stack-mode-type (&optional insert-value) + "Display type info of thing at point." + (interactive "P") + (let* ((filename (buffer-file-name)) + (module-name (haskell-guess-module-name)) + (points (stack-mode-points)) + (orig (point)) + (span (stack-mode-span-from-points (car points) + (cdr points)))) + (let* ((types (stack-contents + (stack-mode-get-exp-types + module-name + (with-current-buffer (stack-mode-buffer) + (file-relative-name filename default-directory)) + span))) + (types (mapcar #'identity types)) + (code (buffer-substring-no-properties + (car points) + (cdr points))) + (type (stack-contents (car types))) + (ty (stack-lookup 'text type))) + (if insert-value + (let ((ident-pos (haskell-ident-pos-at-point))) + (cond + ((region-active-p) + (delete-region (region-beginning) + (region-end)) + (insert "(" code " :: " ty ")") + (goto-char (1+ orig))) + ((= (line-beginning-position) (car ident-pos)) + (goto-char (line-beginning-position)) + (insert code " :: " (haskell-fontify-as-mode ty 'haskell-mode) + "\n")) + (t + (save-excursion + (goto-char (car ident-pos)) + (let ((col (current-column))) + (save-excursion (insert "\n") + (indent-to col)) + (insert code " :: " (haskell-fontify-as-mode ty 'haskell-mode))))))) + (unless (null types) + (let ((type-string (format "%s" + (mapconcat (lambda (type) + (haskell-fontify-as-mode + (concat + code + " :: " + (elt type 0)) + 'haskell-mode)) + (cl-subseq types 0 1) + "\n")))) + (cond (stack-mode-show-popup (popup-tip type-string)) + (t (message type-string))))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Process filters and sentinel + +(defun stack-mode-filter (process response) + (with-current-buffer (stack-mode-buffer (stack-mode-name-from-process process)) + (if stack-mode-current-command + (let* ((lines (split-string (concat stack-mode-buffer response) "\n"))) + (setq stack-mode-buffer (car (last lines))) + (setq lines (butlast lines)) + (let ((data (plist-get stack-mode-current-command :data)) + (cont (plist-get stack-mode-current-command :cont))) + (while lines + (let ((line (pop lines))) + (stack-mode-log + "%s <- %s" + (if (plist-get stack-mode-current-command :label) + (format "[%s]" (plist-get stack-mode-current-command :label)) + "") + (haskell-fontify-as-mode line 'javascript-mode)) + (when (let* ((error-msg nil) + (json (condition-case e + (json-read-from-string line) + (error "Problem reading JSON from server, probably an error message:\n%s" line))) + (ret (condition-case e + (funcall cont data json) + (error (setq error-msg e) + :error)))) + (cl-ecase ret + (:done t) + (:continue nil) + (:error + (setq stack-mode-buffer "") + (setq stack-mode-current-command nil) + (setq stack-mode-queue nil) + (error "Command handler error: %S\n\nThe command queue has been cleared." + error-msg)) + (t + (error "A command handler must return either :done or :continue, +but it returned: %S +command was: %S" ret stack-mode-current-command)))) + (cl-loop for line in lines + do (stack-mode-log + "Extraneous lines after command completed: %s" + (haskell-fontify-as-mode line 'javascript-mode))) + (setq stack-mode-current-command nil) + (setq lines nil) + (stack-mode-queue-trigger)))))) + (stack-mode-log "Ignoring: %s" + (haskell-fontify-as-mode response 'javascript-mode))))) + +(defun stack-mode-sentinel (process event) + (with-current-buffer (stack-mode-buffer (stack-mode-name-from-process process)) + (stack-mode-log "Process event: %s" event))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Command queue + +(defvar stack-mode-current-command nil + "Current command handler.") +(make-variable-buffer-local 'stack-mode-current-command) + +(defvar stack-mode-buffer "" + "A buffer for the process.") +(make-variable-buffer-local 'stack-mode-buffer) + +(defvar stack-mode-queue nil + "Command queue.") +(make-variable-buffer-local 'stack-mode-queue) + +(defun stack-mode-queue () + "Get the FIFO queue of this process." + (or stack-mode-queue + (setq stack-mode-queue (fifo-make)))) + +(defun stack-mode-back-queue () + "Get the FIFO back queue of this process." + (or stack-mode-back-queue + (setq stack-mode-back-queue (fifo-make)))) + +(defun stack-mode-enqueue-front (json data cont &optional label) + "Enqueue a JSON command to the command queue, calling (CONT +DATA line) for each response line until CONT returns nil. This is +the first priority queue, anything pushed to this queue will be +run before anything in the back queue." + (cond + ((stack-mode-live-p) + (stack-mode-log "[%s] => %s" label (haskell-fontify-as-mode (json-encode json) 'javascript-mode)) + (fifo-push (stack-mode-queue) + (list :json json :data data :cont cont :label label)) + (stack-mode-queue-trigger)) + (t (stack-mode-try-start)))) + +(defun stack-mode-enqueue (json data cont &optional label) + "Same as `stack-mode-front', but puts it on the back +queue. Items are only moved onto the front queue when the front +queue is empty. This lets a command which consists of a few back +and forth steps to continue its processing uninterrupted." + (cond + ((stack-mode-live-p) + (stack-mode-log "[%s] ~> %s" label (haskell-fontify-as-mode (json-encode json) 'javascript-mode)) + (fifo-push (stack-mode-back-queue) + (list :json json :data data :cont cont :label label)) + (stack-mode-queue-trigger)) + (t (stack-mode-try-start)))) + +(defun stack-mode-try-start () + "Try to start, but only try once." + (cond + ((not stack-mode-tried-to-start) + (setq stack-mode-tried-to-start t) + (message "Starting a Stack IDE backend process for this project: %s, stack directory: %s" + (stack-mode-cabal-name) + (stack-mode-dir)) + (stack-mode-start)) + (t (message "Attempted to run a Stack IDE command, but the server isn't started. We already tried once this session. Run `M-x stack-mode-restart` to confirm that you want to start it.")))) + +(defun stack-mode-call (json) + "Call a JSON command. Wait for any existing queued commands to +complete, then sends the request, blocking on the +response. Returns the response." + (let ((data (list nil))) + (stack-mode-enqueue + json data + (lambda (data reply) + (setcar data reply) + :done)) + (stack-mode-queue-flush) + (car-safe data))) + +(defun stack-mode-queue-processed-p () + "Return t if command queue has been completely processed." + (and (fifo-null-p stack-mode-queue) + (null stack-mode-current-command))) + +(defun stack-mode-queue-flush () + "Block till PROCESS's command queue has been completely processed. +This uses `accept-process-output' internally." + (let ((proc (stack-mode-process))) + (while (not (stack-mode-queue-processed-p)) + (stack-mode-queue-trigger) + (accept-process-output proc 1)))) + +(defun stack-mode-queue-trigger () + "Trigger the next command in the queue if there is no current +command." + (if stack-mode-current-command + (unless (fifo-null-p (stack-mode-queue)) + (stack-mode-log "Stack command queue is currently active, waiting ...")) + (when (fifo-null-p (stack-mode-queue)) + (stack-mode-log "Command queue is now empty.") + (unless (fifo-null-p (stack-mode-back-queue)) + (stack-mode-log "Pushing next item from back queue to front queue ...") + (fifo-push (stack-mode-queue) + (fifo-pop (stack-mode-back-queue))))) + (unless (fifo-null-p (stack-mode-queue)) + (setq stack-mode-current-command + (fifo-pop (stack-mode-queue))) + (stack-mode-log + "[%S] -> %s" + (plist-get stack-mode-current-command :label) + (haskell-fontify-as-mode + (json-encode (plist-get stack-mode-current-command :json)) + 'javascript-mode)) + (process-send-string + (stack-mode-process) + (concat (json-encode (plist-get stack-mode-current-command :json)) + "\n"))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Project functions + +(defun stack-mode-packages () + "Get packages for the Stack configuration." + (split-string (shell-command-to-string "stack ide packages") "\n" t)) + +(defun stack-mode-process () + "Get the current process." + (get-process (stack-mode-process-name (stack-mode-name)))) + +(defun stack-mode-buffer (&optional name) + "The inferior buffer." + (let ((default-directory (stack-mode-dir))) + (get-buffer-create + (stack-mode-buffer-name + (or name + (stack-mode-name)))))) + +(defun stack-mode-name-from-process (proc) + "Get the name of the project from the process." + (substring (process-name proc) (length "stack:"))) + +(defun stack-mode-process-name (name) + "Name for the inferior process." + (format "stack:%s" + name)) + +(defun stack-mode-buffer-name (name) + "Name for the inferior buffer." + (format "*stack:%s*" + name)) + +(defun stack-mode-dir () + "The directory for the project." + (file-name-directory (haskell-cabal-find-file))) + +(defun stack-mode-name () + "The name for the current project based on the current +directory." + (or stack-mode-name + (setq stack-mode-name + (stack-mode-cabal-name)))) + +(defun stack-mode-cabal-name () + "Get the name of the session to use, based on the cabal file." + (let ((cabal-file (haskell-cabal-find-file))) + (if (string-match "\\([^\\/]+\\)\\.cabal$" cabal-file) + (let ((name (match-string 1 cabal-file))) + (when (not (member name (stack-mode-packages))) + (message "This cabal project ā€œ%sā€ isn't in your stack.yaml configuration." name)) + name) + (progn (message "Couldn't figure out cabal file, assuming no project.") + nil)))) + +(defun stack-mode-log (&rest args) + "Log a string to the inferior buffer." + (with-current-buffer (stack-mode-buffer) + (goto-char (point-max)) + (let ((inhibit-read-only t)) + (insert (apply #'format args) + "\n")))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Commands + +(defun stack-mode-reload () + "Compile the code and fetch compile errors." + (with-current-buffer (stack-mode-buffer) + (stack-mode-enqueue + `((tag . "RequestUpdateSession") + (contents . [])) + nil + 'stack-mode-loading-callback))) + +;; (defun stack-mode-load-buffer () +;; "Compile the code and fetch compile errors." +;; (interactive) +;; (with-current-buffer (stack-mode-buffer) +;; (stack-mode-enqueue +;; `((tag . "RequestUpdateSession") +;; (contents . [((tag . "RequestUpdateTargets") +;; (contents . ((tag . "TargetsInclude") +;; (contents . ["src/Stack/Package.hs"]))))])) +;; nil +;; 'stack-mode-loading-callback))) + +(defun stack-mode-get-span-info (module file span) + "Get the span info of the given location." + (with-current-buffer (stack-mode-buffer) + (stack-mode-call + `((tag . "RequestGetSpanInfo") + (contents + . ((spanFilePath . ,file) + (spanFromLine . ,(plist-get span :sl)) + (spanFromColumn . ,(plist-get span :sc)) + (spanToLine . ,(plist-get span :el)) + (spanToColumn . ,(plist-get span :ec)))))))) + +(defun stack-mode-get-exp-types (module file span) + "Get the type info of the given location." + (with-current-buffer (stack-mode-buffer) + (stack-mode-call + `((tag . "RequestGetExpTypes") + (contents + . ((spanFilePath . ,file) + (spanFromLine . ,(plist-get span :sl)) + (spanFromColumn . ,(plist-get span :sc)) + (spanToLine . ,(plist-get span :el)) + (spanToColumn . ,(plist-get span :ec)))))))) + +(defun stack-mode-get-use-sites (module file span) + "Get all uses of an identifier." + ) + +(defun stack-mode-get-completions (module string) + "Get all uses of an identifier." + ) + +(defun stack-mode-loading-callback (_ reply) + "Callback for when loading modules." + (let ((tag (stack-tag reply))) + (cond + ((string= tag "ResponseUpdateSession") + (let* ((contents (stack-contents reply)) + (tag (stack-tag contents))) + (cond + ((string= tag "UpdateStatusProgress") + (stack-mode-progress-callback _ reply) + :continue) + ((string= tag "UpdateStatusDone") + (stack-mode-enqueue-front + `((tag . "RequestGetSourceErrors") + (contents . [])) + nil + 'stack-mode-get-source-errors-callback) + :done) + (t :continue)))) + (t + :continue)))) + +(defun stack-mode-progress-callback (_ reply) + "Callback for status reports. Utilized in multiple places." + (let* ((contents (stack-contents reply)) + (update (stack-contents contents)) + (step (stack-lookup 'progressStep update)) + (total (stack-lookup 'progressNumSteps update)) + (msg (stack-lookup 'progressParsedMsg update))) + (message "[%s/%s] %s" + (propertize (number-to-string step) 'face 'compilation-line-number) + (propertize (number-to-string total) 'face 'compilation-line-number) + msg))) + +(defun stack-mode-get-source-errors-callback (_ reply) + "Handle the reply from getting source errors." + (let ((tag (stack-tag reply))) + (cond + ((string= tag "ResponseGetSourceErrors") + (let ((any-errors nil) + (warnings 0)) + (cl-loop + for item in (mapcar #'identity (stack-contents reply)) + do (let* ((kind (stack-lookup 'errorKind item)) + (span (stack-contents (stack-lookup 'errorSpan item))) + (msg (stack-lookup 'errorMsg item)) + (fp (stack-lookup 'spanFilePath span)) + (sl (stack-lookup 'spanFromLine span)) + (sc (stack-lookup 'spanFromColumn span)) + (el (stack-lookup 'spanToLine span)) + (ec (stack-lookup 'spanToColumn span))) + (cond ((string= kind "KindError") + (setq any-errors t)) + ((string= kind "KindWarning") + (setq warnings (1+ warnings)))) + (when + stack-mode-print-error-messages + (message "%s" + (propertize + (format "%s:(%d,%d)-(%d,%d): \n%s" + fp sl sc el ec msg) + 'face + (cond + ((string= kind "KindWarning") + 'compilation-warning) + ((string= kind "KindError") + 'compilation-error))))))) + (unless any-errors + (if (= 0 warnings) + (message "OK.") + (message (propertize "OK (%d warning%s)." 'face 'compilation-warning) + warnings + (if (= 1 warnings) "" "s"))))) + :done) + (t :done)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Span functions + +(defun stack-mode-points () + "Get the current points; either a selected region or an +identifier's points." + (if (region-active-p) + (cons (region-beginning) (region-end)) + (let ((ident (haskell-ident-pos-at-point))) + (cons (car ident) + (cdr ident))))) + +(defun stack-mode-span-from-points (beg end) + "Get the span representation for the span from BEG to END." + (save-excursion + (list :sl (progn (goto-char beg) + (line-number-at-pos)) + :sc (1+ (current-column)) + :el (progn (goto-char end) + (line-number-at-pos)) + :ec (1+ (current-column))))) + +(defun stack-mode-span () + "Get the span from the haskell points." + (let ((points (or (haskell-spanable-pos-at-point) + (haskell-ident-pos-at-point) + (stack-mode-loose-ident-at-point)))) + (if points + (stack-mode-span-from-points (car points) (cdr points)) + (error "No identifier at point.")))) + +(defun stack-mode-goto-span (span) + "Get buffer points from a span." + (with-current-buffer (stack-mode-buffer) + (find-file (stack-lookup 'spanFilePath span)) + (goto-char (point-min)) + (let ((beg (point))) + (goto-char (point-min)) + (forward-line (1- (stack-lookup 'spanFromLine span))) + (goto-char (line-beginning-position)) + (forward-char (1- (stack-lookup 'spanFromColumn span)))))) + +(defun stack-mode-loose-ident-at-point () + "Get the loose ident at point." + nil) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; JSON helpers + +(defun stack-mode-list->hashtable (xs) + "Convert a list to a hashtable." + (let ((h (make-hash-table))) + (cl-loop for (key . val) + in xs + do (puthash key val h)) + h)) + +(defun stack-lookup (key object) + "Get from a JSON object." + (cdr (assoc key (mapcar #'identity object)))) + +(defun stack-contents (object) + "Get from a JSON object." + (stack-lookup 'contents object)) + +(defun stack-tag (object) + "Get the tag of an object." + (stack-lookup 'tag object)) + +(defun stack-lookup-contents (key object) + "Get from a JSON object." + (stack-contents (stack-lookup key object))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Flycheck integration + +(defun stack-mode-flycheck-start (checker flycheck-callback &optional buffer attempt-count) + "Run a compile on demand, triggered by Flycheck." + (when buffer (set-buffer buffer)) + (let ((max-attempts 2)) + (if (not (stack-mode-live-p)) + (if (> (or attempt-count 0) max-attempts) + (stack-mode-log "Stack backend isn't ready for Flycheck use. Giving up (waited %d seconds)." + max-attempts) + (stack-mode-log "Stack backend isn't ready. Waiting (%d attempts) ..." + (or attempt-count 0)) + (progn (stack-mode-log "Flycheck tried to use the Stack backend, but the Stack backend isn't started yet. Starting it ...") + (stack-mode-try-start) + (run-with-idle-timer 1 nil 'stack-mode-flycheck-start checker flycheck-callback + (current-buffer) + (1+ (or attempt-count 0))))) + (progn (stack-mode-log "Running Flycheck with Stack backend ...") + (write-region (point-min) (point-max) (buffer-file-name)) + (clear-visited-file-modtime) + (let ((source-buffer (current-buffer)) + (label (format "flycheck %s" (buffer-name (current-buffer))))) + (with-current-buffer (stack-mode-buffer) + (stack-mode-enqueue + `((tag . "RequestUpdateSession") + (contents . [])) + (list :flycheck-callback flycheck-callback + :stack-buffer (current-buffer) + :source-buffer source-buffer + :label label) + 'stack-mode-flycheck-callback + label))))))) + +(defun stack-mode-flycheck-callback (state reply) + "Callback for the flycheck loading. Once done, it will report + errors/warnings to CALLBACK." + (let ((tag (stack-tag reply))) + (cond + ((string= tag "ResponseUpdateSession") + (let* ((contents (stack-contents reply)) + (tag (stack-tag contents))) + (cond + ((string= tag "UpdateStatusProgress") + (stack-mode-progress-callback nil reply) + :continue) + ((string= tag "UpdateStatusDone") + (stack-mode-enqueue-front + `((tag . "RequestGetSourceErrors") + (contents . [])) + state + 'stack-mode-flycheck-errors-callback + (plist-get state :label)) + :done) + (t :continue)))) + (t + :continue)))) + +(defun stack-mode-flycheck-errors-callback (state reply) + "Collect error messages and pass them to FLYCHECK-CALLBACK." + (let ((tag (stack-tag reply))) + (cond + ((string= tag "ResponseGetSourceErrors") + (let ((messages (list))) + (cl-loop + for item in (mapcar #'identity (stack-contents reply)) + do (let* ((kind (stack-lookup 'errorKind item)) + (span (stack-contents (stack-lookup 'errorSpan item))) + (msg (stack-lookup 'errorMsg item)) + (filename (stack-lookup 'spanFilePath span)) + (sl (stack-lookup 'spanFromLine span)) + (sc (stack-lookup 'spanFromColumn span)) + (el (stack-lookup 'spanToLine span)) + (ec (stack-lookup 'spanToColumn span))) + (let ((orig (current-buffer)) + (buffer + (with-current-buffer (plist-get state :stack-buffer) + (let ((value (get-file-buffer filename))) + (if (listp value) + (car value) + value))))) + (if (not (null buffer)) + (add-to-list + 'messages + (flycheck-error-new-at + sl sc + (cond + ((string= kind "KindWarning") 'warning) + ((string= kind "KindError") 'error) + (t (message "kind: %s" kind)'error)) + msg + :checker 'stack-ide + :buffer buffer) + t)) + (set-buffer orig)))) + ;; Calling it asynchronously is necessary for flycheck to + ;; work properly. See + ;; + ;; + ;; Also, the `stack-mode-call-in-buffer' utility is also + ;; needed because the reply needs to be called in the same + ;; buffer. + (run-with-idle-timer 0 + nil + 'stack-mode-call-in-buffer + (plist-get state :source-buffer) + (plist-get state :flycheck-callback) + 'finished + messages) + (message "Flycheck done.")) + :done) + (t :done)))) + +(defun stack-mode-call-in-buffer (buffer func &rest args) + "Utility function which calls FUNC in BUFFER with ARGS." + (with-current-buffer buffer + (apply func args))) + +(flycheck-define-generic-checker 'stack-ide + "A syntax and type checker for Haskell using Stack's IDE support." + :start 'stack-mode-flycheck-start + :modes '(haskell-mode)) + +(provide 'stack-mode)