;;; haskell-bot.el --- A Lambdabot interaction mode -*- lexical-binding: t -*-

;; Copyright (C) 2004, 2005, 2006, 2007  Free Software Foundation, Inc.
;; Copyright (C) 2001  Chris Webb
;; Copyright (C) 1998, 1999  Guy Lapalme

;; Keywords: inferior mode, Bot interaction mode, 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 GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.


;;; Commentary:

;; Purpose:
;;
;; To send a Haskell buffer to another buffer running a Bot
;; interpreter.
;;
;; This mode is derived from version 1.1 of Guy Lapalme's
;; haskell-hugs.el, which can be obtained from:
;;
;;   http://www.iro.umontreal.ca/~lapalme/Hugs-interaction.html
;;
;; This in turn was adapted from Chris Van Humbeeck's hugs-mode.el,
;; which can be obtained from:
;;
;;   http://www-i2.informatik.rwth-aachen.de/Forschung/FP/Haskell/hugs-mode.el
;;
;;
;; Installation:
;;
;; To use with Moss and Thorn's haskell-mode.el
;;
;;   http://www.haskell.org/haskell-mode
;;
;; add this to .emacs:
;;
;;   (add-hook 'haskell-mode-hook 'haskell-bot-mode)
;;
;;
;; Customisation:
;;
;; The name of the Bot interpreter is in haskell-bot-program-name.
;;
;; Arguments can be sent to the Bot interpreter when it is started by
;; setting haskell-bot-program-args (empty by default) to a list of
;; string args to pass it.  This value can be set interactively by
;; calling C-c C-s with an argument (i.e. C-u C-c C-s).
;;
;; `haskell-bot-hook' is invoked in the *bot* buffer once Bot is
;; started.
;;
;; All functions/variables start with `turn-{on,off}-haskell-bot' or
;; `haskell-bot-'.

;;; Code:

(require 'comint)

;;;###autoload
(defgroup haskell-bot nil
  "Major mode for interacting with an inferior Bot session."
  :group 'haskell
  :prefix "haskell-bot-")

(define-derived-mode haskell-bot-mode comint-mode "Lambdabot")

;; Bot interface:

(require 'comint)
(require 'shell)

(defvar haskell-bot-process nil
  "The active Bot subprocess corresponding to current buffer.")

(defvar haskell-bot-process-buffer nil
  "*Buffer used for communication with Bot subprocess for current buffer.")

;;;###autoload
(defcustom haskell-bot-program-name "lambdabot"
  "*The name of the Bot interpreter program."
  :type 'string
  :group 'haskell-bot)

;;;###autoload
(defcustom haskell-bot-program-args nil
  "*A list of string args to pass when starting the Bot interpreter."
  :type '(repeat string)
  :group 'haskell-bot)

(defvar haskell-bot-load-end nil
  "Position of the end of the last load command.")

(defvar haskell-bot-error-pos nil
  "Position of the end of the last load command.")

(defvar haskell-bot-send-end nil
  "Position of the end of the last send command.")

(defvar haskell-bot-comint-prompt-regexp
  "^lambdabot> "
  "A regexp that matches the Bot prompt.")

(defun haskell-bot-start-process (arg)
  "Start a Bot process and invoke `haskell-bot-hook' if not nil.
Prompt for a list of args if called with an argument."
  (interactive "P")
  (if arg
      ;; XXX [CDW] Fix to use more natural 'string' version of the
      ;; XXX arguments rather than a sexp.
      (setq haskell-bot-program-args
            (read-minibuffer (format "List of args for %s:"
                                     haskell-bot-program-name)
                             (prin1-to-string haskell-bot-program-args))))

  ;; Start the Bot process in a new comint buffer.
  (message "Starting Lambdabot process `%s'." haskell-bot-program-name)
  (setq haskell-bot-process-buffer
        (apply 'make-comint
               "lambdabot" haskell-bot-program-name nil
               haskell-bot-program-args))
  (setq haskell-bot-process
        (get-buffer-process haskell-bot-process-buffer))

  ;; Select Bot buffer temporarily.
  (set-buffer haskell-bot-process-buffer)
  (haskell-bot-mode)
  (setq comint-prompt-regexp haskell-bot-comint-prompt-regexp)

  ;; History syntax of comint conflicts with Haskell, e.g. !!, so better
  ;; turn it off.
  (setq comint-input-autoexpand nil)
  (setq comint-process-echoes nil)
  (run-hooks 'haskell-bot-hook)

  ;; Clear message area.
  (message ""))

(defun haskell-bot-wait-for-output ()
  "Wait until output arrives and go to the last input."
  (while (progn
	   (goto-char comint-last-input-end)
	   (not (re-search-forward comint-prompt-regexp nil t)))
    (accept-process-output haskell-bot-process)))

(defun haskell-bot-send (&rest string)
  "Send `haskell-bot-process' the arguments (one or more strings).
A newline is sent after the strings and they are inserted into the
current buffer after the last output."
  (haskell-bot-wait-for-output)        ; wait for prompt
  (goto-char (point-max))               ; position for this input
  (apply 'insert string)
  (comint-send-input)
  (setq haskell-bot-send-end (marker-position comint-last-input-end)))

(defun haskell-bot-show-bot-buffer ()
  "Go to the *bot* buffer."
  (interactive)
  (if (or (not haskell-bot-process-buffer)
          (not (buffer-live-p haskell-bot-process-buffer)))
      (haskell-bot-start-process nil))
  (pop-to-buffer  haskell-bot-process-buffer))

(provide 'haskell-bot)

;;; haskell-bot.el ends here
