gds-scheme.el 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. ;;; gds-scheme.el -- GDS function for Scheme mode buffers
  2. ;;;; Copyright (C) 2005 Neil Jerram
  3. ;;;;
  4. ;;;; This library is free software; you can redistribute it and/or
  5. ;;;; modify it under the terms of the GNU Lesser General Public
  6. ;;;; License as published by the Free Software Foundation; either
  7. ;;;; version 2.1 of the License, or (at your option) any later
  8. ;;;; version.
  9. ;;;;
  10. ;;;; This library is distributed in the hope that it will be useful,
  11. ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. ;;;; Lesser General Public License for more details.
  14. ;;;;
  15. ;;;; You should have received a copy of the GNU Lesser General Public
  16. ;;;; License along with this library; if not, write to the Free
  17. ;;;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
  18. ;;;; 02111-1307 USA
  19. (require 'comint)
  20. (require 'scheme)
  21. (require 'derived)
  22. (require 'pp)
  23. ;;;; Maintaining an association between a Guile client process and a
  24. ;;;; set of Scheme mode buffers.
  25. (defcustom gds-auto-create-utility-client t
  26. "Whether to automatically create a utility Guile client, and
  27. associate the current buffer with it, if there are no existing Guile
  28. clients available to GDS when the user does something that requires a
  29. running Guile client."
  30. :type 'boolean
  31. :group 'gds)
  32. (defcustom gds-auto-associate-single-client t
  33. "Whether to automatically associate the current buffer with an
  34. existing Guile client, if there is only only client known to GDS when
  35. the user does something that requires a running Guile client, and the
  36. current buffer is not already associated with a Guile client."
  37. :type 'boolean
  38. :group 'gds)
  39. (defcustom gds-auto-associate-last-client t
  40. "Whether to automatically associate the current buffer with the
  41. Guile client that most recently caused that buffer to be displayed,
  42. when the user does something that requires a running Guile client and
  43. the current buffer is not already associated with a Guile client."
  44. :type 'boolean
  45. :group 'gds)
  46. (defvar gds-last-touched-by nil
  47. "For each Scheme mode buffer, this records the GDS client that most
  48. recently `touched' that buffer in the sense of using it to display
  49. source code, for example for the source code relevant to a debugger
  50. stack frame.")
  51. (make-variable-buffer-local 'gds-last-touched-by)
  52. (defun gds-auto-associate-buffer ()
  53. "Automatically associate the current buffer with a Guile client, if
  54. possible."
  55. (let* ((num-clients (length gds-client-info))
  56. (client
  57. (or
  58. ;; If there are no clients yet, and
  59. ;; `gds-auto-create-utility-client' allows us to create one
  60. ;; automatically, do that.
  61. (and (= num-clients 0)
  62. gds-auto-create-utility-client
  63. (gds-start-utility-guile))
  64. ;; Otherwise, if there is a single existing client, and
  65. ;; `gds-auto-associate-single-client' allows us to use it
  66. ;; for automatic association, do that.
  67. (and (= num-clients 1)
  68. gds-auto-associate-single-client
  69. (caar gds-client-info))
  70. ;; Otherwise, if the current buffer was displayed because
  71. ;; of a Guile client trapping somewhere in its code, and
  72. ;; `gds-auto-associate-last-client' allows us to associate
  73. ;; with that client, do so.
  74. (and gds-auto-associate-last-client
  75. gds-last-touched-by))))
  76. (if client
  77. (gds-associate-buffer client))))
  78. (defun gds-associate-buffer (client)
  79. "Associate the current buffer with the Guile process CLIENT.
  80. This means that operations in this buffer that require a running Guile
  81. process - such as evaluation, help, completion and setting traps -
  82. will be sent to the Guile process whose name or connection number is
  83. CLIENT."
  84. (interactive (list (gds-choose-client)))
  85. ;; If this buffer is already associated, dissociate from its
  86. ;; existing client first.
  87. (if gds-client (gds-dissociate-buffer))
  88. ;; Store the client number in the buffer-local variable gds-client.
  89. (setq gds-client client)
  90. ;; Add this buffer to the list of buffers associated with the
  91. ;; client.
  92. (gds-client-put client 'associated-buffers
  93. (cons (current-buffer)
  94. (gds-client-get client 'associated-buffers))))
  95. (defun gds-dissociate-buffer ()
  96. "Dissociate the current buffer from any specific Guile process."
  97. (interactive)
  98. (if gds-client
  99. (progn
  100. ;; Remove this buffer from the list of buffers associated with
  101. ;; the current client.
  102. (gds-client-put gds-client 'associated-buffers
  103. (delq (current-buffer)
  104. (gds-client-get gds-client 'associated-buffers)))
  105. ;; Reset the buffer-local variable gds-client.
  106. (setq gds-client nil)
  107. ;; Clear any process status indication from the modeline.
  108. (setq mode-line-process nil)
  109. (force-mode-line-update))))
  110. (defun gds-show-client-status (client status-string)
  111. "Show a client's status in the modeline of all its associated
  112. buffers."
  113. (let ((buffers (gds-client-get client 'associated-buffers)))
  114. (while buffers
  115. (if (buffer-live-p (car buffers))
  116. (with-current-buffer (car buffers)
  117. (setq mode-line-process status-string)
  118. (force-mode-line-update)))
  119. (setq buffers (cdr buffers)))))
  120. (defcustom gds-running-text ":running"
  121. "*Mode line text used to show that a Guile process is \"running\".
  122. \"Running\" means that the process cannot currently accept any input
  123. from the GDS frontend in Emacs, because all of its threads are busy
  124. running code that GDS cannot easily interrupt."
  125. :type 'string
  126. :group 'gds)
  127. (defcustom gds-ready-text ":ready"
  128. "*Mode line text used to show that a Guile process is \"ready\".
  129. \"Ready\" means that the process is ready to interact with the GDS
  130. frontend in Emacs, because at least one of its threads is waiting for
  131. GDS input."
  132. :type 'string
  133. :group 'gds)
  134. (defcustom gds-debug-text ":debug"
  135. "*Mode line text used to show that a Guile process is \"debugging\".
  136. \"Debugging\" means that the process is using the GDS frontend in
  137. Emacs to display an error or trap so that the user can debug it."
  138. :type 'string
  139. :group 'gds)
  140. (defun gds-choose-client ()
  141. "Ask the user to choose a GDS client process from a list."
  142. (let ((table '())
  143. (default nil))
  144. ;; Prepare a table containing all current clients.
  145. (mapcar (lambda (client-info)
  146. (setq table (cons (cons (cadr (memq 'name client-info))
  147. (car client-info))
  148. table)))
  149. gds-client-info)
  150. ;; Add an entry to allow the user to ask for a new process.
  151. (setq table (cons (cons "Start a new Guile process" nil) table))
  152. ;; Work out a good default. If the buffer has a good value in
  153. ;; gds-last-touched-by, we use that; otherwise default to starting
  154. ;; a new process.
  155. (setq default (or (and gds-last-touched-by
  156. (gds-client-get gds-last-touched-by 'name))
  157. (caar table)))
  158. ;; Read using this table.
  159. (let* ((name (completing-read "Choose a Guile process: "
  160. table
  161. nil
  162. t ; REQUIRE-MATCH
  163. nil ; INITIAL-INPUT
  164. nil ; HIST
  165. default))
  166. ;; Convert name to a client number.
  167. (client (cdr (assoc name table))))
  168. ;; If the user asked to start a new Guile process, do that now.
  169. (or client (setq client (gds-start-utility-guile)))
  170. ;; Return the chosen client ID.
  171. client)))
  172. (defvar gds-last-utility-number 0
  173. "Number of the last started Guile utility process.")
  174. (defun gds-start-utility-guile ()
  175. "Start a new utility Guile process."
  176. (setq gds-last-utility-number (+ gds-last-utility-number 1))
  177. (let* ((procname (format "gds-util[%d]" gds-last-utility-number))
  178. (code (format "(begin
  179. %s
  180. (use-modules (ice-9 gds-client))
  181. (run-utility))"
  182. (if gds-scheme-directory
  183. (concat "(set! %load-path (cons "
  184. (format "%S" gds-scheme-directory)
  185. " %load-path))")
  186. "")))
  187. (proc (start-process procname
  188. (get-buffer-create procname)
  189. gds-guile-program
  190. "-q"
  191. "--debug"
  192. "-c"
  193. code))
  194. (client nil))
  195. ;; Note that this process can be killed automatically on Emacs
  196. ;; exit.
  197. (process-kill-without-query proc)
  198. ;; Set up a process filter to catch the new client's number.
  199. (set-process-filter proc
  200. (lambda (proc string)
  201. (setq client (string-to-number string))
  202. (if (process-buffer proc)
  203. (with-current-buffer (process-buffer proc)
  204. (insert string)))))
  205. ;; Accept output from the new process until we have its number.
  206. (while (not client)
  207. (accept-process-output proc))
  208. ;; Return the new process's client number.
  209. client))
  210. ;;;; Evaluating code.
  211. ;; The following commands send code for evaluation through the GDS TCP
  212. ;; connection, receive the result and any output generated through the
  213. ;; same connection, and display the result and output to the user.
  214. ;;
  215. ;; For each buffer where evaluations can be requested, GDS uses the
  216. ;; buffer-local variable `gds-client' to track which GDS client
  217. ;; program should receive and handle that buffer's evaluations.
  218. (defun gds-module-name (start end)
  219. "Determine and return the name of the module that governs the
  220. specified region. The module name is returned as a list of symbols."
  221. (interactive "r") ; why not?
  222. (save-excursion
  223. (goto-char start)
  224. (let (module-name)
  225. (while (and (not module-name)
  226. (beginning-of-defun-raw 1))
  227. (if (looking-at "(define-module ")
  228. (setq module-name
  229. (progn
  230. (goto-char (match-end 0))
  231. (read (current-buffer))))))
  232. module-name)))
  233. (defcustom gds-emacs-buffer-port-name-prefix "Emacs buffer: "
  234. "Prefix used when telling Guile the name of the port from which a
  235. chunk of Scheme code (to be evaluated) comes. GDS uses this prefix,
  236. followed by the buffer name, in two cases: when the buffer concerned
  237. is not associated with a file, or if the buffer has been modified
  238. since last saving to its file. In the case where the buffer is
  239. identical to a saved file, GDS uses the file name as the port name."
  240. :type '(string)
  241. :group 'gds)
  242. (defun gds-port-name (start end)
  243. "Return port name for the specified region of the current buffer.
  244. The name will be used by Guile as the port name when evaluating that
  245. region's code."
  246. (or (and (not (buffer-modified-p))
  247. buffer-file-name)
  248. (concat gds-emacs-buffer-port-name-prefix (buffer-name))))
  249. (defun gds-line-and-column (pos)
  250. "Return 0-based line and column number at POS."
  251. (let (line column)
  252. (save-excursion
  253. (goto-char pos)
  254. (setq column (current-column))
  255. (beginning-of-line)
  256. (setq line (count-lines (point-min) (point))))
  257. (cons line column)))
  258. (defun gds-eval-region (start end &optional debugp)
  259. "Evaluate the current region. If invoked with `C-u' prefix (or, in
  260. a program, with optional DEBUGP arg non-nil), pause and pop up the
  261. stack at the start of the evaluation, so that the user can single-step
  262. through the code."
  263. (interactive "r\nP")
  264. (or gds-client
  265. (gds-auto-associate-buffer)
  266. (call-interactively 'gds-associate-buffer))
  267. (let ((module (gds-module-name start end))
  268. (port-name (gds-port-name start end))
  269. (lc (gds-line-and-column start)))
  270. (let ((code (buffer-substring-no-properties start end)))
  271. (gds-send (format "eval (region . %S) %s %S %d %d %S %s"
  272. (gds-abbreviated code)
  273. (if module (prin1-to-string module) "#f")
  274. port-name (car lc) (cdr lc)
  275. code
  276. (if debugp '(debug) '(none)))
  277. gds-client))))
  278. (defun gds-eval-expression (expr &optional correlator debugp)
  279. "Evaluate the supplied EXPR (a string). If invoked with `C-u'
  280. prefix (or, in a program, with optional DEBUGP arg non-nil), pause and
  281. pop up the stack at the start of the evaluation, so that the user can
  282. single-step through the code."
  283. (interactive "sEvaluate expression: \ni\nP")
  284. (or gds-client
  285. (gds-auto-associate-buffer)
  286. (call-interactively 'gds-associate-buffer))
  287. (set-text-properties 0 (length expr) nil expr)
  288. (gds-send (format "eval (%S . %S) #f \"Emacs expression\" 0 0 %S %s"
  289. (or correlator 'expression)
  290. (gds-abbreviated expr)
  291. expr
  292. (if debugp '(debug) '(none)))
  293. gds-client))
  294. (defconst gds-abbreviated-length 35)
  295. (defun gds-abbreviated (code)
  296. (let ((nlpos (string-match (regexp-quote "\n") code)))
  297. (while nlpos
  298. (setq code
  299. (if (= nlpos (- (length code) 1))
  300. (substring code 0 nlpos)
  301. (concat (substring code 0 nlpos)
  302. "\\n"
  303. (substring code (+ nlpos 1)))))
  304. (setq nlpos (string-match (regexp-quote "\n") code))))
  305. (if (> (length code) gds-abbreviated-length)
  306. (concat (substring code 0 (- gds-abbreviated-length 3)) "...")
  307. code))
  308. (defun gds-eval-defun (&optional debugp)
  309. "Evaluate the defun (top-level form) at point. If invoked with
  310. `C-u' prefix (or, in a program, with optional DEBUGP arg non-nil),
  311. pause and pop up the stack at the start of the evaluation, so that the
  312. user can single-step through the code."
  313. (interactive "P")
  314. (save-excursion
  315. (end-of-defun)
  316. (let ((end (point)))
  317. (beginning-of-defun)
  318. (gds-eval-region (point) end debugp))))
  319. (defun gds-eval-last-sexp (&optional debugp)
  320. "Evaluate the sexp before point. If invoked with `C-u' prefix (or,
  321. in a program, with optional DEBUGP arg non-nil), pause and pop up the
  322. stack at the start of the evaluation, so that the user can single-step
  323. through the code."
  324. (interactive "P")
  325. (gds-eval-region (save-excursion (backward-sexp) (point)) (point) debugp))
  326. ;;;; Help.
  327. ;; Help is implemented as a special case of evaluation, identified by
  328. ;; the evaluation correlator 'help.
  329. (defun gds-help-symbol (sym)
  330. "Get help for SYM (a Scheme symbol)."
  331. (interactive
  332. (let ((sym (thing-at-point 'symbol))
  333. (enable-recursive-minibuffers t)
  334. val)
  335. (setq val (read-from-minibuffer
  336. (if sym
  337. (format "Describe Guile symbol (default %s): " sym)
  338. "Describe Guile symbol: ")))
  339. (list (if (zerop (length val)) sym val))))
  340. (gds-eval-expression (format "(help %s)" sym) 'help))
  341. (defun gds-apropos (regex)
  342. "List Guile symbols matching REGEX."
  343. (interactive
  344. (let ((sym (thing-at-point 'symbol))
  345. (enable-recursive-minibuffers t)
  346. val)
  347. (setq val (read-from-minibuffer
  348. (if sym
  349. (format "Guile apropos (regexp, default \"%s\"): " sym)
  350. "Guile apropos (regexp): ")))
  351. (list (if (zerop (length val)) sym val))))
  352. (set-text-properties 0 (length regex) nil regex)
  353. (gds-eval-expression (format "(apropos %S)" regex) 'apropos))
  354. ;;;; Displaying results of help and eval.
  355. (defun gds-display-results (client correlator stack-available results)
  356. (let* ((helpp+bufname (cond ((eq (car correlator) 'help)
  357. '(t . "*Guile Help*"))
  358. ((eq (car correlator) 'apropos)
  359. '(t . "*Guile Apropos*"))
  360. (t
  361. '(nil . "*Guile Evaluation*"))))
  362. (helpp (car helpp+bufname)))
  363. (let ((buf (get-buffer-create (cdr helpp+bufname))))
  364. (save-selected-window
  365. (save-excursion
  366. (set-buffer buf)
  367. (gds-dissociate-buffer)
  368. (erase-buffer)
  369. (scheme-mode)
  370. (insert (cdr correlator) "\n\n")
  371. (while results
  372. (insert (car results))
  373. (or (bolp) (insert "\\\n"))
  374. (if helpp
  375. nil
  376. (if (cadr results)
  377. (mapcar (function (lambda (value)
  378. (insert " => " value "\n")))
  379. (cadr results))
  380. (insert " => no (or unspecified) value\n"))
  381. (insert "\n"))
  382. (setq results (cddr results)))
  383. (if stack-available
  384. (let ((beg (point))
  385. (map (make-sparse-keymap)))
  386. (define-key map [mouse-1] 'gds-show-last-stack)
  387. (define-key map "\C-m" 'gds-show-last-stack)
  388. (insert "[click here to show error stack]")
  389. (add-text-properties beg (point)
  390. (list 'keymap map
  391. 'mouse-face 'highlight))
  392. (insert "\n")))
  393. (goto-char (point-min))
  394. (gds-associate-buffer client))
  395. (pop-to-buffer buf)
  396. (run-hooks 'temp-buffer-show-hook)))))
  397. (defun gds-show-last-stack ()
  398. "Show stack of the most recent error."
  399. (interactive)
  400. (or gds-client
  401. (gds-auto-associate-buffer)
  402. (call-interactively 'gds-associate-buffer))
  403. (gds-send "debug-lazy-trap-context" gds-client))
  404. ;;;; Completion.
  405. (defvar gds-completion-results nil)
  406. (defun gds-complete-symbol ()
  407. "Complete the Guile symbol before point. Returns `t' if anything
  408. interesting happened, `nil' if not."
  409. (interactive)
  410. (or gds-client
  411. (gds-auto-associate-buffer)
  412. (call-interactively 'gds-associate-buffer))
  413. (let* ((chars (- (point) (save-excursion
  414. (while (let ((syntax (char-syntax (char-before (point)))))
  415. (or (eq syntax ?w) (eq syntax ?_)))
  416. (forward-char -1))
  417. (point)))))
  418. (if (zerop chars)
  419. nil
  420. (setq gds-completion-results nil)
  421. (gds-send (format "complete %s"
  422. (prin1-to-string
  423. (buffer-substring-no-properties (- (point) chars)
  424. (point))))
  425. gds-client)
  426. (while (null gds-completion-results)
  427. (accept-process-output gds-debug-server 0 200))
  428. (cond ((eq gds-completion-results 'error)
  429. (error "Internal error - please report the contents of the *Guile Evaluation* window"))
  430. ((eq gds-completion-results t)
  431. nil)
  432. ((stringp gds-completion-results)
  433. (if (<= (length gds-completion-results) chars)
  434. nil
  435. (insert (substring gds-completion-results chars))
  436. (message "Sole completion")
  437. t))
  438. ((= (length gds-completion-results) 1)
  439. (if (<= (length (car gds-completion-results)) chars)
  440. nil
  441. (insert (substring (car gds-completion-results) chars))
  442. t))
  443. (t
  444. (with-output-to-temp-buffer "*Completions*"
  445. (display-completion-list gds-completion-results))
  446. t)))))
  447. ;;;; Dispatcher for non-debug protocol.
  448. (defun gds-nondebug-protocol (client proc args)
  449. (cond (;; (eval-results ...) - Results of evaluation.
  450. (eq proc 'eval-results)
  451. (gds-display-results client (car args) (cadr args) (cddr args))
  452. ;; If these results indicate an error, set
  453. ;; gds-completion-results to non-nil in case the error arose
  454. ;; when trying to do a completion.
  455. (if (eq (caar args) 'error)
  456. (setq gds-completion-results 'error)))
  457. (;; (completion-result ...) - Available completions.
  458. (eq proc 'completion-result)
  459. (setq gds-completion-results (or (car args) t)))
  460. (;; (note ...) - For debugging only.
  461. (eq proc 'note))
  462. (;; (trace ...) - Tracing.
  463. (eq proc 'trace)
  464. (with-current-buffer (get-buffer-create "*GDS Trace*")
  465. (save-excursion
  466. (goto-char (point-max))
  467. (or (bolp) (insert "\n"))
  468. (insert "[client " (number-to-string client) "] " (car args) "\n"))))
  469. (t
  470. ;; Unexpected.
  471. (error "Bad protocol: %S" form))))
  472. ;;;; Scheme mode keymap items.
  473. (define-key scheme-mode-map "\M-\C-x" 'gds-eval-defun)
  474. (define-key scheme-mode-map "\C-x\C-e" 'gds-eval-last-sexp)
  475. (define-key scheme-mode-map "\C-c\C-e" 'gds-eval-expression)
  476. (define-key scheme-mode-map "\C-c\C-r" 'gds-eval-region)
  477. (define-key scheme-mode-map "\C-hg" 'gds-help-symbol)
  478. (define-key scheme-mode-map "\C-h\C-g" 'gds-apropos)
  479. (define-key scheme-mode-map "\C-hG" 'gds-apropos)
  480. (define-key scheme-mode-map "\C-hS" 'gds-show-last-stack)
  481. (define-key scheme-mode-map "\e\t" 'gds-complete-symbol)
  482. ;;;; The end!
  483. (provide 'gds-scheme)
  484. ;;; gds-scheme.el ends here.