;;; -*- lexical-binding: t -*- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; ghc-process.el ;;; ;; Author: Kazu Yamamoto ;; Created: Mar 9, 2014 ;;; Code: (require 'ghc-func) (defvar ghc-debug-options nil) ;; (setq ghc-debug-options '("-v9")) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defvar ghc-process-running nil) (defvar ghc-process-file-mapping nil) (defvar-local ghc-process-process-name nil) (defvar-local ghc-process-original-buffer nil) (defvar-local ghc-process-original-file nil) (defvar-local ghc-process-root nil) (defvar ghc-command "ghc-mod") (defvar ghc-report-errors t "Report GHC errors to *GHC Error* buffer") (defvar ghc-error-buffer "*GHC Error*") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun ghc-get-project-root () (ghc-run-ghc-mod '("root"))) (defun ghc-with-process (cmd async-after-callback &optional sync-before-hook) (unless ghc-process-process-name (setq ghc-process-process-name (ghc-get-project-root))) (when (and ghc-process-process-name (not ghc-process-running)) (setq ghc-process-running t) (if sync-before-hook (funcall sync-before-hook)) (let* ((cbuf (current-buffer)) (name ghc-process-process-name) (root (file-name-as-directory ghc-process-process-name)) (buf (get-buffer-create (concat " ghc-mod:" name))) (file (buffer-file-name)) (cpro (get-process name))) ;; setting root in the original buffer, sigh (setq ghc-process-root root) (ghc-with-current-buffer buf (setq ghc-process-original-buffer cbuf) (setq ghc-process-original-file file) (setq ghc-process-root root) (let ((pro (ghc-get-process cpro name buf root)) (map-cmd (format "map-file %s\n" file))) ; (unmap-cmd (format "unmap-file %s\n" file))) (when (buffer-modified-p cbuf) (setq ghc-process-file-mapping t) (setq ghc-process-async-after-callback nil) (erase-buffer) (when ghc-debug (ghc-with-debug-buffer (insert (format "%% %s" map-cmd)) (insert "CONTENTS + EOT\n"))) (process-send-string pro map-cmd) (with-current-buffer cbuf (save-restriction (widen) (process-send-region pro (point-min) (point-max)))) (process-send-string pro "\n\004\n") (condition-case nil (let ((inhibit-quit nil)) (while ghc-process-file-mapping (accept-process-output pro 0.1 nil t))) (quit (setq ghc-process-running nil) (setq ghc-process-file-mapping nil)))) ;; command (setq ghc-process-async-after-callback async-after-callback) (erase-buffer) (when ghc-debug (ghc-with-debug-buffer (insert (format "%% %s" cmd)))) (process-send-string pro cmd) ;;; this needs to be done asyncrounously after the command actually ;;; finished, gah ;; (when do-map-file ;; (when ghc-debug ;; (ghc-with-debug-buffer ;; (insert (format "%% %s" unmap-cmd)))) ;; (process-send-string pro unmap-cmd)) pro))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun ghc-get-process (cpro name buf root) (cond ((not cpro) (ghc-start-process name buf root)) ((not (eq (process-status cpro) 'run)) (delete-process cpro) (ghc-start-process name buf root)) (t cpro))) (defun ghc-start-process (name buf root) (let* ((default-directory root) (process-connection-type nil) ;; using PIPE due to ^D (opts (append ghc-debug-options '("-b" "\n" "-l" "--line-prefix=O: ,E: ") (ghc-make-ghc-options) '("legacy-interactive"))) (pro (apply 'start-process name buf ghc-command opts))) (set-process-filter pro 'ghc-process-filter) (set-process-sentinel pro 'ghc-process-sentinel) (set-process-query-on-exit-flag pro nil) pro)) (defun ghc-process-filter (process string) (let* ((pbuf (process-buffer process)) (tbufname (concat " tmp " (buffer-name pbuf))) tbuf) (if (not (get-buffer pbuf)) (setq ghc-process-running nil) ;; just in case (ghc-with-current-buffer pbuf (when ghc-debug (ghc-with-debug-buffer (insert string))) (with-current-buffer (get-buffer-create tbufname) (setq tbuf (current-buffer)) (goto-char (point-max)) (insert string) (goto-char (point-min)) (let ((cont t) end out) (while (and cont (not (eobp)) ghc-process-running) (cond ((looking-at "^O: ") (setq out t)) ((looking-at "^E: ") (setq out nil)) (t (setq cont nil))) (when cont (forward-line) (unless (bolp) (setq cont nil))) (when cont (delete-region 1 4) (setq end (point)) (if out (with-current-buffer pbuf (goto-char (point-max)) (insert-buffer-substring tbuf 1 end)) (when ghc-report-errors (with-current-buffer (get-buffer-create ghc-error-buffer) (setq buffer-read-only t) (let* ((buffer-read-only nil) (inhibit-read-only t) (cbuf (current-buffer)) cwin) (unless (get-buffer-window cbuf) (display-buffer cbuf)) (setq cwin (get-buffer-window cbuf)) (with-selected-window cwin (goto-char (point-max)) (insert-buffer-substring tbuf 1 end) (set-buffer-modified-p nil)) (redisplay))))) (delete-region 1 end))))) (goto-char (point-max)) (forward-line -1) (cond ((looking-at "^OK$") (delete-region (point) (point-max)) (setq ghc-process-file-mapping nil) (when ghc-process-async-after-callback (goto-char (point-min)) (funcall ghc-process-async-after-callback 'ok) (setq ghc-process-running nil))) ((looking-at "^NG ") (funcall ghc-process-async-after-callback 'ng) (setq ghc-process-running nil))))))) (defun ghc-process-sentinel (_process _event) (setq ghc-process-running nil) (setq ghc-process-file-mapping nil)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defvar ghc-process-rendezvous nil) (defvar ghc-process-num-of-results nil) (defvar ghc-process-results nil) (defun ghc-sync-process (cmd &optional n) (unless ghc-process-running (setq ghc-process-rendezvous nil) (setq ghc-process-results nil) (setq ghc-process-num-of-results (or n 1)) (let ((pro (ghc-with-process cmd 'ghc-sync-process-callback nil))) ;; ghc-process-running is now t. ;; But if the process exits abnormally, it is set to nil. (condition-case nil (let ((inhibit-quit nil)) (while (and (null ghc-process-rendezvous) ghc-process-running) (accept-process-output pro 0.1 nil t))) (quit (setq ghc-process-running nil)))) ghc-process-results)) (defun ghc-sync-process-callback (status) (cond ((eq status 'ok) (let* ((n ghc-process-num-of-results) (ret (if (= n 1) (ghc-read-lisp-this-buffer) (ghc-read-lisp-list-this-buffer n)))) (setq ghc-process-results ret))) (t (setq ghc-process-results nil))) (setq ghc-process-num-of-results nil) (setq ghc-process-rendezvous t)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun ghc-kill-process () (interactive) (when (eq major-mode 'haskell-mode) (let* ((name ghc-process-process-name) (cpro (if name (get-process name)))) (if (not cpro) (message "No ghc-mod process") (delete-process cpro) (message "ghc-mod process was killed"))))) (provide 'ghc-process)