summaryrefslogtreecommitdiff
path: root/emacs
diff options
context:
space:
mode:
authorNeil Jerram <neil@ossau.uklinux.net>2003-10-06 20:33:02 +0000
committerNeil Jerram <neil@ossau.uklinux.net>2003-10-06 20:33:02 +0000
commit41a80feb8a4ad30807be0c2f45c94301e40af2e5 (patch)
tree55878ad0e1b0fe2f4d1bf509eb5c6dbb9c1adc67 /emacs
parent6b5dc4ee3317da33fdb880caab1330c966cf93d3 (diff)
UI frontend work: eval support.
Diffstat (limited to 'emacs')
-rw-r--r--emacs/ChangeLog4
-rw-r--r--emacs/gds.el300
2 files changed, 279 insertions, 25 deletions
diff --git a/emacs/ChangeLog b/emacs/ChangeLog
index efa004269..4d7b0bf53 100644
--- a/emacs/ChangeLog
+++ b/emacs/ChangeLog
@@ -1,7 +1,3 @@
-2003-10-04 Neil Jerram <neil@ossau.uklinux.net>
-
- * gds.el (gds-handle-input): Handle `ready-for-input' status.
-
2003-08-20 Neil Jerram <neil@ossau.uklinux.net>
* guileint: New subdirectory.
diff --git a/emacs/gds.el b/emacs/gds.el
index 3b5923f03..c9d53575f 100644
--- a/emacs/gds.el
+++ b/emacs/gds.el
@@ -1,4 +1,4 @@
-;;; gds.el -- Guile debugging frontend
+;;; gds.el -- frontend for Guile development in Emacs
;;;; Copyright (C) 2003 Free Software Foundation, Inc.
;;;;
@@ -23,6 +23,7 @@
(require 'widget)
(require 'wid-edit)
+(require 'scheme)
;;;; Debugging (of this code!).
@@ -35,7 +36,7 @@
;;;; Customization group setup.
(defgroup gds nil
- "Customization options for Guile Debugging."
+ "Customization options for Guile Emacs frontend."
:group 'scheme)
@@ -49,8 +50,9 @@
;; the buffer position of the start of the next unread form.
(defvar gds-read-cursor nil)
-;; Start (or restart) the subprocess.
(defun gds-start ()
+ "Start (or restart, if already running) the GDS subprocess."
+ (interactive)
(if gds-process (gds-shutdown))
(with-current-buffer (get-buffer-create "*GDS Process*")
(erase-buffer)
@@ -72,6 +74,8 @@
;; Shutdown the subprocess and cleanup all associated data.
(defun gds-shutdown ()
+ "Shut down the GDS subprocess."
+ (interactive)
;; Do cleanup for all clients.
(while gds-names
(gds-client-cleanup (caar gds-names)))
@@ -125,7 +129,7 @@
;; At any moment one Guile application has the focus of the frontend
;; code. `gds-displayed-client' holds the port number of that client.
;; If there are no Guile applications wanting the focus - that is,
-;; ready for debugging instructions - `gds-displayed-client' is nil.
+;; ready for instructions - `gds-displayed-client' is nil.
(defvar gds-displayed-client nil)
;; The list of other Guile applications waiting for focus, referenced
@@ -172,7 +176,7 @@
(defun gds-focus-yield ()
(interactive)
(if (and (null gds-waiting)
- (y-or-n-p "No other clients waiting - bury *Guile Debug* buffer? "))
+ (y-or-n-p "No other clients waiting - bury *Guile* buffer? "))
(bury-buffer)
(or (memq gds-displayed-client gds-waiting)
(setq gds-waiting (append gds-waiting (list gds-displayed-client))))
@@ -287,8 +291,26 @@
;; (closed) - Client has gone away.
(gds-client-cleanup client))
+ ((eq proc 'eval-results)
+ ;; (eval-results ...) - Results of evaluation.
+ (gds-display-results client (cddr form)))
+
))))))
+(defun gds-display-results (client results)
+ (let ((buf (get-buffer-create "*Guile Results*")))
+ (save-excursion
+ (set-buffer buf)
+ (erase-buffer)
+ (while results
+ (insert (car results))
+ (mapcar (function (lambda (value)
+ (insert " => " value "\n")))
+ (cadr results))
+ (insert "\n")
+ (setq results (cddr results))))
+ (pop-to-buffer buf)))
+
;; Store latest status, stack or module list for the specified client.
(defmacro gds-set (alist client val)
`(let ((existing (assq ,client ,alist)))
@@ -315,25 +337,25 @@
(define-derived-mode gds-mode
fundamental-mode
- "Guile Debugging"
- "Major mode for Guile debugging information buffers.")
+ "Guile"
+ "Major mode for Guile information buffers.")
(defun gds-set-client-buffer (&optional client)
(if (and gds-client-buffer
(buffer-live-p gds-client-buffer))
(set-buffer gds-client-buffer)
- (setq gds-client-buffer (get-buffer-create "*Guile Debug*"))
+ (setq gds-client-buffer (get-buffer-create "*Guile*"))
(set-buffer gds-client-buffer)
(gds-mode))
;; Rename to something we don't want first. Otherwise, if the
;; buffer is already correctly named, we get a confusing change
- ;; from, say, `*Guile Debug: REPL*' to `*Guile Debug: REPL*<2>'.
- (rename-buffer "*Guile Debug Fake Buffer Name*" t)
+ ;; from, say, `*Guile: REPL*' to `*Guile: REPL*<2>'.
+ (rename-buffer "*Guile Fake Buffer Name*" t)
(rename-buffer (if client
- (concat "*Guile Debug: "
+ (concat "*Guile: "
(cdr (assq client gds-names))
"*")
- "*Guile Debug*")
+ "*Guile*")
t) ; Rename uniquely if needed,
; although it shouldn't be.
(force-mode-line-update t))
@@ -363,7 +385,7 @@
(defvar gds-displayed-stack nil)
(defvar gds-displayed-modules nil)
-;; Types of display areas in the *Guile Debug* buffer.
+;; Types of display areas in the *Guile* buffer.
(defvar gds-display-types '("Status" "Stack" "Modules"))
(defvar gds-display-type-regexp
(concat "^\\("
@@ -461,7 +483,7 @@
(setq gds-displayed-client client)
(dmessage "consider display")
(if (eq (window-buffer (selected-window)) gds-client-buffer)
- ;; *Guile Debug* buffer already selected.
+ ;; *Guile* buffer already selected.
(gds-display-buffers)
(dmessage "Running GDS timer")
(setq gds-timer
@@ -472,7 +494,7 @@
(gds-display-buffers))))))
(defun gds-display-buffers ()
- ;; If there's already a window showing the *Guile Debug* buffer, use
+ ;; If there's already a window showing the *Guile* buffer, use
;; it.
(let ((window (get-buffer-window gds-client-buffer t)))
(if window
@@ -751,9 +773,245 @@ not of primary interest when debugging application code."
;;;; Evaluating code.
-;; The Scheme process to which code is sent is determined in the usual
-;; cmuscheme.el way by the `scheme-buffer' variable (q.v.).
-;; Customizations to the way that code is sent, for example pro- and
-;; postlogs to set up and restore evaluation context correctly in the
-;; Scheme process, are achieved (elsewhere than this file) by advising
-;; `scheme-send-region' accordingly.
+;; 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.
+;;
+;; Where there are multiple Guile applications known to GDS, GDS by
+;; default sends code to the one that holds the debugging focus,
+;; i.e. `gds-displayed-client'. Where no application has the focus,
+;; or the command is invoked `C-u', GDS asks the user which
+;; application is intended.
+
+(defun gds-read-client ()
+ (let* ((def (if gds-displayed-client
+ (cdr (assq gds-displayed-client gds-names))))
+ (prompt (if def
+ (concat "Application for eval (default "
+ def
+ "): ")
+ "Application for eval: "))
+ (name
+ (completing-read prompt
+ (mapcar (function cdr) gds-names)
+ nil t nil nil
+ def)))
+ (let (client (names gds-names))
+ (while (and names (not client))
+ (if (string-equal (cadar names) name)
+ (setq client (caar names)))
+ (setq names (cdr names))))))
+
+(defun gds-choose-client (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 (gds-read-client))
+ ;; If ask not forced, and there is a client with the focus,
+ ;; default to that one.
+ gds-displayed-client
+ ;; Last resort - ask the user.
+ (gds-read-client)
+ ;; Signal an error.
+ (error "No application chosen.")))
+
+(defcustom gds-default-module-name '(guile-user)
+ "Name of the default module for GDS code evaluation, as list of symbols.
+This module is used when there is no `define-module' form in the
+buffer preceding the code to be evaluated."
+ :type 'sexp
+ :group 'gds)
+
+(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
+ (gds-send (format "(%S eval %s %S %d %d %S)\n"
+ client
+ (if module (prin1-to-string module) "#f")
+ port-name line column
+ (buffer-substring-no-properties start end)))))
+
+(defun gds-eval-expression (expr &optional client)
+ "Evaluate the supplied EXPR (a string)."
+ (interactive "sEvaluate expression: \nP")
+ (setq client (gds-choose-client client))
+ (gds-send (format "(%S eval #f \"Emacs expression\" 0 0 %S)\n"
+ client expr)))
+
+(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))
+
+(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 "(%S load %S)\n" client file-name)))
+
+;; Install the process communication commands in the scheme-mode keymap.
+(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-defun)
+(define-key scheme-mode-map "\C-c\C-r" 'gds-eval-region)
+(define-key scheme-mode-map "\C-c\C-l" 'gds-load-file)
+
+
+;;;; Menu bar entries.
+
+(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 [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-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-here]
+ '(menu-item "Symbol At Point" gds-help-symbol-here))
+ (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 [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 [help]
+ `(menu-item "Help" ,gds-help-menu :enable gds-names))
+ (define-key gds-menu [eval]
+ `(menu-item "Evaluate" ,gds-eval-menu :enable gds-names))
+ (define-key gds-menu [debug]
+ `(menu-item "Debug" ,gds-debug-menu :enable (and gds-displayed-client
+ (gds-client-waiting))))
+ (setq menu-bar-final-items
+ (cons 'guile menu-bar-final-items))
+ (define-key global-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)
+
+(if (and gds-autostart-server
+ (not gds-process))
+ (gds-start))
+
+(provide 'gds)
+
+;;; gds.el ends here.