|
;;; -*-Emacs-Lisp-*-
|
|
;;;%Header
|
|
;;; Bridge process filter, V1.0
|
|
;;; Copyright (C) 1991 Chris McConnell, ccm@cs.cmu.edu
|
|
;;;
|
|
;;; Send mail to ilisp@cons.org if you have problems.
|
|
;;;
|
|
;;; Send mail to majordomo@cons.org if you want to be on the
|
|
;;; ilisp mailing list.
|
|
|
|
;;; This file is part of GNU Emacs.
|
|
|
|
;;; GNU Emacs is distributed in the hope that it will be useful,
|
|
;;; but WITHOUT ANY WARRANTY. No author or distributor
|
|
;;; accepts responsibility to anyone for the consequences of using it
|
|
;;; or for whether it serves any particular purpose or works at all,
|
|
;;; unless he says so in writing. Refer to the GNU Emacs General Public
|
|
;;; License for full details.
|
|
|
|
;;; Everyone is granted permission to copy, modify and redistribute
|
|
;;; GNU Emacs, but only under the conditions described in the
|
|
;;; GNU Emacs General Public License. A copy of this license is
|
|
;;; supposed to have been given to you along with GNU Emacs so you
|
|
;;; can know your rights and responsibilities. It should be in a
|
|
;;; file named COPYING. Among other things, the copyright notice
|
|
;;; and this notice must be preserved on all copies.
|
|
|
|
;;; Send any bugs or comments. Thanks to Todd Kaufmann for rewriting
|
|
;;; the process filter for continuous handlers.
|
|
|
|
;;; USAGE: M-x install-bridge will add a process output filter to the
|
|
;;; current buffer. Any output that the process does between
|
|
;;; bridge-start-regexp and bridge-end-regexp will be bundled up and
|
|
;;; passed to the first handler on bridge-handlers that matches the
|
|
;;; output using string-match. If bridge-prompt-regexp shows up
|
|
;;; before bridge-end-regexp, the bridge will be cancelled. If no
|
|
;;; handler matches the output, the first symbol in the output is
|
|
;;; assumed to be a buffer name and the rest of the output will be
|
|
;;; sent to that buffer's process. This can be used to communicate
|
|
;;; between processes or to set up two way interactions between Emacs
|
|
;;; and an inferior process.
|
|
|
|
;;; You can write handlers that process the output in special ways.
|
|
;;; See bridge-send-handler for the default handler. The command
|
|
;;; hand-bridge is useful for testing. Keep in mind that all
|
|
;;; variables are buffer local.
|
|
|
|
;;; YOUR .EMACS FILE:
|
|
;;;
|
|
;;; ;;; Set up load path to include bridge
|
|
;;; (setq load-path (cons "/bridge-directory/" load-path))
|
|
;;; (autoload 'install-bridge "bridge" "Install a process bridge." t)
|
|
;;; (setq bridge-hook
|
|
;;; '(lambda ()
|
|
;;; ;; Example options
|
|
;;; (setq bridge-source-insert nil) ;Don't insert in source buffer
|
|
;;; (setq bridge-destination-insert nil) ;Don't insert in dest buffer
|
|
;;; ;; Handle copy-it messages yourself
|
|
;;; (setq bridge-handlers
|
|
;;; '(("copy-it" . my-copy-handler)))))
|
|
|
|
;;; EXAMPLE:
|
|
;;; # This pipes stdin to the named buffer in a Unix shell
|
|
;;; alias devgnu '(echo -n "\!* "; cat -; echo -n "")'
|
|
;;;
|
|
;;; ls | devgnu *scratch*
|
|
|
|
(eval-when-compile
|
|
(require 'cl))
|
|
|
|
;;;%Parameters
|
|
(defvar bridge-hook nil
|
|
"Hook called when a bridge is installed by install-hook.")
|
|
|
|
(defvar bridge-start-regexp ""
|
|
"*Regular expression to match the start of a process bridge in
|
|
process output. It should be followed by a buffer name, the data to
|
|
be sent and a bridge-end-regexp.")
|
|
|
|
(defvar bridge-end-regexp ""
|
|
"*Regular expression to match the end of a process bridge in process
|
|
output.")
|
|
|
|
(defvar bridge-prompt-regexp nil
|
|
"*Regular expression for detecting a prompt. If there is a
|
|
comint-prompt-regexp, it will be initialized to that. A prompt before
|
|
a bridge-end-regexp will stop the process bridge.")
|
|
|
|
(defvar bridge-handlers nil
|
|
"Alist of (regexp . handler) for handling process output delimited
|
|
by bridge-start-regexp and bridge-end-regexp. The first entry on the
|
|
list whose regexp matches the output will be called on the process and
|
|
the delimited output.")
|
|
|
|
(defvar bridge-source-insert t
|
|
"*T to insert bridge input in the source buffer minus delimiters.")
|
|
|
|
(defvar bridge-destination-insert t
|
|
"*T for bridge-send-handler to insert bridge input into the
|
|
destination buffer minus delimiters.")
|
|
|
|
(defvar bridge-chunk-size 512
|
|
"*Long inputs send to comint processes are broken up into chunks of
|
|
this size. If your process is choking on big inputs, try lowering the
|
|
value.")
|
|
|
|
;;;%Internal variables
|
|
(defvar bridge-old-filter nil
|
|
"Old filter for a bridged process buffer.")
|
|
|
|
(defvar bridge-string nil
|
|
"The current output in the process bridge.")
|
|
|
|
(defvar bridge-in-progress nil
|
|
"The current handler function, if any, that bridge passes strings on to,
|
|
or nil if none.")
|
|
|
|
(defvar bridge-leftovers nil
|
|
"Because of chunking you might get an incomplete bridge signal - start but the end is in the next packet. Save the overhanging text here.")
|
|
|
|
(defvar bridge-send-to-buffer nil
|
|
"The buffer that the default bridge-handler (bridge-send-handler) is
|
|
currently sending to, or nil if it hasn't started yet. Your handler
|
|
function can use this variable also.")
|
|
|
|
(defvar bridge-last-failure ()
|
|
"Last thing that broke the bridge handler. First item is function call
|
|
(eval'able); last item is error condition which resulted. This is provided
|
|
to help handler-writers in their debugging.")
|
|
|
|
(defvar bridge-insert-function nil
|
|
"If non-nil use this instead of `bridge-insert'")
|
|
|
|
;;;%Utilities
|
|
(defun bridge-insert (output &optional _dummy)
|
|
"Insert process OUTPUT into the current buffer."
|
|
(if bridge-insert-function
|
|
(funcall bridge-insert-function output)
|
|
(if output
|
|
(let* ((buffer (current-buffer))
|
|
(process (get-buffer-process buffer))
|
|
(mark (process-mark process))
|
|
(window (selected-window))
|
|
(at-end nil))
|
|
(if (eq (window-buffer window) buffer)
|
|
(setq at-end (= (point) mark))
|
|
(setq window (get-buffer-window buffer)))
|
|
(save-excursion
|
|
(goto-char mark)
|
|
(insert output)
|
|
(set-marker mark (point)))
|
|
(if window
|
|
(progn
|
|
(if at-end (goto-char mark))
|
|
(if (not (pos-visible-in-window-p (point) window))
|
|
(let ((original (selected-window)))
|
|
(save-excursion
|
|
(select-window window)
|
|
(recenter '(center))
|
|
(select-window original))))))))))
|
|
|
|
;;;
|
|
;(defun bridge-send-string (process string)
|
|
; "Send PROCESS the contents of STRING as input.
|
|
;This is equivalent to process-send-string, except that long input strings
|
|
;are broken up into chunks of size comint-input-chunk-size. Processes
|
|
;are given a chance to output between chunks. This can help prevent processes
|
|
;from hanging when you send them long inputs on some OS's."
|
|
; (let* ((len (length string))
|
|
; (i (min len bridge-chunk-size)))
|
|
; (process-send-string process (substring string 0 i))
|
|
; (while (< i len)
|
|
; (let ((next-i (+ i bridge-chunk-size)))
|
|
; (accept-process-output)
|
|
; (process-send-string process (substring string i (min len next-i)))
|
|
; (setq i next-i)))))
|
|
|
|
;;;
|
|
(defun bridge-call-handler (handler proc string)
|
|
"Funcall HANDLER on PROC, STRING carefully. Error is caught if happens,
|
|
and user is signaled. State is put in bridge-last-failure. Returns t if
|
|
handler executed without error."
|
|
(let ((inhibit-quit nil)
|
|
(failed nil))
|
|
(condition-case err
|
|
(funcall handler proc string)
|
|
(error
|
|
(ding)
|
|
(setq failed t)
|
|
(message "bridge-handler \"%s\" failed %s (see bridge-last-failure)"
|
|
handler err)
|
|
(setq bridge-last-failure
|
|
`((funcall ',handler ',proc ,string)
|
|
"Caused: "
|
|
,err))))
|
|
(not failed)))
|
|
|
|
;;;%Handlers
|
|
(defun bridge-send-handler (process input)
|
|
"Send PROCESS INPUT to the buffer name found at the start of the
|
|
input. The input after the buffer name is sent to the buffer's
|
|
process if it has one. If bridge-destination-insert is T, the input
|
|
will be inserted into the buffer. If it does not have a process, it
|
|
will be inserted at the end of the buffer."
|
|
(if (null input)
|
|
(setq bridge-send-to-buffer nil) ; end of bridge
|
|
(let (buffer-and-start buffer-name dest to)
|
|
;; if this is first time, get the buffer out of the first line
|
|
(cond ((not bridge-send-to-buffer)
|
|
(setq buffer-and-start (read-from-string input)
|
|
buffer-name (format "%s" (car (read-from-string input)))
|
|
dest (get-buffer buffer-name)
|
|
to (get-buffer-process dest)
|
|
input (substring input (cdr buffer-and-start)))
|
|
(setq bridge-send-to-buffer dest))
|
|
(t
|
|
(setq buffer-name bridge-send-to-buffer
|
|
dest (get-buffer buffer-name)
|
|
to (get-buffer-process dest)
|
|
)))
|
|
(if dest
|
|
(let ((buffer (current-buffer)))
|
|
(if bridge-destination-insert
|
|
(unwind-protect
|
|
(progn
|
|
(set-buffer dest)
|
|
(if to
|
|
(bridge-insert process input)
|
|
(goto-char (point-max))
|
|
(insert input)))
|
|
(set-buffer buffer)))
|
|
(if to
|
|
;; (bridge-send-string to input)
|
|
(process-send-string to input)
|
|
))
|
|
(error "%s is not a buffer" buffer-name)))))
|
|
|
|
;;;%Filter
|
|
(defun bridge-filter (process output)
|
|
"Given PROCESS and some OUTPUT, check for the presence of
|
|
bridge-start-regexp. Everything prior to this will be passed to the
|
|
normal filter function or inserted in the buffer if it is nil. The
|
|
output up to bridge-end-regexp will be sent to the first handler on
|
|
bridge-handlers that matches the string. If no handlers match, the
|
|
input will be sent to bridge-send-handler. If bridge-prompt-regexp is
|
|
encountered before the bridge-end-regexp, the bridge will be cancelled."
|
|
(let ((inhibit-quit t)
|
|
(match-data (match-data))
|
|
(buffer (current-buffer))
|
|
(process-buffer (process-buffer process))
|
|
(case-fold-search t)
|
|
(start 0) (end 0)
|
|
function
|
|
b-start b-start-end b-end)
|
|
(set-buffer process-buffer) ;; access locals
|
|
|
|
;; Handle bridge messages that straddle a packet by prepending
|
|
;; them to this packet.
|
|
|
|
(when bridge-leftovers
|
|
(setq output (concat bridge-leftovers output))
|
|
(setq bridge-leftovers nil))
|
|
|
|
(setq function bridge-in-progress)
|
|
|
|
;; How it works:
|
|
;;
|
|
;; start, end delimit the part of string we are interested in;
|
|
;; initially both 0; after an iteration we move them to next string.
|
|
|
|
;; b-start, b-end delimit part of string to bridge (possibly whole string);
|
|
;; this will be string between corresponding regexps.
|
|
|
|
;; There are two main cases when we come into loop:
|
|
|
|
;; bridge in progress
|
|
;;0 setq b-start = start
|
|
;;1 setq b-end (or end-pattern end)
|
|
;;4 process string
|
|
;;5 remove handler if end found
|
|
|
|
;; no bridge in progress
|
|
;;0 setq b-start if see start-pattern
|
|
;;1 setq b-end if bstart to (or end-pattern end)
|
|
;;2 send (substring start b-start) to normal place
|
|
;;3 find handler (in b-start, b-end) if not set
|
|
;;4 process string
|
|
;;5 remove handler if end found
|
|
|
|
;; equivalent sections have the same numbers here;
|
|
;; we fold them together in this code.
|
|
|
|
(block bridge-filter
|
|
(unwind-protect
|
|
(while (< end (length output))
|
|
|
|
;;0 setq b-start if find
|
|
(setq b-start
|
|
(cond (bridge-in-progress
|
|
(setq b-start-end start)
|
|
start)
|
|
((string-match bridge-start-regexp output start)
|
|
(setq b-start-end (match-end 0))
|
|
(match-beginning 0))
|
|
(t nil)))
|
|
;;1 setq b-end
|
|
(setq b-end
|
|
(if b-start
|
|
(let ((end-seen (string-match bridge-end-regexp
|
|
output b-start-end)))
|
|
(if end-seen (setq end (match-end 0)))
|
|
|
|
end-seen)))
|
|
|
|
;; Detect and save partial bridge messages
|
|
(when (and b-start b-start-end (not b-end))
|
|
(setq bridge-leftovers (substring output b-start))
|
|
)
|
|
|
|
(if (and b-start (not b-end))
|
|
(setq end b-start)
|
|
(if (not b-end)
|
|
(setq end (length output))))
|
|
|
|
;;1.5 - if see prompt before end, remove current
|
|
(if (and b-start b-end)
|
|
(let ((prompt (string-match bridge-prompt-regexp
|
|
output b-start-end)))
|
|
(if (and prompt (<= (match-end 0) b-end))
|
|
(setq b-start nil ; b-start-end start
|
|
b-end start
|
|
end (match-end 0)
|
|
bridge-in-progress nil
|
|
))))
|
|
|
|
;;2 send (substring start b-start) to old filter, if any
|
|
(when (not (equal start (or b-start end))) ; don't bother on empty string
|
|
(let ((pass-on (substring output start (or b-start end))))
|
|
(if bridge-old-filter
|
|
(let ((old bridge-old-filter))
|
|
(store-match-data match-data)
|
|
(funcall old process pass-on)
|
|
;; if filter changed, re-install ourselves
|
|
(let ((new (process-filter process)))
|
|
(if (not (eq new 'bridge-filter))
|
|
(progn (setq bridge-old-filter new)
|
|
(set-process-filter process 'bridge-filter)))))
|
|
(set-buffer process-buffer)
|
|
(bridge-insert pass-on))))
|
|
|
|
(if (and b-start-end (not b-end))
|
|
(return-from bridge-filter t) ; when last bit has prematurely ending message, exit early.
|
|
(progn
|
|
;;3 find handler (in b-start, b-end) if none current
|
|
(if (and b-start (not bridge-in-progress))
|
|
(let ((handlers bridge-handlers))
|
|
(while (and handlers (not function))
|
|
(let* ((handler (car handlers))
|
|
(m (string-match (car handler) output b-start-end)))
|
|
(if (and m (< m b-end))
|
|
(setq function (cdr handler))
|
|
(setq handlers (cdr handlers)))))
|
|
;; Set default handler if none
|
|
(if (null function)
|
|
(setq function 'bridge-send-handler))
|
|
(setq bridge-in-progress function)))
|
|
;;4 process strin
|
|
(if function
|
|
(let ((ok t))
|
|
(if (/= b-start-end b-end)
|
|
(let ((send (substring output b-start-end b-end)))
|
|
;; also, insert the stuff in buffer between
|
|
;; iff bridge-source-insert.
|
|
(if bridge-source-insert (bridge-insert send))
|
|
;; call handler on string
|
|
(setq ok (bridge-call-handler function process send))))
|
|
;;5 remove handler if end found
|
|
;; if function removed then tell it that's all
|
|
(if (or (not ok) (/= b-end end)) ;; saw end before end-of-string
|
|
(progn
|
|
(bridge-call-handler function process nil)
|
|
;; have to remove function too for next time around
|
|
(setq function nil
|
|
bridge-in-progress nil)
|
|
))
|
|
))
|
|
|
|
;; continue looping, in case there's more string
|
|
(setq start end))
|
|
))
|
|
;; protected forms: restore buffer, match-data
|
|
(set-buffer buffer)
|
|
(store-match-data match-data)
|
|
))))
|
|
|
|
|
|
;;;%Interface
|
|
(defun install-bridge ()
|
|
"Set up a process bridge in the current buffer."
|
|
(interactive)
|
|
(if (not (get-buffer-process (current-buffer)))
|
|
(error "%s does not have a process" (buffer-name (current-buffer)))
|
|
(make-local-variable 'bridge-start-regexp)
|
|
(make-local-variable 'bridge-end-regexp)
|
|
(make-local-variable 'bridge-prompt-regexp)
|
|
(make-local-variable 'bridge-handlers)
|
|
(make-local-variable 'bridge-source-insert)
|
|
(make-local-variable 'bridge-destination-insert)
|
|
(make-local-variable 'bridge-chunk-size)
|
|
(make-local-variable 'bridge-old-filter)
|
|
(make-local-variable 'bridge-string)
|
|
(make-local-variable 'bridge-in-progress)
|
|
(make-local-variable 'bridge-send-to-buffer)
|
|
(make-local-variable 'bridge-leftovers)
|
|
(setq bridge-string nil bridge-in-progress nil
|
|
bridge-send-to-buffer nil)
|
|
(if (boundp 'comint-prompt-regexp)
|
|
(setq bridge-prompt-regexp comint-prompt-regexp))
|
|
(let ((process (get-buffer-process (current-buffer))))
|
|
(if process
|
|
(if (not (eq (process-filter process) 'bridge-filter))
|
|
(progn
|
|
(setq bridge-old-filter (process-filter process))
|
|
(set-process-filter process 'bridge-filter)))
|
|
(error "%s does not have a process"
|
|
(buffer-name (current-buffer)))))
|
|
(run-hooks 'bridge-hook)
|
|
(message "Process bridge is installed")))
|
|
|
|
;;;
|
|
(defun reset-bridge ()
|
|
"Must be called from the process's buffer. Removes any active bridge."
|
|
(interactive)
|
|
;; for when things get wedged
|
|
(if bridge-in-progress
|
|
(unwind-protect
|
|
(funcall bridge-in-progress (get-buffer-process
|
|
(current-buffer))
|
|
nil)
|
|
(setq bridge-in-progress nil))
|
|
(message "No bridge in progress.")))
|
|
|
|
;;;
|
|
(defun remove-bridge ()
|
|
"Remove bridge from the current buffer."
|
|
(interactive)
|
|
(let ((process (get-buffer-process (current-buffer))))
|
|
(if (or (not process) (not (eq (process-filter process) 'bridge-filter)))
|
|
(error "%s has no bridge" (buffer-name (current-buffer)))
|
|
;; remove any bridge-in-progress
|
|
(reset-bridge)
|
|
(set-process-filter process bridge-old-filter)
|
|
(funcall bridge-old-filter process bridge-string)
|
|
(message "Process bridge is removed."))))
|
|
|
|
;;;% Utility for testing
|
|
(defun hand-bridge (start end)
|
|
"With point at bridge-start, sends bridge-start + string +
|
|
bridge-end to bridge-filter. With prefix, use current region to send."
|
|
(interactive "r")
|
|
(let ((p0 (if current-prefix-arg (min start end)
|
|
(if (looking-at bridge-start-regexp) (point)
|
|
(error "Not looking at bridge-start-regexp"))))
|
|
(p1 (if current-prefix-arg (max start end)
|
|
(if (re-search-forward bridge-end-regexp nil t)
|
|
(point) (error "Didn't see bridge-end-regexp")))))
|
|
|
|
(bridge-filter (get-buffer-process (current-buffer))
|
|
(buffer-substring-no-properties p0 p1))
|
|
))
|
|
|
|
(provide 'bridge)
|