mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-05-01 12:20:26 +02:00
and report read errors nicely. * gds.el (gds-display-buffers): Don't select the GDS window.
1626 lines
52 KiB
EmacsLisp
1626 lines
52 KiB
EmacsLisp
;;; gds.el -- frontend for Guile development in Emacs
|
|
|
|
;;;; Copyright (C) 2003 Free Software Foundation, Inc.
|
|
;;;;
|
|
;;;; 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
|
|
|
|
|
|
;;;; Prerequisites.
|
|
|
|
(require 'widget)
|
|
(require 'wid-edit)
|
|
(require 'scheme)
|
|
(require 'cl)
|
|
(require 'comint)
|
|
(require 'info)
|
|
|
|
|
|
;;;; Customization group setup.
|
|
|
|
(defgroup gds nil
|
|
"Customization options for Guile Emacs frontend."
|
|
:group 'scheme)
|
|
|
|
|
|
;;;; Communication with the (emacs gds-server) subprocess.
|
|
|
|
;; The subprocess object.
|
|
(defvar gds-process nil)
|
|
|
|
;; Subprocess output goes into the `*GDS Process*' buffer, and
|
|
;; is then read from there one form at a time. `gds-read-cursor' is
|
|
;; the buffer position of the start of the next unread form.
|
|
(defvar gds-read-cursor nil)
|
|
|
|
;; The guile executable used by the GDS server and captive client
|
|
;; processes.
|
|
(defcustom gds-guile-program "guile"
|
|
"*The guile executable used by GDS, specifically by its server and
|
|
captive client processes."
|
|
:type 'string
|
|
:group 'gds)
|
|
|
|
(defun gds-start ()
|
|
"Start (or restart, if already running) the GDS subprocess."
|
|
(interactive)
|
|
(gds-kill-captive)
|
|
(if gds-process (gds-shutdown))
|
|
(with-current-buffer (get-buffer-create "*GDS Process*")
|
|
(erase-buffer)
|
|
(setq gds-process
|
|
(let ((process-connection-type nil)) ; use a pipe
|
|
(start-process "gds"
|
|
(current-buffer)
|
|
gds-guile-program
|
|
"-q"
|
|
"--debug"
|
|
"-c"
|
|
"(begin (use-modules (emacs gds-server)) (run-server))"))))
|
|
(setq gds-read-cursor (point-min))
|
|
(set-process-filter gds-process (function gds-filter))
|
|
(set-process-sentinel gds-process (function gds-sentinel))
|
|
(set-process-coding-system gds-process 'latin-1-unix)
|
|
(process-kill-without-query gds-process))
|
|
|
|
;; Shutdown the subprocess and cleanup all associated data.
|
|
(defun gds-shutdown ()
|
|
"Shut down the GDS subprocess."
|
|
(interactive)
|
|
;; Reset variables.
|
|
(setq gds-buffers nil)
|
|
;; Kill the subprocess.
|
|
(condition-case nil
|
|
(progn
|
|
(kill-process gds-process)
|
|
(accept-process-output gds-process 0 200))
|
|
(error))
|
|
(setq gds-process nil))
|
|
|
|
;; Subprocess output filter: inserts normally into the process buffer,
|
|
;; then tries to reread the output one form at a time and delegates
|
|
;; processing of each form to `gds-handle-input'.
|
|
(defun gds-filter (proc string)
|
|
(with-current-buffer (process-buffer proc)
|
|
(save-excursion
|
|
(goto-char (process-mark proc))
|
|
(insert-before-markers string))
|
|
(goto-char gds-read-cursor)
|
|
(while (let ((form (condition-case nil
|
|
(read (current-buffer))
|
|
(error nil))))
|
|
(if form
|
|
(save-excursion
|
|
(gds-handle-input form)))
|
|
form)
|
|
(setq gds-read-cursor (point)))))
|
|
|
|
;; Subprocess sentinel: do nothing. (Currently just here to avoid
|
|
;; inserting un-`read'able process status messages into the process
|
|
;; buffer.)
|
|
(defun gds-sentinel (proc event)
|
|
)
|
|
|
|
;; Send input to the subprocess.
|
|
(defun gds-send (string client)
|
|
(process-send-string gds-process (format "(%S %s)\n" client string))
|
|
(let ((buf (gds-client-ref 'gds-transcript)))
|
|
(if buf
|
|
(with-current-buffer buf
|
|
(goto-char (point-max))
|
|
(let ((inhibit-read-only t))
|
|
(insert (format "tx (%S %s)\n" client string)))))))
|
|
|
|
|
|
;;;; Focussing in and out on interaction with a particular client.
|
|
|
|
;;;; The slight possible problems here are that popping up a client's
|
|
;;;; interaction windows when that client wants attention might
|
|
;;;; interrupt something else that the Emacs user was working on at
|
|
;;;; the time, and that if multiple clients are being debugged at the
|
|
;;;; same time, their popping up of interaction windows might become
|
|
;;;; confusing. For this reason, we allow GDS's behavior to be
|
|
;;;; customized via the variables `gds-focus-in-function' and
|
|
;;;; `gds-focus-out-function'.
|
|
;;;;
|
|
;;;; That said, the default policy, which is probably OK for most
|
|
;;;; users most of the time, is very simple: when a client wants
|
|
;;;; attention, its interaction windows are popped up immediately.
|
|
|
|
(defun gds-request-focus (client)
|
|
(funcall gds-focus-in-function client))
|
|
|
|
(defcustom gds-focus-in-function (function gds-focus-in)
|
|
"Function to call when a GDS client program wants user attention.
|
|
The function is called with one argument, the CLIENT in question."
|
|
:type 'function
|
|
:group 'gds)
|
|
|
|
(defun gds-focus-in (client)
|
|
(gds-display-buffers client))
|
|
|
|
(defun gds-quit ()
|
|
(interactive)
|
|
(funcall gds-focus-out-function))
|
|
|
|
(defcustom gds-focus-out-function (function gds-focus-out)
|
|
"Function to call when user quits interacting with a GDS client."
|
|
:type 'function
|
|
:group 'gds)
|
|
|
|
(defun gds-focus-out ()
|
|
(if (if (gds-client-blocked)
|
|
(y-or-n-p "Client is waiting for input. Quit anyway? ")
|
|
t)
|
|
(bury-buffer (current-buffer))))
|
|
|
|
|
|
;;;; Multiple client focus -- an alternative implementation.
|
|
|
|
;;;; The following code is provided as an alternative example of how a
|
|
;;;; customized GDS could schedule the display of multiple clients
|
|
;;;; that are competing for user attention.
|
|
|
|
;; - `gds-waiting' holds a list of clients that want attention but
|
|
;; haven't yet got it. A client is added to this list for two
|
|
;; reasons. (1) When it is blocked waiting for user input.
|
|
;; (2) When it first connects to GDS, even if not blocked.
|
|
;;
|
|
;; - `gds-focus-client' holds the client, if any, that currently has
|
|
;; the user's attention. A client can be given the focus if
|
|
;; `gds-focus-client' is nil at the time that the client wants
|
|
;; attention, or if another client relinquishes it. A client can
|
|
;; relinquish the focus in two ways. (1) If the client application
|
|
;; says that it is no longer blocked, and a small time passes without
|
|
;; it becoming blocked again. (2) If the user explicitly `quits'
|
|
;; that client.
|
|
;;
|
|
;; (defvar gds-focus-client nil)
|
|
;; (defvar gds-waiting nil)
|
|
;;
|
|
;; (defun gds-focus-in-alternative (client)
|
|
;; (cond ((eq client gds-focus-client)
|
|
;; ;; CLIENT already has the focus. Display its buffer.
|
|
;; (gds-display-buffers client))
|
|
;; (gds-focus-client
|
|
;; ;; Another client has the focus. Add CLIENT to `gds-waiting'.
|
|
;; (or (memq client gds-waiting)
|
|
;; (setq gds-waiting (append gds-waiting (list client)))))
|
|
;; (t
|
|
;; ;; Give focus to CLIENT and display its buffer.
|
|
;; (setq gds-focus-client client)
|
|
;; (gds-display-buffers client))))
|
|
;;
|
|
;; (defun gds-focus-out-alternative ()
|
|
;; (if (or (car gds-waiting)
|
|
;; (not (gds-client-blocked))
|
|
;; (y-or-n-p
|
|
;; "Client is blocked and no others are waiting. Still quit? "))
|
|
;; (progn
|
|
;; (bury-buffer (current-buffer))
|
|
;; ;; Pass on the focus.
|
|
;; (setq gds-focus-client (car gds-waiting)
|
|
;; gds-waiting (cdr gds-waiting))
|
|
;; ;; If this client is blocked, add it back into the waiting list.
|
|
;; (if (gds-client-blocked)
|
|
;; (gds-request-focus gds-client))
|
|
;; ;; If there is a new focus client, request display for it.
|
|
;; (if gds-focus-client
|
|
;; (gds-request-focus gds-focus-client)))))
|
|
|
|
|
|
;;;; GDS protocol dispatch.
|
|
|
|
;; General dispatch function called by the subprocess filter.
|
|
(defun gds-handle-input (form)
|
|
(let ((client (car form)))
|
|
(or (eq client '*)
|
|
(let* ((proc (cadr form))
|
|
(args (cddr form))
|
|
(buf (gds-client-buffer client proc args)))
|
|
(if buf (gds-handle-client-input buf client proc args))))))
|
|
|
|
(defun gds-handle-client-input (buf client proc args)
|
|
(with-current-buffer buf
|
|
(with-current-buffer gds-transcript
|
|
(goto-char (point-max))
|
|
(let ((inhibit-read-only t))
|
|
(insert (format "rx %S" (cons client (cons proc args))) "\n")))
|
|
|
|
(cond (;; (name ...) - Client name.
|
|
(eq proc 'name)
|
|
(setq gds-pid (cadr args))
|
|
(gds-promote-view 'interaction)
|
|
(gds-request-focus client))
|
|
|
|
(;; (current-module ...) - Current module.
|
|
(eq proc 'current-module)
|
|
(setq gds-current-module (car args)))
|
|
|
|
(;; (stack ...) - Stack at an error or breakpoint.
|
|
(eq proc 'stack)
|
|
(setq gds-stack args)
|
|
(gds-promote-view 'stack))
|
|
|
|
(;; (modules ...) - Application's loaded modules.
|
|
(eq proc 'modules)
|
|
(while args
|
|
(or (assoc (car args) gds-modules)
|
|
(setq gds-modules (cons (list (car args)) gds-modules)))
|
|
(setq args (cdr args))))
|
|
|
|
(;; (output ...) - Last printed output.
|
|
(eq proc 'output)
|
|
(setq gds-output (car args))
|
|
(gds-add-view 'messages))
|
|
|
|
(;; (status ...) - Application status indication.
|
|
(eq proc 'status)
|
|
(setq gds-status (car args))
|
|
(if (eq gds-status 'running)
|
|
(gds-delete-view 'browser)
|
|
(gds-add-view 'browser))
|
|
(if (eq gds-status 'waiting-for-input)
|
|
(progn
|
|
(gds-promote-view 'stack)
|
|
(gds-update-buffers)
|
|
(gds-request-focus client))
|
|
(setq gds-stack nil)
|
|
(gds-delete-view 'stack)
|
|
(gds-update-buffers-in-a-while)))
|
|
|
|
(;; (module MODULE ...) - The specified module's bindings.
|
|
(eq proc 'module)
|
|
(let ((minfo (assoc (car args) gds-modules)))
|
|
(if minfo
|
|
(setcdr (cdr minfo) (cdr args)))))
|
|
|
|
(;; (closed) - Client has gone away.
|
|
(eq proc 'closed)
|
|
(setq gds-status 'closed)
|
|
(gds-update-buffers)
|
|
(setq gds-buffers
|
|
(delq (assq client gds-buffers) gds-buffers)))
|
|
|
|
(;; (eval-results ...) - Results of evaluation.
|
|
(eq proc 'eval-results)
|
|
(gds-display-results client (car args) (cdr args)))
|
|
|
|
(;; (completion-result ...) - Available completions.
|
|
(eq proc 'completion-result)
|
|
(setq gds-completion-results (or (car args) t)))
|
|
|
|
(;; (breakpoint-set FILE LINE COLUMN INFO) - Breakpoint set.
|
|
(eq proc 'breakpoint-set)
|
|
(let ((file (nth 0 args))
|
|
(line (nth 1 args))
|
|
(column (nth 2 args))
|
|
(info (nth 3 args)))
|
|
(with-current-buffer (find-file-noselect file)
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(or (zerop line)
|
|
(forward-line line))
|
|
(move-to-column column)
|
|
(let ((os (overlays-at (point))) o)
|
|
(while os
|
|
(if (and (overlay-get (car os) 'gds-breakpoint-info)
|
|
(= (overlay-start (car os)) (point)))
|
|
(progn
|
|
(overlay-put (car os)
|
|
'gds-breakpoint-info
|
|
info)
|
|
(overlay-put (car os)
|
|
'before-string
|
|
gds-active-breakpoint-before-string)
|
|
(overlay-put (car os)
|
|
'after-string
|
|
gds-active-breakpoint-after-string)
|
|
(setq os nil))
|
|
(setq os (cdr os)))))))))
|
|
|
|
(;; (thread-status THREAD-TYPE THREAD-NUMBER STATUS [CORRELATOR])
|
|
(eq proc 'thread-status)
|
|
(if (eq (car args) 'eval)
|
|
(let ((number (nth 1 args))
|
|
(status (nth 2 args))
|
|
(correlator (nth 3 args)))
|
|
(if (eq status 'busy)
|
|
(progn
|
|
(setq gds-evals-in-progress
|
|
(append gds-evals-in-progress
|
|
(list (cons number correlator))))
|
|
(run-at-time 0.5 nil
|
|
(function gds-display-slow-eval)
|
|
buf number correlator)
|
|
(gds-promote-view 'interaction))
|
|
(let ((existing (assq number gds-evals-in-progress)))
|
|
(if existing
|
|
(setq gds-evals-in-progress
|
|
(delq existing gds-evals-in-progress)))))
|
|
(gds-update-buffers))))
|
|
|
|
)))
|
|
|
|
(defun gds-display-slow-eval (buf number correlator)
|
|
(with-current-buffer buf
|
|
(let ((entry (assq number gds-evals-in-progress)))
|
|
(if (and entry
|
|
(eq (cdr entry) correlator))
|
|
(progn
|
|
(gds-promote-view 'interaction)
|
|
(gds-request-focus gds-client))))))
|
|
|
|
|
|
;;;; Per-client buffer state.
|
|
|
|
;; This section contains code that is specific to each Guile client's
|
|
;; buffer but independent of any particular `view'.
|
|
|
|
;; Alist mapping each client port number to corresponding buffer.
|
|
(defvar gds-buffers nil)
|
|
|
|
(define-derived-mode gds-mode
|
|
scheme-mode
|
|
"Guile Interaction"
|
|
"Major mode for interacting with a Guile client application."
|
|
(widget-minor-mode 1))
|
|
|
|
(defvar gds-client nil
|
|
"GDS client's port number.")
|
|
(make-variable-buffer-local 'gds-client)
|
|
|
|
(defvar gds-status nil
|
|
"GDS client's latest status, one of the following symbols.
|
|
`running' - Application is running.
|
|
`waiting-for-input' - Application is blocked waiting for instruction
|
|
from the frontend.
|
|
`ready-for-input' - Application is not blocked but can also accept
|
|
asynchronous instructions from the frontend.")
|
|
(make-variable-buffer-local 'gds-status)
|
|
|
|
(defvar gds-transcript nil
|
|
"Transcript buffer for this GDS client.")
|
|
(make-variable-buffer-local 'gds-transcript)
|
|
|
|
;; Return client buffer for specified client and protocol input.
|
|
(defun gds-client-buffer (client proc args)
|
|
(if (eq proc 'name)
|
|
;; Introduction from client - create a new buffer.
|
|
(with-current-buffer (generate-new-buffer (car args))
|
|
(gds-mode)
|
|
(setq gds-client client)
|
|
(setq gds-transcript
|
|
(find-file-noselect
|
|
(expand-file-name (concat "~/.gds-transcript-" (car args)))))
|
|
(with-current-buffer gds-transcript
|
|
(goto-char (point-max))
|
|
(insert "\nTranscript:\n"))
|
|
(setq gds-buffers
|
|
(cons (cons client (current-buffer))
|
|
gds-buffers))
|
|
(current-buffer))
|
|
;; Otherwise there should be an existing buffer that we can
|
|
;; return.
|
|
(let ((existing (assq client gds-buffers)))
|
|
(if (buffer-live-p (cdr existing))
|
|
(cdr existing)
|
|
(setq gds-buffers (delq existing gds-buffers))
|
|
(gds-client-buffer client 'name '("(GDS buffer killed)"))))))
|
|
|
|
;; Get the current buffer's associated client's value of SYM.
|
|
(defun gds-client-ref (sym &optional client)
|
|
(and (or client gds-client)
|
|
(let ((buf (assq (or client gds-client) gds-buffers)))
|
|
(and buf
|
|
(cdr buf)
|
|
(buffer-live-p (cdr buf))
|
|
(with-current-buffer (cdr buf)
|
|
(symbol-value sym))))))
|
|
|
|
(defun gds-client-blocked ()
|
|
(eq (gds-client-ref 'gds-status) 'waiting-for-input))
|
|
|
|
(defvar gds-delayed-update-timer nil)
|
|
|
|
(defvar gds-delayed-update-buffers nil)
|
|
|
|
(defun gds-update-delayed-update-buffers ()
|
|
(while gds-delayed-update-buffers
|
|
(with-current-buffer (car gds-delayed-update-buffers)
|
|
(setq gds-delayed-update-buffers
|
|
(cdr gds-delayed-update-buffers))
|
|
(gds-update-buffers))))
|
|
|
|
(defun gds-update-buffers ()
|
|
(if (timerp gds-delayed-update-timer)
|
|
(cancel-timer gds-delayed-update-timer))
|
|
(setq gds-delayed-update-timer nil)
|
|
(let ((view (car gds-views))
|
|
(inhibit-read-only t))
|
|
(cond ((eq view 'stack)
|
|
(gds-insert-stack))
|
|
((eq view 'interaction)
|
|
(gds-insert-interaction))
|
|
((eq view 'browser)
|
|
(gds-insert-modules))
|
|
((eq view 'messages)
|
|
(gds-insert-messages))
|
|
(t
|
|
(error "Bad GDS view %S" view)))
|
|
;; Finish off.
|
|
(force-mode-line-update t)))
|
|
|
|
(defun gds-update-buffers-in-a-while ()
|
|
(or (memq (current-buffer) gds-delayed-update-buffers)
|
|
(setq gds-delayed-update-buffers
|
|
(cons (current-buffer) gds-delayed-update-buffers)))
|
|
(if (timerp gds-delayed-update-timer)
|
|
nil
|
|
(setq gds-delayed-update-timer
|
|
(run-at-time 0.5 nil (function gds-update-delayed-update-buffers)))))
|
|
|
|
(defun gds-display-buffers (client)
|
|
(let ((buf (cdr (assq client gds-buffers))))
|
|
;; If there's already a window showing the buffer, use it.
|
|
(let ((window (get-buffer-window buf t)))
|
|
(if window
|
|
(make-frame-visible (window-frame window))
|
|
(display-buffer buf)))
|
|
;; If there is an associated source buffer, display it as well.
|
|
(if (and (eq (car gds-views) 'stack)
|
|
gds-frame-source-overlay
|
|
(> (overlay-end gds-frame-source-overlay) 1))
|
|
(let ((window (display-buffer
|
|
(overlay-buffer gds-frame-source-overlay))))
|
|
(set-window-point window
|
|
(overlay-start gds-frame-source-overlay))))))
|
|
|
|
|
|
;;;; Management of `views'.
|
|
|
|
;; The idea here is to keep the buffer describing a Guile client
|
|
;; relatively uncluttered by only showing one kind of information
|
|
;; about that client at a time. Menu items and key sequences are
|
|
;; provided to switch easily between the available views.
|
|
|
|
(defvar gds-views nil
|
|
"List of available views for a GDS client. Each element is one of
|
|
the following symbols.
|
|
`interaction' - Interaction with running client.
|
|
`stack' - Call stack view.
|
|
`browser' - Modules and bindings browser view.
|
|
`breakpoints' - List of set breakpoints.
|
|
`messages' - Non-GDS-protocol output from the debugger.")
|
|
(make-variable-buffer-local 'gds-views)
|
|
|
|
(defun gds-promote-view (view)
|
|
(setq gds-views (cons view (delq view gds-views))))
|
|
|
|
(defun gds-switch-to-view (view)
|
|
(or (memq view gds-views)
|
|
(error "View %S is not available" view))
|
|
(gds-promote-view view)
|
|
(gds-update-buffers))
|
|
|
|
(defun gds-add-view (view)
|
|
(or (memq view gds-views)
|
|
(setq gds-views (append gds-views (list view)))))
|
|
|
|
(defun gds-delete-view (view)
|
|
(setq gds-views (delq view gds-views)))
|
|
|
|
|
|
;;;; `Interaction' view.
|
|
|
|
;; This view provides interaction with a normally running Guile
|
|
;; client, in other words one that is not stopped in the debugger but
|
|
;; is still available to take input from GDS (usually via a thread for
|
|
;; that purpose). The view supports evaluation, help requests,
|
|
;; control of `debug-on-exception' function, and methods for breaking
|
|
;; into the running code.
|
|
|
|
(defvar gds-current-module "()"
|
|
"GDS client's current module.")
|
|
(make-variable-buffer-local 'gds-current-module)
|
|
|
|
(defvar gds-pid nil
|
|
"GDS client's process ID.")
|
|
(make-variable-buffer-local 'gds-pid)
|
|
|
|
(defvar gds-debug-exceptions nil
|
|
"Whether to debug exceptions.")
|
|
(make-variable-buffer-local 'gds-debug-exceptions)
|
|
|
|
(defvar gds-exception-keys "signal misc-error"
|
|
"The exception keys for which to debug a GDS client.")
|
|
(make-variable-buffer-local 'gds-exception-keys)
|
|
|
|
(defvar gds-evals-in-progress nil
|
|
"Alist describing evaluations in progress.")
|
|
(make-variable-buffer-local 'gds-evals-in-progress)
|
|
|
|
(defvar gds-results nil
|
|
"Last help or evaluation results.")
|
|
(make-variable-buffer-local 'gds-results)
|
|
|
|
(defcustom gds-heading-face 'info-menu-header
|
|
"*Face used for headings in Guile Interaction buffers."
|
|
:type 'face
|
|
:group 'gds)
|
|
|
|
(defun gds-insert-interaction ()
|
|
(erase-buffer)
|
|
;; Insert stuff for interacting with a running (non-blocked) Guile
|
|
;; client.
|
|
(gds-heading-insert (buffer-name))
|
|
(widget-insert " "
|
|
(cdr (assq gds-status
|
|
'((running . "running (cannot accept input)")
|
|
(waiting-for-input . "waiting for input")
|
|
(ready-for-input . "running")
|
|
(closed . "closed"))))
|
|
", in "
|
|
gds-current-module
|
|
"\n\n")
|
|
(widget-create 'push-button
|
|
:notify (function gds-sigint)
|
|
"SIGINT")
|
|
(widget-insert " ")
|
|
(widget-create 'push-button
|
|
:notify (function gds-async-break)
|
|
"Break")
|
|
(widget-insert "\n")
|
|
(widget-create 'checkbox
|
|
:notify (function gds-toggle-debug-exceptions)
|
|
gds-debug-exceptions)
|
|
(widget-insert " Debug exception keys: ")
|
|
(widget-create 'editable-field
|
|
:notify (function gds-set-exception-keys)
|
|
gds-exception-keys)
|
|
;; Evaluation report area.
|
|
(widget-insert "\n")
|
|
(gds-heading-insert "Recent Evaluations")
|
|
(widget-insert " To run an evaluation, see the Guile->Evaluate menu.\n")
|
|
(if gds-results
|
|
(widget-insert "\n" (cdr gds-results)))
|
|
(let ((evals gds-evals-in-progress))
|
|
(while evals
|
|
(widget-insert "\n" (cddar evals) " - running ")
|
|
(let ((w (widget-create 'push-button
|
|
:notify (function gds-interrupt-eval)
|
|
"Interrupt")))
|
|
(widget-put w :thread-number (caar evals)))
|
|
(widget-insert "\n")
|
|
(setq evals (cdr evals)))))
|
|
|
|
(defun gds-heading-insert (text)
|
|
(let ((start (point)))
|
|
(widget-insert text)
|
|
(let ((o (make-overlay start (point))))
|
|
(overlay-put o 'face gds-heading-face)
|
|
(overlay-put o 'evaporate t))))
|
|
|
|
(defun gds-sigint (w &rest ignore)
|
|
(interactive)
|
|
(signal-process gds-pid 2))
|
|
|
|
(defun gds-async-break (w &rest ignore)
|
|
(interactive)
|
|
(gds-send "async-break" gds-client))
|
|
|
|
(defun gds-interrupt-eval (w &rest ignore)
|
|
(interactive)
|
|
(gds-send (format "interrupt-eval %S" (widget-get w :thread-number))
|
|
gds-client))
|
|
|
|
(defun gds-toggle-debug-exceptions (w &rest ignore)
|
|
(interactive)
|
|
(setq gds-debug-exceptions (widget-value w))
|
|
(gds-eval-expression (concat "(use-modules (ice-9 debugger))"
|
|
"(debug-on-error '("
|
|
gds-exception-keys
|
|
"))")))
|
|
|
|
(defun gds-set-exception-keys (w &rest ignore)
|
|
(interactive)
|
|
(setq gds-exception-keys (widget-value w)))
|
|
|
|
(defun gds-view-interaction ()
|
|
(interactive)
|
|
(gds-switch-to-view 'interaction))
|
|
|
|
|
|
;;;; `Stack' view.
|
|
|
|
;; This view shows the Guile call stack after the application has hit
|
|
;; an error, or when it is stopped in the debugger.
|
|
|
|
(defvar gds-stack nil
|
|
"GDS client's stack when last stopped.")
|
|
(make-variable-buffer-local 'gds-stack)
|
|
|
|
(defun gds-insert-stack ()
|
|
(erase-buffer)
|
|
(let ((frames (car gds-stack))
|
|
(index (cadr gds-stack))
|
|
(flags (caddr gds-stack))
|
|
frame items)
|
|
(cond ((memq 'application flags)
|
|
(widget-insert "Calling procedure:\n"))
|
|
((memq 'evaluation flags)
|
|
(widget-insert "Evaluating expression:\n"))
|
|
((memq 'return flags)
|
|
(widget-insert "Return value: "
|
|
(cadr (memq 'return flags))
|
|
"\n"))
|
|
(t
|
|
(widget-insert "Stack: " (prin1-to-string flags) "\n")))
|
|
(let ((i -1))
|
|
(gds-show-selected-frame (caddr (nth index frames)))
|
|
(while frames
|
|
(setq frame (car frames)
|
|
frames (cdr frames)
|
|
i (+ i 1)
|
|
items (cons (list 'item
|
|
(let ((s (cadr frame)))
|
|
(put-text-property 0 1 'index i s)
|
|
s))
|
|
items))))
|
|
(setq items (nreverse items))
|
|
(apply (function widget-create)
|
|
'radio-button-choice
|
|
:value (cadr (nth index items))
|
|
:notify (function gds-select-stack-frame)
|
|
items)
|
|
(widget-insert "\n")
|
|
(goto-char (point-min))))
|
|
|
|
(defun gds-select-stack-frame (widget &rest ignored)
|
|
(let* ((s (widget-value widget))
|
|
(ind (memq 'index (text-properties-at 0 s))))
|
|
(gds-send (format "debugger-command frame %d" (cadr ind))
|
|
gds-client)))
|
|
|
|
;; Overlay used to highlight the source expression corresponding to
|
|
;; the selected frame.
|
|
(defvar gds-frame-source-overlay nil)
|
|
|
|
(defun gds-show-selected-frame (source)
|
|
;; Highlight the frame source, if possible.
|
|
(if (and source
|
|
(file-readable-p (car source)))
|
|
(with-current-buffer (find-file-noselect (car source))
|
|
(if gds-frame-source-overlay
|
|
nil
|
|
(setq gds-frame-source-overlay (make-overlay 0 0))
|
|
(overlay-put gds-frame-source-overlay 'face 'highlight))
|
|
;; Move to source line. Note that Guile line numbering is
|
|
;; 0-based, while Emacs numbering is 1-based.
|
|
(save-restriction
|
|
(widen)
|
|
(goto-line (+ (cadr source) 1))
|
|
(move-to-column (caddr source))
|
|
(move-overlay gds-frame-source-overlay
|
|
(point)
|
|
(if (not (looking-at ")"))
|
|
(save-excursion (forward-sexp 1) (point))
|
|
;; It seems that the source coordinates for
|
|
;; backquoted expressions are at the end of
|
|
;; the sexp rather than the beginning...
|
|
(save-excursion (forward-char 1)
|
|
(backward-sexp 1) (point)))
|
|
(current-buffer))))
|
|
(if gds-frame-source-overlay
|
|
(move-overlay gds-frame-source-overlay 0 0))))
|
|
|
|
(defun gds-view-stack ()
|
|
(interactive)
|
|
(gds-switch-to-view 'stack))
|
|
|
|
|
|
;;;; `Breakpoints' view.
|
|
|
|
;; This view shows a list of breakpoints.
|
|
|
|
(defun gds-view-breakpoints ()
|
|
(interactive)
|
|
(gds-switch-to-view 'breakpoints))
|
|
|
|
|
|
;;;; `Browser' view.
|
|
|
|
;; This view shows a list of modules and module bindings.
|
|
|
|
(defcustom gds-module-filter '(t (guile nil) (ice-9 nil) (oop nil))
|
|
"Specification of which Guile modules the debugger should display.
|
|
This is a list with structure (DEFAULT EXCEPTION EXCEPTION...), where
|
|
DEFAULT is `t' or `nil' and each EXCEPTION has the structure (SYMBOL
|
|
DEFAULT EXCEPTION EXCEPTION...).
|
|
|
|
A Guile module name `(x y z)' is matched against this filter as
|
|
follows. If one of the top level EXCEPTIONs has SYMBOL `x', continue
|
|
by matching the rest of the module name, in this case `(y z)', against
|
|
that SYMBOL's DEFAULT and next level EXCEPTION list. Otherwise, if
|
|
the current DEFAULT is `t' display the module, and if the current
|
|
DEFAULT is `nil', don't display it.
|
|
|
|
This variable is usually set to exclude Guile system modules that are
|
|
not of primary interest when debugging application code."
|
|
:type 'sexp
|
|
:group 'gds)
|
|
|
|
(defun gds-show-module-p (name)
|
|
;; Determine whether to display the NAMEd module by matching NAME
|
|
;; against `gds-module-filter'.
|
|
(let ((default (car gds-module-filter))
|
|
(exceptions (cdr gds-module-filter)))
|
|
(let ((exception (assq (car name) exceptions)))
|
|
(if exception
|
|
(let ((gds-module-filter (cdr exception)))
|
|
(gds-show-module-p (cdr name)))
|
|
default))))
|
|
|
|
(defvar gds-modules nil
|
|
"GDS client's module information.
|
|
Alist mapping module names to their symbols and related information.
|
|
This looks like:
|
|
|
|
(((guile) t sym1 sym2 ...)
|
|
((guile-user))
|
|
((ice-9 debug) nil sym3 sym4)
|
|
...)
|
|
|
|
The `t' or `nil' after the module name indicates whether the module is
|
|
displayed in expanded form (that is, showing the bindings in that
|
|
module). The syms are actually all strings because some Guile symbols
|
|
are not readable by Emacs.")
|
|
(make-variable-buffer-local 'gds-modules)
|
|
|
|
(defun gds-insert-modules ()
|
|
(let ((p (if (eq (window-buffer (selected-window)) (current-buffer))
|
|
(point)
|
|
(point-min)))
|
|
(modules gds-modules))
|
|
(erase-buffer)
|
|
(insert "Modules:\n")
|
|
(while modules
|
|
(let ((minfo (car modules)))
|
|
(if (gds-show-module-p (car minfo))
|
|
(let ((w (widget-create 'push-button
|
|
:notify (function gds-module-notify)
|
|
(if (and (cdr minfo)
|
|
(cadr minfo))
|
|
"-" "+"))))
|
|
(widget-put w :module (cons gds-client (car minfo)))
|
|
(widget-insert " " (prin1-to-string (car minfo)) "\n")
|
|
(if (cadr minfo)
|
|
(let ((syms (cddr minfo)))
|
|
(while syms
|
|
(widget-insert " > " (car syms) "\n")
|
|
(setq syms (cdr syms))))))))
|
|
(setq modules (cdr modules)))
|
|
(insert "\n")
|
|
(goto-char p)))
|
|
|
|
(defun gds-module-notify (w &rest ignore)
|
|
(let* ((module (widget-get w :module))
|
|
(client (car module))
|
|
(name (cdr module))
|
|
(minfo (assoc name gds-modules)))
|
|
(if (cdr minfo)
|
|
;; Just toggle expansion state.
|
|
(progn
|
|
(setcar (cdr minfo) (not (cadr minfo)))
|
|
(gds-update-buffers))
|
|
;; Set flag to indicate module expanded.
|
|
(setcdr minfo (list t))
|
|
;; Get symlist from Guile.
|
|
(gds-send (format "query-module %S" name) client))))
|
|
|
|
(defun gds-query-modules ()
|
|
(interactive)
|
|
(gds-send "query-modules" gds-client))
|
|
|
|
(defun gds-view-browser ()
|
|
(interactive)
|
|
(or gds-modules (gds-query-modules))
|
|
(gds-switch-to-view 'browser))
|
|
|
|
|
|
;;;; `Messages' view.
|
|
|
|
;; This view shows recent non-GDS-protocol messages output from the
|
|
;; (ice-9 debugger) code.
|
|
|
|
(defvar gds-output nil
|
|
"GDS client's recent output (printed).")
|
|
(make-variable-buffer-local 'gds-output)
|
|
|
|
(defun gds-insert-messages ()
|
|
(erase-buffer)
|
|
;; Insert recent non-protocol output from (ice-9 debugger).
|
|
(insert gds-output)
|
|
(goto-char (point-min)))
|
|
|
|
(defun gds-view-messages ()
|
|
(interactive)
|
|
(gds-switch-to-view 'messages))
|
|
|
|
|
|
;;;; Debugger commands.
|
|
|
|
;; Typically but not necessarily used from the `stack' view.
|
|
|
|
(defun gds-go ()
|
|
(interactive)
|
|
(gds-send "debugger-command continue" gds-client))
|
|
|
|
(defun gds-next ()
|
|
(interactive)
|
|
(gds-send "debugger-command next 1" gds-client))
|
|
|
|
(defun gds-evaluate (expr)
|
|
(interactive "sEvaluate (in this stack frame): ")
|
|
(gds-send (format "debugger-command evaluate %s" (prin1-to-string expr))
|
|
gds-client))
|
|
|
|
(defun gds-step-in ()
|
|
(interactive)
|
|
(gds-send "debugger-command step 1" gds-client))
|
|
|
|
(defun gds-step-out ()
|
|
(interactive)
|
|
(gds-send "debugger-command finish" gds-client))
|
|
|
|
(defun gds-trace-finish ()
|
|
(interactive)
|
|
(gds-send "debugger-command trace-finish" gds-client))
|
|
|
|
(defun gds-frame-info ()
|
|
(interactive)
|
|
(gds-send "debugger-command info-frame" gds-client))
|
|
|
|
(defun gds-frame-args ()
|
|
(interactive)
|
|
(gds-send "debugger-command info-args" gds-client))
|
|
|
|
(defun gds-debug-trap-hooks ()
|
|
(interactive)
|
|
(gds-send "debugger-command debug-trap-hooks" gds-client))
|
|
|
|
(defun gds-up ()
|
|
(interactive)
|
|
(gds-send "debugger-command up 1" gds-client))
|
|
|
|
(defun gds-down ()
|
|
(interactive)
|
|
(gds-send "debugger-command down 1" gds-client))
|
|
|
|
|
|
;;;; Setting breakpoints.
|
|
|
|
(defun gds-set-breakpoint ()
|
|
(interactive)
|
|
(cond ((gds-in-source-buffer)
|
|
(gds-set-source-breakpoint))
|
|
((gds-in-stack)
|
|
(gds-set-stack-breakpoint))
|
|
((gds-in-modules)
|
|
(gds-set-module-breakpoint))
|
|
(t
|
|
(error "No way to set a breakpoint from here"))))
|
|
|
|
(defun gds-in-source-buffer ()
|
|
;; Not yet worked out what will be available in Scheme source
|
|
;; buffers.
|
|
nil)
|
|
|
|
(defun gds-in-stack ()
|
|
(save-excursion
|
|
(and (re-search-backward "^\\(Stack\\|Modules\\):" nil t)
|
|
(looking-at "Stack"))))
|
|
|
|
(defun gds-in-modules ()
|
|
(save-excursion
|
|
(and (re-search-backward "^\\(Stack\\|Modules\\):" nil t)
|
|
(looking-at "Modules"))))
|
|
|
|
(defun gds-set-module-breakpoint ()
|
|
(let ((sym (save-excursion
|
|
(beginning-of-line)
|
|
(and (looking-at " > \\([^ \n\t]+\\)")
|
|
(match-string 1))))
|
|
(module (save-excursion
|
|
(and (re-search-backward "^\\[[+---]\\] \\(([^)]+)\\)" nil t)
|
|
(match-string 1)))))
|
|
(or sym
|
|
(error "Couldn't find procedure name on current line"))
|
|
(or module
|
|
(error "Couldn't find module name for current line"))
|
|
(let ((behaviour
|
|
(completing-read
|
|
(format "Behaviour for breakpoint at %s:%s (default debug-here): "
|
|
module sym)
|
|
'(("debug-here")
|
|
("trace-here")
|
|
("trace-subtree"))
|
|
nil
|
|
t
|
|
nil
|
|
nil
|
|
"debug-here")))
|
|
(gds-send (format "set-breakpoint %s %s %s"
|
|
module
|
|
sym
|
|
behaviour)
|
|
gds-client))))
|
|
|
|
|
|
;;;; Scheme source breakpoints.
|
|
|
|
(defcustom gds-breakpoint-face 'default
|
|
"*Face used to highlight the location of a source breakpoint.
|
|
Specifically, this face highlights the opening parenthesis of the
|
|
form where the breakpoint is set."
|
|
:type 'face
|
|
:group 'gds)
|
|
|
|
(defcustom gds-new-breakpoint-before-string ""
|
|
"*String used to show the presence of a new source breakpoint.
|
|
`New' means that the breakpoint has been set but isn't yet known to
|
|
Guile because the containing code hasn't been reevaluated yet.
|
|
This string appears before the opening parenthesis of the form where
|
|
the breakpoint is set. If you prefer a marker to appear after the
|
|
opening parenthesis, make this string empty and use
|
|
`gds-new-breakpoint-after-string'."
|
|
:type 'string
|
|
:group 'gds)
|
|
|
|
(defcustom gds-new-breakpoint-after-string "=?= "
|
|
"*String used to show the presence of a new source breakpoint.
|
|
`New' means that the breakpoint has been set but isn't yet known to
|
|
Guile because the containing code hasn't been reevaluated yet.
|
|
This string appears after the opening parenthesis of the form where
|
|
the breakpoint is set. If you prefer a marker to appear before the
|
|
opening parenthesis, make this string empty and use
|
|
`gds-new-breakpoint-before-string'."
|
|
:type 'string
|
|
:group 'gds)
|
|
|
|
(defcustom gds-active-breakpoint-before-string ""
|
|
"*String used to show the presence of a source breakpoint.
|
|
`Active' means that the breakpoint is known to Guile.
|
|
This string appears before the opening parenthesis of the form where
|
|
the breakpoint is set. If you prefer a marker to appear after the
|
|
opening parenthesis, make this string empty and use
|
|
`gds-active-breakpoint-after-string'."
|
|
:type 'string
|
|
:group 'gds)
|
|
|
|
(defcustom gds-active-breakpoint-after-string "=|= "
|
|
"*String used to show the presence of a source breakpoint.
|
|
`Active' means that the breakpoint is known to Guile.
|
|
This string appears after the opening parenthesis of the form where
|
|
the breakpoint is set. If you prefer a marker to appear before the
|
|
opening parenthesis, make this string empty and use
|
|
`gds-active-breakpoint-before-string'."
|
|
:type 'string
|
|
:group 'gds)
|
|
|
|
(defun gds-source-breakpoint-pos ()
|
|
"Return the position of the starting parenthesis of the innermost
|
|
Scheme pair around point."
|
|
(if (eq (char-syntax (char-after)) ?\()
|
|
(point)
|
|
(save-excursion
|
|
(condition-case nil
|
|
(while t (forward-sexp -1))
|
|
(error))
|
|
(forward-char -1)
|
|
(while (not (eq (char-syntax (char-after)) ?\())
|
|
(forward-char -1))
|
|
(point))))
|
|
|
|
(defun gds-source-breakpoint-overlay-at (pos)
|
|
"Return the source breakpoint overlay at POS, if any."
|
|
(let* (o (os (overlays-at pos)))
|
|
(while os
|
|
(if (and (overlay-get (car os) 'gds-breakpoint-info)
|
|
(= (overlay-start (car os)) pos))
|
|
(setq o (car os)
|
|
os nil))
|
|
(setq os (cdr os)))
|
|
o))
|
|
|
|
(defun gds-set-source-breakpoint ()
|
|
(interactive)
|
|
(let* ((pos (gds-source-breakpoint-pos))
|
|
(o (gds-source-breakpoint-overlay-at pos)))
|
|
(if o
|
|
(error "There is already a breakpoint here!")
|
|
(setq o (make-overlay pos (+ pos 1)))
|
|
(overlay-put o 'evaporate t)
|
|
(overlay-put o 'face gds-breakpoint-face)
|
|
(overlay-put o 'gds-breakpoint-info 0)
|
|
(overlay-put o 'before-string gds-new-breakpoint-before-string)
|
|
(overlay-put o 'after-string gds-new-breakpoint-after-string))))
|
|
|
|
(defun gds-delete-source-breakpoint ()
|
|
(interactive)
|
|
(let* ((pos (gds-source-breakpoint-pos))
|
|
(o (gds-source-breakpoint-overlay-at pos)))
|
|
(or o
|
|
(error "There is no breakpoint here to delete!"))
|
|
(delete-overlay o)))
|
|
|
|
(defun gds-region-breakpoint-info (beg end)
|
|
"Return an alist of breakpoints in REGION.
|
|
The car of each alist element is a cons (LINE . COLUMN) giving the
|
|
source location of the breakpoint. The cdr is information describing
|
|
breakpoint properties. Currently `information' is just the breakpoint
|
|
index, for an existing Guile breakpoint, or 0 for a breakpoint that
|
|
isn't yet known to Guile."
|
|
(interactive "r")
|
|
(let ((os (overlays-in beg end))
|
|
info o)
|
|
(while os
|
|
(setq o (car os)
|
|
os (cdr os))
|
|
(if (overlay-get o 'gds-breakpoint-info)
|
|
(progn
|
|
(setq info
|
|
(cons (cons (save-excursion
|
|
(goto-char (overlay-start o))
|
|
(cons (save-excursion
|
|
(beginning-of-line)
|
|
(count-lines (point-min) (point)))
|
|
(current-column)))
|
|
(overlay-get o 'gds-breakpoint-info))
|
|
info))
|
|
;; Also now mark the breakpoint as `new'. It will become
|
|
;; `active' (again) when we receive a notification from
|
|
;; Guile that the breakpoint has been set.
|
|
(overlay-put o 'gds-breakpoint-info 0)
|
|
(overlay-put o 'before-string gds-new-breakpoint-before-string)
|
|
(overlay-put o 'after-string gds-new-breakpoint-after-string))))
|
|
(nreverse info)))
|
|
|
|
|
|
;;;; 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. In
|
|
;; the common case where GDS is only managing one client program, a
|
|
;; buffer's value of `gds-client' is set automatically to point to
|
|
;; that program the first time that an evaluation (or help or
|
|
;; completion) is requested. If there are multiple GDS clients
|
|
;; running at that time, GDS asks the user which one is intended.
|
|
|
|
(defun gds-read-client ()
|
|
(let* ((def (and gds-client (cdr (assq gds-client gds-names))))
|
|
(prompt (if def
|
|
(concat "Application for eval (default "
|
|
def
|
|
"): ")
|
|
"Application for eval: "))
|
|
(name
|
|
(completing-read prompt
|
|
(mapcar (function list)
|
|
(mapcar (function cdr) gds-names))
|
|
nil t nil nil
|
|
def)))
|
|
(let (client (names gds-names))
|
|
(while (and names (not client))
|
|
(if (string-equal (cdar names) name)
|
|
(setq client (caar names)))
|
|
(setq names (cdr names)))
|
|
client)))
|
|
|
|
(defun gds-choose-client (client)
|
|
;; Only keep the supplied client number if it is still valid.
|
|
(if (integerp client)
|
|
(setq client (gds-client-ref 'gds-client client)))
|
|
;; Only keep the current buffer's setting of `gds-client' if it is
|
|
;; still valid.
|
|
(if gds-client
|
|
(setq gds-client (gds-client-ref 'gds-client)))
|
|
|
|
(or ;; If client is an integer, it is the port number of the
|
|
;; intended client.
|
|
(if (integerp client)
|
|
client)
|
|
;; Any other non-nil value indicates invocation with a prefix
|
|
;; arg, which forces asking the user which application is
|
|
;; intended.
|
|
(if client
|
|
(setq gds-client (gds-read-client)))
|
|
;; If ask not forced, and current buffer is associated with a
|
|
;; client, use that client.
|
|
gds-client
|
|
;; If there are no clients at this point, and we are
|
|
;; allowed to autostart a captive Guile, do so.
|
|
(and (null gds-buffers)
|
|
gds-autostart-captive
|
|
(progn
|
|
(gds-start-captive t)
|
|
(while (null gds-buffers)
|
|
(accept-process-output (get-buffer-process gds-captive)
|
|
0 100000))
|
|
(setq gds-client (caar gds-buffers))))
|
|
;; If there is only one known client, use that one.
|
|
(if (and (car gds-buffers)
|
|
(null (cdr gds-buffers)))
|
|
(setq gds-client (caar gds-buffers)))
|
|
;; Last resort - ask the user.
|
|
(setq gds-client (gds-read-client))
|
|
;; Signal an error.
|
|
(error "No application chosen.")))
|
|
|
|
(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)))
|
|
|
|
(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 (buffer-file-name)
|
|
(concat "Emacs buffer: " (buffer-name))))
|
|
|
|
(defun gds-eval-region (start end &optional client)
|
|
"Evaluate the current region."
|
|
(interactive "r\nP")
|
|
(setq client (gds-choose-client client))
|
|
(let ((module (gds-module-name start end))
|
|
(port-name (gds-port-name start end))
|
|
line column)
|
|
(save-excursion
|
|
(goto-char start)
|
|
(setq column (current-column)) ; 0-based
|
|
(beginning-of-line)
|
|
(setq line (count-lines (point-min) (point)))) ; 0-based
|
|
(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 line column
|
|
(let ((bpinfo (gds-region-breakpoint-info start end)))
|
|
;; Make sure that "no bpinfo" is represented
|
|
;; as "()", not "nil", as Scheme doesn't
|
|
;; understand "nil".
|
|
(if bpinfo (format "%S" bpinfo) "()"))
|
|
code)
|
|
client))))
|
|
|
|
(defun gds-eval-expression (expr &optional client correlator)
|
|
"Evaluate the supplied EXPR (a string)."
|
|
(interactive "sEvaluate expression: \nP")
|
|
(setq client (gds-choose-client client))
|
|
(set-text-properties 0 (length expr) nil expr)
|
|
(gds-send (format "eval (%S . %S) #f \"Emacs expression\" 0 0 () %S"
|
|
(or correlator 'expression)
|
|
(gds-abbreviated expr)
|
|
expr)
|
|
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 client)
|
|
"Evaluate the defun (top-level form) at point."
|
|
(interactive "P")
|
|
(save-excursion
|
|
(end-of-defun)
|
|
(let ((end (point)))
|
|
(beginning-of-defun)
|
|
(gds-eval-region (point) end client))))
|
|
|
|
(defun gds-eval-last-sexp (&optional client)
|
|
"Evaluate the sexp before point."
|
|
(interactive "P")
|
|
(gds-eval-region (save-excursion (backward-sexp) (point)) (point) client))
|
|
|
|
|
|
;;;; Help.
|
|
|
|
;; Help is implemented as a special case of evaluation, identified by
|
|
;; the evaluation correlator 'help.
|
|
|
|
(defun gds-help-symbol (sym &optional client)
|
|
"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)
|
|
current-prefix-arg)))
|
|
(gds-eval-expression (format "(help %s)" sym) client 'help))
|
|
|
|
(defun gds-apropos (regex &optional client)
|
|
"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)
|
|
current-prefix-arg)))
|
|
(set-text-properties 0 (length regex) nil regex)
|
|
(gds-eval-expression (format "(apropos %S)" regex) client 'help))
|
|
|
|
(defvar gds-completion-results nil)
|
|
|
|
(defun gds-complete-symbol (&optional client)
|
|
"Complete the Guile symbol before point. Returns `t' if anything
|
|
interesting happened, `nil' if not."
|
|
(interactive "P")
|
|
(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 client (gds-choose-client client))
|
|
(setq gds-completion-results nil)
|
|
(gds-send (format "complete %s"
|
|
(prin1-to-string
|
|
(buffer-substring-no-properties (- (point) chars)
|
|
(point))))
|
|
client)
|
|
(while (null gds-completion-results)
|
|
(accept-process-output gds-process 0 200))
|
|
(cond ((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)))))
|
|
|
|
|
|
;;;; Display of evaluation and help results.
|
|
|
|
(defun gds-display-results (client correlator results)
|
|
(let ((helpp (eq (car correlator) 'help)))
|
|
(let ((buf (get-buffer-create (if helpp
|
|
"*Guile Help*"
|
|
"*Guile Results*"))))
|
|
(setq gds-results
|
|
(save-excursion
|
|
(set-buffer buf)
|
|
(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)))
|
|
(goto-char (point-min))
|
|
(if (and helpp (looking-at "Evaluating in "))
|
|
(delete-region (point) (progn (forward-line 1) (point))))
|
|
(cons correlator (buffer-string))))
|
|
;;(pop-to-buffer buf)
|
|
;;(run-hooks 'temp-buffer-show-hook)
|
|
;;(other-window 1)
|
|
))
|
|
(gds-promote-view 'interaction)
|
|
(gds-request-focus client))
|
|
|
|
|
|
;;;; Loading (evaluating) a whole Scheme file.
|
|
|
|
(defcustom gds-source-modes '(scheme-mode)
|
|
"*Used to determine if a buffer contains Scheme source code.
|
|
If it's loaded into a buffer that is in one of these major modes, it's
|
|
considered a scheme source file by `gds-load-file'."
|
|
:type '(repeat function)
|
|
:group 'gds)
|
|
|
|
(defvar gds-prev-load-dir/file nil
|
|
"Holds the last (directory . file) pair passed to `gds-load-file'.
|
|
Used for determining the default for the next `gds-load-file'.")
|
|
|
|
(defun gds-load-file (file-name &optional client)
|
|
"Load a Scheme file into the inferior Scheme process."
|
|
(interactive (list (car (comint-get-source "Load Scheme file: "
|
|
gds-prev-load-dir/file
|
|
gds-source-modes t))
|
|
; T because LOAD needs an
|
|
; exact name
|
|
current-prefix-arg))
|
|
(comint-check-source file-name) ; Check to see if buffer needs saved.
|
|
(setq gds-prev-load-dir/file (cons (file-name-directory file-name)
|
|
(file-name-nondirectory file-name)))
|
|
(setq client (gds-choose-client client))
|
|
(gds-send (format "load %S" file-name) client))
|
|
|
|
|
|
;;;; Scheme mode keymap items.
|
|
|
|
(define-key scheme-mode-map "\M-\C-x" 'gds-eval-defun);gnu convention
|
|
(define-key scheme-mode-map "\C-x\C-e" 'gds-eval-last-sexp);gnu convention
|
|
(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-c\C-l" 'gds-load-file)
|
|
(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 "\e\t" 'gds-complete-symbol)
|
|
(define-key scheme-mode-map "\C-x " 'gds-set-source-breakpoint)
|
|
(define-key scheme-mode-map "\C-x\e " 'gds-delete-source-breakpoint)
|
|
|
|
|
|
;;;; Guile Interaction mode keymap and menu items.
|
|
|
|
(define-key gds-mode-map "M" (function gds-query-modules))
|
|
|
|
(define-key gds-mode-map "g" (function gds-go))
|
|
(define-key gds-mode-map "q" (function gds-quit))
|
|
(define-key gds-mode-map " " (function gds-next))
|
|
(define-key gds-mode-map "e" (function gds-evaluate))
|
|
(define-key gds-mode-map "i" (function gds-step-in))
|
|
(define-key gds-mode-map "o" (function gds-step-out))
|
|
(define-key gds-mode-map "t" (function gds-trace-finish))
|
|
(define-key gds-mode-map "I" (function gds-frame-info))
|
|
(define-key gds-mode-map "A" (function gds-frame-args))
|
|
(define-key gds-mode-map "H" (function gds-debug-trap-hooks))
|
|
(define-key gds-mode-map "u" (function gds-up))
|
|
(define-key gds-mode-map "d" (function gds-down))
|
|
(define-key gds-mode-map "b" (function gds-set-breakpoint))
|
|
|
|
(define-key gds-mode-map "vi" (function gds-view-interaction))
|
|
(define-key gds-mode-map "vs" (function gds-view-stack))
|
|
(define-key gds-mode-map "vb" (function gds-view-breakpoints))
|
|
(define-key gds-mode-map "vB" (function gds-view-browser))
|
|
(define-key gds-mode-map "vm" (function gds-view-messages))
|
|
|
|
(defvar gds-view-menu nil
|
|
"GDS view menu.")
|
|
(if gds-view-menu
|
|
nil
|
|
(setq gds-view-menu (make-sparse-keymap "View"))
|
|
(define-key gds-view-menu [messages]
|
|
'(menu-item "Messages" gds-view-messages
|
|
:enable (memq 'messages gds-views)))
|
|
(define-key gds-view-menu [browser]
|
|
'(menu-item "Browser" gds-view-browser
|
|
:enable (memq 'browser gds-views)))
|
|
(define-key gds-view-menu [breakpoints]
|
|
'(menu-item "Breakpoints" gds-view-breakpoints
|
|
:enable (memq 'breakpoints gds-views)))
|
|
(define-key gds-view-menu [stack]
|
|
'(menu-item "Stack" gds-view-stack
|
|
:enable (memq 'stack gds-views)))
|
|
(define-key gds-view-menu [interaction]
|
|
'(menu-item "Interaction" gds-view-interaction
|
|
:enable (memq 'interaction gds-views))))
|
|
|
|
(defvar gds-debug-menu nil
|
|
"GDS debugging menu.")
|
|
(if gds-debug-menu
|
|
nil
|
|
(setq gds-debug-menu (make-sparse-keymap "Debug"))
|
|
(define-key gds-debug-menu [go]
|
|
'(menu-item "Go" gds-go))
|
|
(define-key gds-debug-menu [down]
|
|
'(menu-item "Move Down 1 Frame" gds-down))
|
|
(define-key gds-debug-menu [up]
|
|
'(menu-item "Move Up 1 Frame" gds-up))
|
|
(define-key gds-debug-menu [trace-finish]
|
|
'(menu-item "Trace This Frame" gds-trace-finish))
|
|
(define-key gds-debug-menu [step-out]
|
|
'(menu-item "Finish This Frame" gds-step-out))
|
|
(define-key gds-debug-menu [next]
|
|
'(menu-item "Next" gds-next))
|
|
(define-key gds-debug-menu [step-in]
|
|
'(menu-item "Single Step" gds-step-in))
|
|
(define-key gds-debug-menu [eval]
|
|
'(menu-item "Eval In This Frame..." gds-evaluate)))
|
|
|
|
(defvar gds-breakpoint-menu nil
|
|
"GDS breakpoint menu.")
|
|
(if gds-breakpoint-menu
|
|
nil
|
|
(setq gds-breakpoint-menu (make-sparse-keymap "Breakpoint"))
|
|
(define-key gds-breakpoint-menu [last-sexp]
|
|
'(menu-item "Delete Breakpoint" gds-delete-source-breakpoint))
|
|
(define-key gds-breakpoint-menu [set]
|
|
'(menu-item "Set Breakpoint" gds-set-source-breakpoint)))
|
|
|
|
(defvar gds-eval-menu nil
|
|
"GDS evaluation menu.")
|
|
(if gds-eval-menu
|
|
nil
|
|
(setq gds-eval-menu (make-sparse-keymap "Evaluate"))
|
|
(define-key gds-eval-menu [load-file]
|
|
'(menu-item "Load Scheme File" gds-load-file))
|
|
(define-key gds-eval-menu [defun]
|
|
'(menu-item "Defun At Point" gds-eval-defun))
|
|
(define-key gds-eval-menu [region]
|
|
'(menu-item "Region" gds-eval-region))
|
|
(define-key gds-eval-menu [last-sexp]
|
|
'(menu-item "Sexp Before Point" gds-eval-last-sexp))
|
|
(define-key gds-eval-menu [expr]
|
|
'(menu-item "Expression..." gds-eval-expression)))
|
|
|
|
(defvar gds-help-menu nil
|
|
"GDS help menu.")
|
|
(if gds-help-menu
|
|
nil
|
|
(setq gds-help-menu (make-sparse-keymap "Help"))
|
|
(define-key gds-help-menu [apropos]
|
|
'(menu-item "Apropos..." gds-apropos))
|
|
(define-key gds-help-menu [sym]
|
|
'(menu-item "Symbol..." gds-help-symbol)))
|
|
|
|
(defvar gds-advanced-menu nil
|
|
"Menu of rarely needed GDS operations.")
|
|
(if gds-advanced-menu
|
|
nil
|
|
(setq gds-advanced-menu (make-sparse-keymap "Advanced"))
|
|
(define-key gds-advanced-menu [run-captive]
|
|
'(menu-item "Run Captive Guile" gds-start-captive
|
|
:enable (not (comint-check-proc gds-captive))))
|
|
(define-key gds-advanced-menu [restart-gds]
|
|
'(menu-item "Restart IDE" gds-start :enable gds-process))
|
|
(define-key gds-advanced-menu [kill-gds]
|
|
'(menu-item "Shutdown IDE" gds-shutdown :enable gds-process))
|
|
(define-key gds-advanced-menu [start-gds]
|
|
'(menu-item "Start IDE" gds-start :enable (not gds-process))))
|
|
|
|
(defvar gds-menu nil
|
|
"Global menu for GDS commands.")
|
|
(if gds-menu
|
|
nil
|
|
(setq gds-menu (make-sparse-keymap "Guile"))
|
|
(define-key gds-menu [advanced]
|
|
(cons "Advanced" gds-advanced-menu))
|
|
(define-key gds-menu [separator-1]
|
|
'("--"))
|
|
(define-key gds-menu [view]
|
|
`(menu-item "View" ,gds-view-menu :enable gds-views))
|
|
(define-key gds-menu [debug]
|
|
`(menu-item "Debug" ,gds-debug-menu :enable (and gds-client
|
|
(gds-client-blocked))))
|
|
(define-key gds-menu [breakpoint]
|
|
`(menu-item "Breakpoints" ,gds-breakpoint-menu :enable t))
|
|
(define-key gds-menu [eval]
|
|
`(menu-item "Evaluate" ,gds-eval-menu :enable (or gds-buffers
|
|
gds-autostart-captive)))
|
|
(define-key gds-menu [help]
|
|
`(menu-item "Help" ,gds-help-menu :enable (or gds-buffers
|
|
gds-autostart-captive)))
|
|
(setq menu-bar-final-items
|
|
(cons 'guile menu-bar-final-items))
|
|
(define-key scheme-mode-map [menu-bar guile]
|
|
(cons "Guile" gds-menu)))
|
|
|
|
|
|
;;;; Autostarting the GDS server.
|
|
|
|
(defcustom gds-autostart-server t
|
|
"Whether to automatically start the GDS server when `gds.el' is loaded."
|
|
:type 'boolean
|
|
:group 'gds)
|
|
|
|
|
|
;;;; `Captive' Guile - a Guile process that is started when needed to
|
|
;;;; provide help, completion, evaluations etc.
|
|
|
|
(defcustom gds-autostart-captive t
|
|
"Whether to automatically start a `captive' Guile process when needed."
|
|
:type 'boolean
|
|
:group 'gds)
|
|
|
|
(defvar gds-captive nil
|
|
"Buffer of captive Guile.")
|
|
|
|
(defun gds-start-captive (&optional restart)
|
|
(interactive)
|
|
(if (and restart
|
|
(comint-check-proc gds-captive))
|
|
(gds-kill-captive))
|
|
(if (comint-check-proc gds-captive)
|
|
nil
|
|
(let ((process-connection-type nil))
|
|
(setq gds-captive (make-comint "captive-guile"
|
|
gds-guile-program
|
|
nil
|
|
"-q")))
|
|
(let ((proc (get-buffer-process gds-captive)))
|
|
(process-kill-without-query proc)
|
|
(comint-send-string proc "(set! %load-path (cons \"/home/neil/Guile/cvs/guile-core\" %load-path))\n")
|
|
(comint-send-string proc "(debug-enable 'backtrace)\n")
|
|
(comint-send-string proc "(use-modules (emacs gds-client))\n")
|
|
(comint-send-string proc "(gds-connect \"Captive Guile\" #f)\n"))))
|
|
|
|
(defun gds-kill-captive ()
|
|
(if gds-captive
|
|
(condition-case nil
|
|
(progn
|
|
(kill-process (get-buffer-process gds-captive))
|
|
(accept-process-output gds-process 0 200))
|
|
(error))))
|
|
|
|
|
|
;;;; If requested, autostart the server after loading.
|
|
|
|
(if (and gds-autostart-server
|
|
(not gds-process))
|
|
(gds-start))
|
|
|
|
|
|
;;;; The end!
|
|
|
|
(provide 'gds)
|
|
|
|
;;; gds.el ends here.
|