emacs.d/elpa/racket-mode-20200218.1623/racket/interactions.rkt
2020-02-22 12:54:34 +01:00

84 lines
3.3 KiB
Racket

#lang racket/base
(require racket/match
"fresh-line.rkt")
(provide current-sync/yield
get-interaction)
;; A channel to which a thread puts interactions that it reads using
;; the current-read-interaction handler (which can be set by a lang
;; from its configure-runtime, so, this should be compatible with
;; any lang, even non-sexpr langs).
;;
;; This is its own thread and channel for a couple reasons:
;;
;; - Issue #311. A consumer can use sync/timeout to avoid displaying a
;; prompt when multiple interactions are waiting.
;;
;; - Debugging. We can switch from the normal REPL to a debugger REPL,
;; without input being stuck inside a read call for the former.
;;
;; One wrinkle is we need to be careful about calling yield instead of
;; sync when the gui is active. See issue #326.
;; FIXME??: This used to be under the REPL custodian. Is it OK for it
;; _not_ to be, now? For instance what if user runs another file, but
;; this is still using the previous current-read-interaction value?
(define chan (make-channel))
(define (read-interaction/put-channel)
(define in ((current-get-interaction-input-port)))
(define (read-interaction)
(with-handlers ([exn:fail? values])
((current-read-interaction) (object-name in) in))) ;[^1]
(match (read-interaction)
[(? eof-object?) (sync in)] ;[^2]
[(? exn:fail? e) (channel-put chan e)] ;raise in other thread
[v (channel-put chan v)])
(read-interaction/put-channel))
(void (thread read-interaction/put-channel))
(define current-sync/yield (make-parameter sync)) ;see issue #326
(define (get-interaction prompt)
(match (or (sync/timeout 0.01 chan) ;see issue #311
(begin (display-prompt prompt)
((current-sync/yield) chan)))
[(? exn:fail? exn) (raise exn)]
[v v]))
(define (display-prompt str)
(flush-output (current-error-port))
(fresh-line)
(display str)
(display "> ")
(flush-output)
(zero-column!))
;; "Footnote" comments about make-prompt-read and many attempts to fix
;; issue #305.
;;
;; [^1]: datalog/lang expects each interaction to be EOF terminated.
;; This seems to be a DrRacket convention (?). We could make
;; that work here if we composed open-input-string with
;; read-line. But that would fail for valid multi-line
;; expressions in langs like racket/base e.g. "(+ 1\n2)". We
;; could have Emacs racket-repl-submit append some marker that
;; lets us know to combine multiple lines here -- but we'd have
;; to be careful to eat the marker and avoid combining lines
;; when the user is entering input for their own program that
;; uses `read-line` etc. Trying to be clever here is maybe not
;; smart. I _think_ the safest thing is for each lang like
;; datalog to implement current-read-interaction like it says on
;; the tin -- it can parse just one expression/statement from a
;; normal, "infinite" input port; if that means the lang parser
;; has to be tweaked for a single-expression/statement mode of
;; usage, so be it.
;;
;; [^2]: The eof-object? clause is here only for datalog/lang
;; configure-runtime.rkt. Its `the-read` returns eof if
;; char-ready? is false. WAT. Why doesn't it just block like a
;; normal read-interaction handler? Catch this and wait for more
;; input to be available before calling it again.