123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535 |
- ;;; gds-scheme.el -- GDS function for Scheme mode buffers
- ;;;; Copyright (C) 2005 Neil Jerram
- ;;;;
- ;;;; This library is free software; you can redistribute it and/or
- ;;;; modify it under the terms of the GNU Lesser General Public
- ;;;; License as published by the Free Software Foundation; either
- ;;;; version 2.1 of the License, or (at your option) any later
- ;;;; version.
- ;;;;
- ;;;; This library 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
- ;;;; Lesser General Public License for more details.
- ;;;;
- ;;;; You should have received a copy of the GNU Lesser General Public
- ;;;; License along with this library; if not, write to the Free
- ;;;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- ;;;; 02111-1307 USA
- (require 'comint)
- (require 'scheme)
- (require 'derived)
- (require 'pp)
- ;;;; Maintaining an association between a Guile client process and a
- ;;;; set of Scheme mode buffers.
- (defcustom gds-auto-create-utility-client t
- "Whether to automatically create a utility Guile client, and
- associate the current buffer with it, if there are no existing Guile
- clients available to GDS when the user does something that requires a
- running Guile client."
- :type 'boolean
- :group 'gds)
- (defcustom gds-auto-associate-single-client t
- "Whether to automatically associate the current buffer with an
- existing Guile client, if there is only only client known to GDS when
- the user does something that requires a running Guile client, and the
- current buffer is not already associated with a Guile client."
- :type 'boolean
- :group 'gds)
- (defcustom gds-auto-associate-last-client t
- "Whether to automatically associate the current buffer with the
- Guile client that most recently caused that buffer to be displayed,
- when the user does something that requires a running Guile client and
- the current buffer is not already associated with a Guile client."
- :type 'boolean
- :group 'gds)
- (defvar gds-last-touched-by nil
- "For each Scheme mode buffer, this records the GDS client that most
- recently `touched' that buffer in the sense of using it to display
- source code, for example for the source code relevant to a debugger
- stack frame.")
- (make-variable-buffer-local 'gds-last-touched-by)
- (defun gds-auto-associate-buffer ()
- "Automatically associate the current buffer with a Guile client, if
- possible."
- (let* ((num-clients (length gds-client-info))
- (client
- (or
- ;; If there are no clients yet, and
- ;; `gds-auto-create-utility-client' allows us to create one
- ;; automatically, do that.
- (and (= num-clients 0)
- gds-auto-create-utility-client
- (gds-start-utility-guile))
- ;; Otherwise, if there is a single existing client, and
- ;; `gds-auto-associate-single-client' allows us to use it
- ;; for automatic association, do that.
- (and (= num-clients 1)
- gds-auto-associate-single-client
- (caar gds-client-info))
- ;; Otherwise, if the current buffer was displayed because
- ;; of a Guile client trapping somewhere in its code, and
- ;; `gds-auto-associate-last-client' allows us to associate
- ;; with that client, do so.
- (and gds-auto-associate-last-client
- gds-last-touched-by))))
- (if client
- (gds-associate-buffer client))))
- (defun gds-associate-buffer (client)
- "Associate the current buffer with the Guile process CLIENT.
- This means that operations in this buffer that require a running Guile
- process - such as evaluation, help, completion and setting traps -
- will be sent to the Guile process whose name or connection number is
- CLIENT."
- (interactive (list (gds-choose-client)))
- ;; If this buffer is already associated, dissociate from its
- ;; existing client first.
- (if gds-client (gds-dissociate-buffer))
- ;; Store the client number in the buffer-local variable gds-client.
- (setq gds-client client)
- ;; Add this buffer to the list of buffers associated with the
- ;; client.
- (gds-client-put client 'associated-buffers
- (cons (current-buffer)
- (gds-client-get client 'associated-buffers))))
- (defun gds-dissociate-buffer ()
- "Dissociate the current buffer from any specific Guile process."
- (interactive)
- (if gds-client
- (progn
- ;; Remove this buffer from the list of buffers associated with
- ;; the current client.
- (gds-client-put gds-client 'associated-buffers
- (delq (current-buffer)
- (gds-client-get gds-client 'associated-buffers)))
- ;; Reset the buffer-local variable gds-client.
- (setq gds-client nil)
- ;; Clear any process status indication from the modeline.
- (setq mode-line-process nil)
- (force-mode-line-update))))
- (defun gds-show-client-status (client status-string)
- "Show a client's status in the modeline of all its associated
- buffers."
- (let ((buffers (gds-client-get client 'associated-buffers)))
- (while buffers
- (if (buffer-live-p (car buffers))
- (with-current-buffer (car buffers)
- (setq mode-line-process status-string)
- (force-mode-line-update)))
- (setq buffers (cdr buffers)))))
- (defcustom gds-running-text ":running"
- "*Mode line text used to show that a Guile process is \"running\".
- \"Running\" means that the process cannot currently accept any input
- from the GDS frontend in Emacs, because all of its threads are busy
- running code that GDS cannot easily interrupt."
- :type 'string
- :group 'gds)
- (defcustom gds-ready-text ":ready"
- "*Mode line text used to show that a Guile process is \"ready\".
- \"Ready\" means that the process is ready to interact with the GDS
- frontend in Emacs, because at least one of its threads is waiting for
- GDS input."
- :type 'string
- :group 'gds)
- (defcustom gds-debug-text ":debug"
- "*Mode line text used to show that a Guile process is \"debugging\".
- \"Debugging\" means that the process is using the GDS frontend in
- Emacs to display an error or trap so that the user can debug it."
- :type 'string
- :group 'gds)
- (defun gds-choose-client ()
- "Ask the user to choose a GDS client process from a list."
- (let ((table '())
- (default nil))
- ;; Prepare a table containing all current clients.
- (mapcar (lambda (client-info)
- (setq table (cons (cons (cadr (memq 'name client-info))
- (car client-info))
- table)))
- gds-client-info)
- ;; Add an entry to allow the user to ask for a new process.
- (setq table (cons (cons "Start a new Guile process" nil) table))
- ;; Work out a good default. If the buffer has a good value in
- ;; gds-last-touched-by, we use that; otherwise default to starting
- ;; a new process.
- (setq default (or (and gds-last-touched-by
- (gds-client-get gds-last-touched-by 'name))
- (caar table)))
- ;; Read using this table.
- (let* ((name (completing-read "Choose a Guile process: "
- table
- nil
- t ; REQUIRE-MATCH
- nil ; INITIAL-INPUT
- nil ; HIST
- default))
- ;; Convert name to a client number.
- (client (cdr (assoc name table))))
- ;; If the user asked to start a new Guile process, do that now.
- (or client (setq client (gds-start-utility-guile)))
- ;; Return the chosen client ID.
- client)))
- (defvar gds-last-utility-number 0
- "Number of the last started Guile utility process.")
- (defun gds-start-utility-guile ()
- "Start a new utility Guile process."
- (setq gds-last-utility-number (+ gds-last-utility-number 1))
- (let* ((procname (format "gds-util[%d]" gds-last-utility-number))
- (code (format "(begin
- %s
- (use-modules (ice-9 gds-client))
- (run-utility))"
- (if gds-scheme-directory
- (concat "(set! %load-path (cons "
- (format "%S" gds-scheme-directory)
- " %load-path))")
- "")))
- (proc (start-process procname
- (get-buffer-create procname)
- gds-guile-program
- "-q"
- "--debug"
- "-c"
- code))
- (client nil))
- ;; Note that this process can be killed automatically on Emacs
- ;; exit.
- (process-kill-without-query proc)
- ;; Set up a process filter to catch the new client's number.
- (set-process-filter proc
- (lambda (proc string)
- (setq client (string-to-number string))
- (if (process-buffer proc)
- (with-current-buffer (process-buffer proc)
- (insert string)))))
- ;; Accept output from the new process until we have its number.
- (while (not client)
- (accept-process-output proc))
- ;; Return the new process's client number.
- client))
- ;;;; Evaluating code.
- ;; The following commands send code for evaluation through the GDS TCP
- ;; connection, receive the result and any output generated through the
- ;; same connection, and display the result and output to the user.
- ;;
- ;; For each buffer where evaluations can be requested, GDS uses the
- ;; buffer-local variable `gds-client' to track which GDS client
- ;; program should receive and handle that buffer's evaluations.
- (defun gds-module-name (start end)
- "Determine and return the name of the module that governs the
- specified region. The module name is returned as a list of symbols."
- (interactive "r") ; why not?
- (save-excursion
- (goto-char start)
- (let (module-name)
- (while (and (not module-name)
- (beginning-of-defun-raw 1))
- (if (looking-at "(define-module ")
- (setq module-name
- (progn
- (goto-char (match-end 0))
- (read (current-buffer))))))
- module-name)))
- (defcustom gds-emacs-buffer-port-name-prefix "Emacs buffer: "
- "Prefix used when telling Guile the name of the port from which a
- chunk of Scheme code (to be evaluated) comes. GDS uses this prefix,
- followed by the buffer name, in two cases: when the buffer concerned
- is not associated with a file, or if the buffer has been modified
- since last saving to its file. In the case where the buffer is
- identical to a saved file, GDS uses the file name as the port name."
- :type '(string)
- :group 'gds)
- (defun gds-port-name (start end)
- "Return port name for the specified region of the current buffer.
- The name will be used by Guile as the port name when evaluating that
- region's code."
- (or (and (not (buffer-modified-p))
- buffer-file-name)
- (concat gds-emacs-buffer-port-name-prefix (buffer-name))))
- (defun gds-line-and-column (pos)
- "Return 0-based line and column number at POS."
- (let (line column)
- (save-excursion
- (goto-char pos)
- (setq column (current-column))
- (beginning-of-line)
- (setq line (count-lines (point-min) (point))))
- (cons line column)))
- (defun gds-eval-region (start end &optional debugp)
- "Evaluate the current region. If invoked with `C-u' prefix (or, in
- a program, with optional DEBUGP arg non-nil), pause and pop up the
- stack at the start of the evaluation, so that the user can single-step
- through the code."
- (interactive "r\nP")
- (or gds-client
- (gds-auto-associate-buffer)
- (call-interactively 'gds-associate-buffer))
- (let ((module (gds-module-name start end))
- (port-name (gds-port-name start end))
- (lc (gds-line-and-column start)))
- (let ((code (buffer-substring-no-properties start end)))
- (gds-send (format "eval (region . %S) %s %S %d %d %S %s"
- (gds-abbreviated code)
- (if module (prin1-to-string module) "#f")
- port-name (car lc) (cdr lc)
- code
- (if debugp '(debug) '(none)))
- gds-client))))
- (defun gds-eval-expression (expr &optional correlator debugp)
- "Evaluate the supplied EXPR (a string). If invoked with `C-u'
- prefix (or, in a program, with optional DEBUGP arg non-nil), pause and
- pop up the stack at the start of the evaluation, so that the user can
- single-step through the code."
- (interactive "sEvaluate expression: \ni\nP")
- (or gds-client
- (gds-auto-associate-buffer)
- (call-interactively 'gds-associate-buffer))
- (set-text-properties 0 (length expr) nil expr)
- (gds-send (format "eval (%S . %S) #f \"Emacs expression\" 0 0 %S %s"
- (or correlator 'expression)
- (gds-abbreviated expr)
- expr
- (if debugp '(debug) '(none)))
- gds-client))
- (defconst gds-abbreviated-length 35)
- (defun gds-abbreviated (code)
- (let ((nlpos (string-match (regexp-quote "\n") code)))
- (while nlpos
- (setq code
- (if (= nlpos (- (length code) 1))
- (substring code 0 nlpos)
- (concat (substring code 0 nlpos)
- "\\n"
- (substring code (+ nlpos 1)))))
- (setq nlpos (string-match (regexp-quote "\n") code))))
- (if (> (length code) gds-abbreviated-length)
- (concat (substring code 0 (- gds-abbreviated-length 3)) "...")
- code))
- (defun gds-eval-defun (&optional debugp)
- "Evaluate the defun (top-level form) at point. If invoked with
- `C-u' prefix (or, in a program, with optional DEBUGP arg non-nil),
- pause and pop up the stack at the start of the evaluation, so that the
- user can single-step through the code."
- (interactive "P")
- (save-excursion
- (end-of-defun)
- (let ((end (point)))
- (beginning-of-defun)
- (gds-eval-region (point) end debugp))))
- (defun gds-eval-last-sexp (&optional debugp)
- "Evaluate the sexp before point. If invoked with `C-u' prefix (or,
- in a program, with optional DEBUGP arg non-nil), pause and pop up the
- stack at the start of the evaluation, so that the user can single-step
- through the code."
- (interactive "P")
- (gds-eval-region (save-excursion (backward-sexp) (point)) (point) debugp))
- ;;;; Help.
- ;; Help is implemented as a special case of evaluation, identified by
- ;; the evaluation correlator 'help.
- (defun gds-help-symbol (sym)
- "Get help for SYM (a Scheme symbol)."
- (interactive
- (let ((sym (thing-at-point 'symbol))
- (enable-recursive-minibuffers t)
- val)
- (setq val (read-from-minibuffer
- (if sym
- (format "Describe Guile symbol (default %s): " sym)
- "Describe Guile symbol: ")))
- (list (if (zerop (length val)) sym val))))
- (gds-eval-expression (format "(help %s)" sym) 'help))
- (defun gds-apropos (regex)
- "List Guile symbols matching REGEX."
- (interactive
- (let ((sym (thing-at-point 'symbol))
- (enable-recursive-minibuffers t)
- val)
- (setq val (read-from-minibuffer
- (if sym
- (format "Guile apropos (regexp, default \"%s\"): " sym)
- "Guile apropos (regexp): ")))
- (list (if (zerop (length val)) sym val))))
- (set-text-properties 0 (length regex) nil regex)
- (gds-eval-expression (format "(apropos %S)" regex) 'apropos))
- ;;;; Displaying results of help and eval.
- (defun gds-display-results (client correlator stack-available results)
- (let* ((helpp+bufname (cond ((eq (car correlator) 'help)
- '(t . "*Guile Help*"))
- ((eq (car correlator) 'apropos)
- '(t . "*Guile Apropos*"))
- (t
- '(nil . "*Guile Evaluation*"))))
- (helpp (car helpp+bufname)))
- (let ((buf (get-buffer-create (cdr helpp+bufname))))
- (save-selected-window
- (save-excursion
- (set-buffer buf)
- (gds-dissociate-buffer)
- (erase-buffer)
- (scheme-mode)
- (insert (cdr correlator) "\n\n")
- (while results
- (insert (car results))
- (or (bolp) (insert "\\\n"))
- (if helpp
- nil
- (if (cadr results)
- (mapcar (function (lambda (value)
- (insert " => " value "\n")))
- (cadr results))
- (insert " => no (or unspecified) value\n"))
- (insert "\n"))
- (setq results (cddr results)))
- (if stack-available
- (let ((beg (point))
- (map (make-sparse-keymap)))
- (define-key map [mouse-1] 'gds-show-last-stack)
- (define-key map "\C-m" 'gds-show-last-stack)
- (insert "[click here to show error stack]")
- (add-text-properties beg (point)
- (list 'keymap map
- 'mouse-face 'highlight))
- (insert "\n")))
- (goto-char (point-min))
- (gds-associate-buffer client))
- (pop-to-buffer buf)
- (run-hooks 'temp-buffer-show-hook)))))
- (defun gds-show-last-stack ()
- "Show stack of the most recent error."
- (interactive)
- (or gds-client
- (gds-auto-associate-buffer)
- (call-interactively 'gds-associate-buffer))
- (gds-send "debug-lazy-trap-context" gds-client))
- ;;;; Completion.
- (defvar gds-completion-results nil)
- (defun gds-complete-symbol ()
- "Complete the Guile symbol before point. Returns `t' if anything
- interesting happened, `nil' if not."
- (interactive)
- (or gds-client
- (gds-auto-associate-buffer)
- (call-interactively 'gds-associate-buffer))
- (let* ((chars (- (point) (save-excursion
- (while (let ((syntax (char-syntax (char-before (point)))))
- (or (eq syntax ?w) (eq syntax ?_)))
- (forward-char -1))
- (point)))))
- (if (zerop chars)
- nil
- (setq gds-completion-results nil)
- (gds-send (format "complete %s"
- (prin1-to-string
- (buffer-substring-no-properties (- (point) chars)
- (point))))
- gds-client)
- (while (null gds-completion-results)
- (accept-process-output gds-debug-server 0 200))
- (cond ((eq gds-completion-results 'error)
- (error "Internal error - please report the contents of the *Guile Evaluation* window"))
- ((eq gds-completion-results t)
- nil)
- ((stringp gds-completion-results)
- (if (<= (length gds-completion-results) chars)
- nil
- (insert (substring gds-completion-results chars))
- (message "Sole completion")
- t))
- ((= (length gds-completion-results) 1)
- (if (<= (length (car gds-completion-results)) chars)
- nil
- (insert (substring (car gds-completion-results) chars))
- t))
- (t
- (with-output-to-temp-buffer "*Completions*"
- (display-completion-list gds-completion-results))
- t)))))
- ;;;; Dispatcher for non-debug protocol.
- (defun gds-nondebug-protocol (client proc args)
- (cond (;; (eval-results ...) - Results of evaluation.
- (eq proc 'eval-results)
- (gds-display-results client (car args) (cadr args) (cddr args))
- ;; If these results indicate an error, set
- ;; gds-completion-results to non-nil in case the error arose
- ;; when trying to do a completion.
- (if (eq (caar args) 'error)
- (setq gds-completion-results 'error)))
- (;; (completion-result ...) - Available completions.
- (eq proc 'completion-result)
- (setq gds-completion-results (or (car args) t)))
- (;; (note ...) - For debugging only.
- (eq proc 'note))
- (;; (trace ...) - Tracing.
- (eq proc 'trace)
- (with-current-buffer (get-buffer-create "*GDS Trace*")
- (save-excursion
- (goto-char (point-max))
- (or (bolp) (insert "\n"))
- (insert "[client " (number-to-string client) "] " (car args) "\n"))))
- (t
- ;; Unexpected.
- (error "Bad protocol: %S" form))))
-
- ;;;; Scheme mode keymap items.
- (define-key scheme-mode-map "\M-\C-x" 'gds-eval-defun)
- (define-key scheme-mode-map "\C-x\C-e" 'gds-eval-last-sexp)
- (define-key scheme-mode-map "\C-c\C-e" 'gds-eval-expression)
- (define-key scheme-mode-map "\C-c\C-r" 'gds-eval-region)
- (define-key scheme-mode-map "\C-hg" 'gds-help-symbol)
- (define-key scheme-mode-map "\C-h\C-g" 'gds-apropos)
- (define-key scheme-mode-map "\C-hG" 'gds-apropos)
- (define-key scheme-mode-map "\C-hS" 'gds-show-last-stack)
- (define-key scheme-mode-map "\e\t" 'gds-complete-symbol)
- ;;;; The end!
- (provide 'gds-scheme)
- ;;; gds-scheme.el ends here.
|