;;;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ;;;; ;;;; The author can be reached at djurfeldt@nada.kth.se ;;;; Mikael Djurfeldt, SANS/NADA KTH, 10044 STOCKHOLM, SWEDEN ;;;; (I didn't write this!) ;;;; ;;; ********************************************************************* ;;; * This is the Guile side of the Emacs interface * ;;; * Experimental hACK---the real version will be coming soon (almost) * ;;; ********************************************************************* ;;; {Session support for Emacs} ;;; (define-module (ice-9 emacs) :use-module (ice-9 debug) :use-module (ice-9 threads) :use-module (ice-9 session) :no-backtrace) (define emacs-escape-character #\sub) (define emacs-output-port (current-output-port)) (define (make-emacs-command char) (let ((cmd (list->string (list emacs-escape-character char)))) (lambda () (display cmd emacs-output-port)))) (define enter-input-wait (make-emacs-command #\s)) (define exit-input-wait (make-emacs-command #\f)) (define enter-read-character #\r) (define sending-error (make-emacs-command #\F)) (define sending-backtrace (make-emacs-command #\B)) (define sending-result (make-emacs-command #\x)) (define end-of-text (make-emacs-command #\.)) (define no-stack (make-emacs-command #\S)) (define no-source (make-emacs-command #\R)) ;; {Error handling} ;; (add-hook! before-backtrace-hook sending-backtrace) (add-hook! after-backtrace-hook end-of-text) (add-hook! before-error-hook sending-error) (add-hook! after-error-hook end-of-text) ;; {Repl} ;; (set-current-error-port emacs-output-port) (add-hook! before-read-hook (lambda () (enter-input-wait) (force-output emacs-output-port))) (add-hook! after-read-hook (lambda () (exit-input-wait) (force-output emacs-output-port))) ;;; {Misc.} (define (make-emacs-load-port orig-port) (letrec ((read-char-fn (lambda args (let ((c (read-char orig-port))) (if (eq? c #\soh) (throw 'end-of-chunk) c))))) (make-soft-port (vector #f #f #f read-char-fn (lambda () (close-port orig-port))) "r"))) (set-current-input-port (make-emacs-load-port (current-input-port))) (define (result-to-emacs exp) (sending-result) (write exp emacs-output-port) (end-of-text) (force-output emacs-output-port)) (define load-acknowledge (make-emacs-command #\l)) (define load-port (current-input-port)) (define (flush-line port) (let loop ((c (read-char port))) (if (not (eq? c #\nl)) (loop (read-char port))))) (define whitespace-chars (list #\space #\tab #\nl #\np)) (define (flush-whitespace port) (catch 'end-of-chunk (lambda () (let loop ((c (read-char port))) (cond ((eq? c the-eof-object) (error "End of file while receiving Emacs data")) ((memq c whitespace-chars) (loop (read-char port))) ((eq? c #\;) (flush-line port) (loop (read-char port))) (else (unread-char c port)))) #f) (lambda args (read-char port) ; Read final newline #t))) (define (emacs-load filename linum colnum module interactivep) (define (read-and-eval! port) (let ((x (read port))) (if (eof-object? x) (throw 'end-of-file) (primitive-eval x)))) (set-port-filename! %%load-port filename) (set-port-line! %%load-port linum) (set-port-column! %%load-port colnum) (lazy-catch #t (lambda () (let loop ((endp (flush-whitespace %%load-port))) (if (not endp) (begin (save-module-excursion (lambda () (if module (set-current-module (resolve-module module #f))) (let ((result (start-stack read-and-eval! (read-and-eval! %%load-port)))) (if interactivep (result-to-emacs result))))) (loop (flush-whitespace %%load-port))) (begin (load-acknowledge))) (set-port-filename! %%load-port #f))) ;reset port filename (lambda (key . args) (set-port-filename! %%load-port #f) (cond ((eq? key 'end-of-chunk) (fluid-set! the-last-stack #f) (set! stack-saved? #t) (scm-error 'misc-error #f "Incomplete expression" '() '())) ((eq? key 'exit)) (else (save-stack 2) (catch 'end-of-chunk (lambda () (let loop () (read-char %%load-port) (loop))) (lambda args #f)) (apply throw key args)))))) (define (emacs-eval-request form) (result-to-emacs (eval form (interaction-environment)))) ;;*fixme* Not necessary to use flags no-stack and no-source (define (get-frame-source frame) (if (or (not (fluid-ref the-last-stack)) (>= frame (stack-length (fluid-ref the-last-stack)))) (begin (no-stack) #f) (let* ((frame (stack-ref (fluid-ref the-last-stack) (frame-number->index frame))) (source (frame-source frame))) (or source (begin (no-source) #f))))) (define (emacs-select-frame frame) (let ((source (get-frame-source frame))) (if source (let ((fname (source-property source 'filename)) (line (source-property source 'line)) (column (source-property source 'column))) (if (and fname line column) (list fname line column) (begin (no-source) '()))) '()))) (define (object->string x . method) (with-output-to-string (lambda () ((if (null? method) write (car method)) x)))) (define (format template . rest) (let loop ((chars (string->list template)) (result '()) (rest rest)) (cond ((null? chars) (list->string (reverse result))) ((char=? (car chars) #\%) (loop (cddr chars) (append (reverse (string->list (case (cadr chars) ((#\S) (object->string (car rest))) ((#\s) (object->string (car rest) display))))) result) (cdr rest))) (else (loop (cdr chars) (cons (car chars) result) rest))))) (define (error-args->string args) (let ((msg (apply format (caddr args) (cadddr args)))) (if (symbol? (cadr args)) (string-append (symbol->string (cadr args)) ": " msg) msg))) (define (emacs-frame-eval frame form) (let ((source (get-frame-source frame))) (if source (catch #t (lambda () (list 'result (object->string (local-eval (with-input-from-string form read) (memoized-environment source))))) (lambda args (list (car args) (error-args->string args)))) (begin (no-source) '())))) (define (emacs-symdoc symbol) (if (or (not (module-bound? (current-module) symbol)) (not (procedure? (eval symbol (interaction-environment))))) 'nil (procedure-documentation (eval symbol (interaction-environment))))) ;;; A fix to get the emacs interface to work together with the module system. ;;; (for-each (lambda (name value) (module-define! the-root-module name value)) '(%%load-port %%emacs-load %%emacs-eval-request %%emacs-select-frame %%emacs-frame-eval %%emacs-symdoc %%apropos-internal) (list load-port emacs-load emacs-eval-request emacs-select-frame emacs-frame-eval emacs-symdoc apropos-internal))