Download
(defvar lui-version "0.9"
"Lui version string.")
(defgroup lui nil
"The Linewise User Interface."
:prefix "lui-"
:group 'applications)
(defcustom lui-input-ring-size 32
"Size of input history ring."
:type 'integer
:group 'lui)
(defcustom lui-mode-hook nil
"The hook run after `lui-mode' has been set up."
:type 'hook
:options '(lui-add-scroll-to-bottom)
:group 'lui)
(defcustom lui-insert-hook nil
"This hook is run narrowed to every line that is inserted into the
buffer."
:type 'hook
:group 'lui)
(defvar lui-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "RET") 'lui-send-input)
(define-key map (kbd "M-p") 'lui-previous-input)
(define-key map (kbd "M-n") 'lui-next-input)
map)
"The keymap for the LUI mode.")
(defvar lui-input-function nil
"This function is called when the user types something to the
program, with the single argument being the string the user typed.")
(make-variable-buffer-local 'lui-input-function)
(defvar lui-input-marker nil
"Marks where the user input starts.")
(make-variable-buffer-local 'lui-input-marker)
(defvar lui-output-marker nil
"Marks where the program output ends.")
(make-variable-buffer-local 'lui-output-marker)
(defvar lui-input-ring nil
"The input ring of lui.")
(make-variable-buffer-local 'lui-input-ring)
(defvar lui-input-ring-index nil
"The index into the input ring.")
(make-variable-buffer-local 'lui-input-ring-index)
(defun lui-mode (input-func)
"A mode for Linewise User Interaction. This mode will set up a
shell-style user interface, especially taylored for Emacs programs
that need to interact with the user.
\\{lui-mode-map}"
(interactive "aInput function: ")
(kill-all-local-variables)
(setq major-mode 'lui-mode
mode-name "LUI")
(use-local-map lui-mode-map)
(setq lui-input-function input-func
lui-input-marker (make-marker)
lui-output-marker (make-marker)
lui-input-ring (make-ring lui-input-ring-size)
lui-input-ring-index nil)
(set-marker lui-input-marker (point-max))
(set-marker lui-output-marker (point-max))
(run-hooks 'lui-mode-hook))
(defun lui-prompt (str)
"Set STR as the prompt in the current LUI buffer."
(save-restriction
(widen)
(delete-region lui-output-marker lui-input-marker)
(goto-char lui-output-marker)
(insert str)
(set-marker lui-input-marker (point))
(let ((ov (make-overlay lui-output-marker lui-input-marker)))
(overlay-put ov 'inhibit-line-move-field-capture t)
(overlay-put ov 'field 'lui-prompt)
(overlay-put ov 'evaporate 'lui-prompt))))
(defun lui-insert (str)
"Insert STR as output in the LUI buffer."
(save-excursion
(save-restriction
(widen)
(goto-char lui-output-marker)
(let ((from (point))
(to nil))
(insert str "\n")
(setq to (point))
(set-marker lui-output-marker (point))
(mapc (lambda (ov)
(move-overlay ov lui-output-marker lui-input-marker))
(overlays-in lui-output-marker lui-input-marker))
(narrow-to-region from to)
(run-hooks 'lui-insert-hook)
(when (< lui-input-marker lui-output-marker)
(set-marker lui-input-marker lui-output-marker)))))
(goto-char lui-input-marker))
(defun lui-send-input ()
"Send the current input to the LUI application."
(interactive)
(if (< (point) lui-input-marker)
(self-insert-command 1)
(let ((input (buffer-substring lui-input-marker (point-max))))
(delete-region lui-input-marker (point-max))
(ring-insert lui-input-ring input)
(setq lui-input-ring-index nil)
(funcall lui-input-function input))))
(defun lui-previous-input ()
"Replace the current input with the previous one from the history."
(interactive)
(when (> (ring-length lui-input-ring) 0)
(if (and lui-input-ring-index
(= (1- (ring-length lui-input-ring))
lui-input-ring-index))
(progn
(lui-replace-input "")
(setq lui-input-ring-index nil))
(when (and (null lui-input-ring-index)
(> (point-max) lui-input-marker))
(ring-insert lui-input-ring
(buffer-substring lui-input-marker (point-max)))
(setq lui-input-ring-index 0))
(setq lui-input-ring-index
(if lui-input-ring-index
(ring-plus1 lui-input-ring-index (ring-length lui-input-ring))
0))
(lui-replace-input (ring-ref lui-input-ring lui-input-ring-index)))))
(defun lui-next-input ()
"Replace the current input with the next one from the history."
(interactive)
(when (> (ring-length lui-input-ring) 0)
(if (and lui-input-ring-index
(= 0 lui-input-ring-index))
(progn
(lui-replace-input "")
(setq lui-input-ring-index nil))
(setq lui-input-ring-index (ring-minus1 (or lui-input-ring-index 0)
(ring-length lui-input-ring)))
(lui-replace-input (ring-ref lui-input-ring lui-input-ring-index)))))
(defun lui-replace-input (str)
"Replace the current input with STR."
(goto-char lui-input-marker)
(delete-region lui-input-marker (point-max))
(insert str))
(defun lui-add-scroll-to-bottom ()
"Add this to `lui-mode-hook' to recenter output at the bottom of the
window.
This works whenever scrolling happens, so it's added to
`window-scroll-functions'."
(add-hook 'window-scroll-functions 'lui-scroll-to-bottom nil t))
(defun lui-scroll-to-bottom (window display-start)
"Recenter WINDOW so that point is on the last line.
This is added to `window-scroll-functions' by
`lui-add-scroll-to-bottom'.
The code is shamelessly taken (but adapted) from ERC."
(when (and window
(window-live-p window)
(>= (point) lui-input-marker)
(let ((resize-mini-windows nil))
(save-selected-window
(select-window window)
(save-restriction
(widen)
(when (>= (point) lui-input-marker)
(save-excursion
(recenter -1)
(sit-for 0))))))))
(provide 'lui)