diff --git a/doc/racket-mode.texi b/doc/racket-mode.texi index ce14befa..21da5f5b 100644 --- a/doc/racket-mode.texi +++ b/doc/racket-mode.texi @@ -3557,7 +3557,15 @@ restart by using @code{racket-start-back-end}. @node racket-pretty-print @subsection racket-pretty-print -Use pretty-print instead of print in REPL@? +Use pretty-print instead of plain print? + +When true, before each run set global-port-print-handler to use +pretty-print from racket/pretty, which is suitable for +s-expressions. + +Note: A configure-runtime submodule might replace this initial +value with its own global port print handler -- for example to +implement printing a non-s-expression syntax. @node racket-repl-command-file @subsection racket-repl-command-file @@ -4417,7 +4425,7 @@ Face @ref{racket-repl-mode} uses for prompts. @node racket-repl-value @subsection racket-repl-value -Face @ref{racket-repl-mode} uses for values output by current-print. +Face @ref{racket-repl-mode} uses for @code{print}-ed values. @node racket-repl-error-message @subsection racket-repl-error-message diff --git a/racket-custom.el b/racket-custom.el index 74c0d012..4b899ee9 100644 --- a/racket-custom.el +++ b/racket-custom.el @@ -398,7 +398,15 @@ restart by using `racket-start-back-end'." :safe #'booleanp) (defcustom racket-pretty-print t - "Use pretty-print instead of print in REPL?" + "Use pretty-print instead of plain print? + +When true, before each run set global-port-print-handler to use +pretty-print from racket/pretty, which is suitable for +s-expressions. + +Note: A configure-runtime submodule might replace this initial +value with its own global port print handler -- for example to +implement printing a non-s-expression syntax." :type 'boolean :safe #'booleanp) @@ -800,7 +808,7 @@ See the variable `racket-browse-url-function'.") (defface-racket racket-repl-value '((t (:inherit font-lock-constant-face))) - "Face `racket-repl-mode' uses for values output by current-print.") + "Face `racket-repl-mode' uses for `print'-ed values.") (defface-racket racket-repl-error-message '((t (:inherit error))) diff --git a/racket/elisp.rkt b/racket/elisp.rkt index ab100000..fcc872c5 100644 --- a/racket/elisp.rkt +++ b/racket/elisp.rkt @@ -77,7 +77,7 @@ (? symbol? v) (? string? v)) (write v)] [(? bytes? bstr) (write (bytes->string/utf-8 bstr))] ; ??? - [v (eprintf "elisp-write can't write Racket value ~v\n" v) + [v (eprintf "elisp-write can't write Racket value ~s\n" v) (void)])) (module+ test diff --git a/racket/image.rkt b/racket/image.rkt index 8503d469..7f30efa0 100644 --- a/racket/image.rkt +++ b/racket/image.rkt @@ -18,24 +18,35 @@ (define use-svg? #t) (define (set-use-svg?! v) (set! use-svg? v)) -(define (convert-image v) +;; For a given value, pretty-print-size-hook can be called multiple +;; times (!) followed once by pretty-print-print-hook. So because +;; convert-and-save does non-trivial work, we cache. +(define ht (make-weak-hasheq)) ;weak because #624 + +(define (convert-image v #:remove-from-cache? [remove? #f]) (and (convertible? v) - ;; Rationale for the order here: - ;; - ;; - Try bounded before unbounded flavors. Because we want - ;; accurate image width, if available, for pretty-printing. - ;; - ;; - Within each flavor: Try svg (if this Emacs can use it) - ;; before png. Because space. - (let ([fmts/exts (if use-svg? - '((svg-bytes+bounds "svg") - (png-bytes+bounds "png") - (svg-bytes "svg") - (png-bytes "png")) - '((png-bytes+bounds "png") - (png-bytes "png")))]) - (for/or ([fmt/ext (in-list fmts/exts)]) - (apply convert-and-save v fmt/ext))))) + (begin0 (hash-ref! ht v + (λ () (raw-convert-image v))) + (when remove? + (hash-remove! ht v))))) + +(define (raw-convert-image v) + ;; Rationale for the order here: + ;; + ;; - Try bounded before unbounded flavors. Because we want + ;; accurate image width, if available, for pretty-printing. + ;; + ;; - Within each flavor: Try svg (if this Emacs can use it) + ;; before png. Because space. + (define fmts/exts (if use-svg? + '((svg-bytes+bounds "svg") + (png-bytes+bounds "png") + (svg-bytes "svg") + (png-bytes "png")) + '((png-bytes+bounds "png") + (png-bytes "png")))) + (for/or ([fmt/ext (in-list fmts/exts)]) + (apply convert-and-save v fmt/ext))) (define (convert-and-save v fmt ext) (define (default-width _) 4096) diff --git a/racket/main.rkt b/racket/main.rkt index af54f915..62841cbf 100644 --- a/racket/main.rkt +++ b/racket/main.rkt @@ -41,7 +41,7 @@ [(vector "--do-not-use-svg") (set-use-svg?! #f)] [v (error '|Racket Mode back end| - "Bad command-line arguments:\n~v\n" v)]) + "Bad command-line arguments:\n~s\n" v)]) ;; Save original current-{input output}-port to give to ;; command-server-loop for command I/O. diff --git a/racket/package.rkt b/racket/package.rkt index c8a74666..e64cba89 100644 --- a/racket/package.rkt +++ b/racket/package.rkt @@ -89,7 +89,7 @@ (for/list ([p (in-list (get-pkg-modules name (pkg-catalog p) (pkg-checksum p)))]) (match p [`(lib ,path) path] - [other (format "~v" other)])) + [other (format "~s" other)])) stringstring/utf-8 could fail if we happen to read -;; only some of a multi byte utf-8 sequence; issue #715. In that case, -;; read more bytes and try decoding again. If the buffer is full, grow -;; it just a little. - -(define (make-value-pipe) - (make-pipe-with-specials)) - -(define (drain-value-pipe in out) - (flush-output out) - (close-output-port out) - (let loop ([buffer (make-bytes 8192)] - [buffer-read-pos 0]) - (match (read-bytes-avail! buffer in buffer-read-pos) - ;; When read-bytes-avail! returns 0, it means there are bytes - ;; available to read but no buffer space remaining. Grow buffer - ;; by 8 bytes; 4 bytes max utf-8 sequence plus margin. - [0 - (loop (bytes-append buffer (make-bytes 8)) - (bytes-length buffer))] - [(? exact-nonnegative-integer? len) - (match (safe-bytes->string/utf-8 buffer len) - [#f - (loop buffer (+ buffer-read-pos len))] - [(? string? s) - (repl-output-value s) - (loop buffer 0)])] - [(? procedure? read-special) - ;; m-p-w-specials ignores the position arguments so just pass - ;; something satisfying the contract. - (define v (read-special #f #f #f 1)) - (repl-output-value-special v) - (loop buffer 0)] - [(? eof-object?) (void)]))) - -(define (safe-bytes->string/utf-8 buffer len) - (with-handlers ([exn:fail:contract? (λ _ #f)]) - (bytes->string/utf-8 buffer #f 0 len))) - -;; pretty-print uses separate size and print hooks -- and the size -;; hook can even be called more than once per object. Avoid calling -;; convert-image two (or more!) times per object. That could be slow -;; for large images; furthermore each call creates a temp file. -;; -;; Instead: Call convert-image once in the size hook, storing the -;; result in a hash-table for use across later calls to the size -;; and/or print hook. Remove in the print hook. -;; -;; (Note: Although I had tried using the pre-print and post-print -;; hooks, they seemed to be called inconsistently.) -;; -;; Also: "The print-hook procedure is applied to a value for printing + (if (repl-output-print-value?) + (parameterize ([print-syntax-width +inf.0] + [pretty-print-columns columns] + [pretty-print-size-hook (size-hook pixels/char)] + [pretty-print-print-hook print-hook]) + (pretty-print v out depth #:newline? #f)) + (pretty-print v out depth #:newline? #f)))) + racket-mode-pretty-global-port-print-handler) + +;; Return char width of convertible image. +(define ((size-hook pixels/char) value _display? _port) + (match (convert-image value) ;caches + [(cons _path-name pixel-width) + (inexact->exact + (ceiling + (/ pixel-width pixels/char)))] + [#f #f])) + +;; Note: "The print-hook procedure is applied to a value for printing ;; when the sizing hook (see pretty-print-size-hook) returns an ;; integer size for the value." i.e. But not called otherwise. - -(define ht (make-weak-hasheq)) ;weak because #624 - -(define (make-pp-size-hook pixels/char) - (define (racket-mode-size-hook value display? port) - (define (not-found) - (match (convert-image value) - [(cons path-name pixel-width) - (define char-width (inexact->exact (ceiling (/ pixel-width pixels/char)))) - (cons path-name char-width)] - [#f #f])) - (match (hash-ref! ht value not-found) - [(cons _path-name char-width) char-width] - [#f #f])) - racket-mode-size-hook) - -;; Only called if size-hook returned an integer size. -(define (make-pp-print-hook) - (define orig (pretty-print-print-hook)) - (define (racket-mode-print-hook value display? port) - (match (hash-ref ht value #f) - [(cons path-name _char-width) - (hash-remove! ht value) - (write-special (cons 'image path-name))] - [_ ;shouldn't happen, but... - (orig value display? port)])) - racket-mode-print-hook) +(define (print-hook value _display? port) + (match (convert-image value #:remove-from-cache? #t) + [(cons path-name _pixel-width) + (write-special (cons 'image path-name) port)])) diff --git a/racket/repl-output.rkt b/racket/repl-output.rkt index df50da60..97deaa6e 100644 --- a/racket/repl-output.rkt +++ b/racket/repl-output.rkt @@ -5,6 +5,8 @@ (require racket/async-channel racket/match + (only-in racket/port make-pipe-with-specials) + "image.rkt" "repl-session.rkt") (provide repl-output-channel @@ -18,7 +20,8 @@ make-repl-output-manager make-repl-output-port make-repl-error-port - repl-error-port?) + repl-error-port? + repl-output-print-value?) ;;; REPL output @@ -29,7 +32,7 @@ ;; Instead we want structured output -- distinctly separated: ;; - current-output-port ;; - current-error-port -;; - current-print values +;; - printed values ;; - strings ;; - image files ;; - prompts @@ -151,14 +154,24 @@ (define (repl-output-exit) (repl-output 'exit "REPL session ended")) -;; For current-print (define (repl-output-value v) (repl-output 'value v)) (define (repl-output-value-special v) (repl-output 'value-special v)) -;; Output port wrappers around repl-output: +;;; Output port wrappers around repl-output: + +(define (make-repl-port kind) + (define name (format "racket-mode-repl-~a" kind)) + (define (write-out bstr start end non-block? breakable?) + (repl-output kind (subbytes bstr start end)) + (- end start)) + (define close void) + (make-output-port name + repl-output-channel + write-out + close)) ;; Tuck the port in a struct just for a simple, reliable ;; repl-error-port? predicate. @@ -167,19 +180,82 @@ (define (make-repl-error-port) (repl-error-port (make-repl-port 'stderr))) -;; And do same for this, just for conistency. +;; And do same for this, just for consistency. (struct repl-output-port (p) #:property prop:output-port 0) (define (make-repl-output-port) - (repl-output-port (make-repl-port 'stdout))) + (define out (repl-output-port (make-repl-port 'stdout))) + (port-print-handler out (make-value-pipe-handler)) + out) -(define (make-repl-port kind) - (define name (format "racket-mode-repl-~a" kind)) - (define (write-out bstr start end non-block? breakable?) - (repl-output kind (subbytes bstr start end)) - (- end start)) - (define close void) - (make-output-port name - repl-output-channel - write-out - close)) +;; A flag for our global-port-print-handler (see print.rkt) that it's +;; being used for a REPL output port and that it should convert +;; images (which isn't appropriate to do for ports generally). +(define repl-output-print-value? (make-parameter #f)) + +;; We want to avoid many calls to repl-output-value with short +;; strings. This can happen for example with pretty-print, which does +;; a print for each value within a list, plus for each space and +;; newline, etc. +;; +;; Use a pipe of unlimited size to accumulate all the printed bytes +;; and specials. Finally drain it using read-bytes-avail! to +;; consolidate runs of bytes (interrupted only by specials, if any) up +;; to a fixed buffer size. +;; +;; One wrinkle: bytes->string/utf-8 could fail if we happen to read +;; only some of a multi byte utf-8 sequence; issue #715. In that case, +;; read more bytes and try decoding again. If the buffer is full, grow +;; it just a little. + +(define (make-value-pipe-handler) + ;; Account for recursive calls to us, e.g. when the global port + ;; print handler does pretty printing. + (define outermost? (make-parameter #t)) + (define-values (pin pout) (make-value-pipe)) + (define (handler v _out [depth 0]) + (parameterize ([outermost? #f] + [repl-output-print-value? #t]) + (match (convert-image v #:remove-from-cache? #t) + [(cons path-name _pixel-width) + (write-special (cons 'image path-name) pout)] + [#f + ((global-port-print-handler) v pout depth)])) + (when (outermost?) + (drain-value-pipe pin pout) + (set!-values (pin pout) (make-value-pipe)))) + handler) + +(define (make-value-pipe) + (make-pipe-with-specials)) + +(define (drain-value-pipe in out) + (flush-output out) + (close-output-port out) + (let loop ([buffer (make-bytes 8192)] + [buffer-read-pos 0]) + (match (read-bytes-avail! buffer in buffer-read-pos) + ;; When read-bytes-avail! returns 0, it means there are bytes + ;; available to read but no buffer space remaining. Grow buffer + ;; by 8 bytes: 4 bytes max utf-8 sequence plus margin. + [0 + (loop (bytes-append buffer (make-bytes 8)) + (bytes-length buffer))] + [(? exact-nonnegative-integer? len) + (match (safe-bytes->string/utf-8 buffer len) + [#f + (loop buffer (+ buffer-read-pos len))] + [(? string? s) + (repl-output-value s) + (loop buffer 0)])] + [(? procedure? read-special) + ;; m-p-w-specials ignores the position arguments so just pass + ;; something satisfying the contract. + (define v (read-special #f #f #f 1)) + (repl-output-value-special v) + (loop buffer 0)] + [(? eof-object?) (void)]))) + +(define (safe-bytes->string/utf-8 buffer len) + (with-handlers ([exn:fail:contract? (λ _ #f)]) + (bytes->string/utf-8 buffer #f 0 len))) diff --git a/racket/repl-session.rkt b/racket/repl-session.rkt index 014be91f..48b38ab6 100644 --- a/racket/repl-session.rkt +++ b/racket/repl-session.rkt @@ -42,7 +42,7 @@ (current-submissions) maybe-mod (current-namespace))) - (log-racket-mode-debug @~a{(set-session! @~v[sid] @~v[maybe-mod]) => sessions: @~v[sessions]})) + (log-racket-mode-debug @~a{(set-session! @~s[sid] @~s[maybe-mod]) => sessions: @~s[sessions]})) (define (remove-session! sid) (hash-remove! sessions sid) diff --git a/racket/repl.rkt b/racket/repl.rkt index 7c964113..4fc1882c 100644 --- a/racket/repl.rkt +++ b/racket/repl.rkt @@ -118,7 +118,7 @@ (-> list? (listof (listof symbol?)) number? elisp-bool/c number? number? context-level? list? (listof path-string?) list?) (unless (current-repl-msg-chan) - (error 'run "current-repl-msg-chan was #f; current-session-id=~v" + (error 'run "current-repl-msg-chan was #f; current-session-id=~s" (current-session-id))) (define mod-path (match what @@ -164,7 +164,7 @@ ;;; REPL sessions (define ((repl-manager-thread-thunk session-id ready-ch)) - (log-racket-mode-info "starting repl session ~v" session-id) + (log-racket-mode-info "starting repl session ~s" session-id) ;; Make pipe for user program input (as distinct form repl-submit ;; input). (parameterize* ([current-session-id session-id] @@ -216,12 +216,13 @@ (define (repl-thunk) ;; Command line arguments (current-command-line-arguments cmd-line-args) - ;; Set ports, current-print handler, and output handlers + ;; Set ports (current-input-port user-pipe-in) (current-output-port (make-repl-output-port)) (current-error-port (make-repl-error-port)) - (current-print (make-racket-mode-print-handler pretty-print? columns pixels/char)) - (set-output-handlers) + (when pretty-print? + (global-port-print-handler + (make-pretty-global-port-print-handler columns pixels/char))) ;; Record as much info about our session as we can, before ;; possibly entering module->namespace. (set-session! (current-session-id) maybe-mod) @@ -294,7 +295,7 @@ [`(input ,bstr) (write-bytes bstr user-pipe-out) (get-message)] ['exit (our-exit)] - [v (log-racket-mode-warning "ignoring unknown repl-msg-chan message: ~v" v) + [v (log-racket-mode-warning "ignoring unknown repl-msg-chan message: ~s" v) (get-message)]))) (define (make-prompt-read maybe-mod) @@ -358,21 +359,3 @@ (unless (memq '#%top-interaction (namespace-mapped-symbols)) (repl-output-message "Because the language used by this module provides no #%top-interaction\n you will be unable to evaluate expressions here in the REPL."))) - -;;; Output handlers; see issues #381 #397 - -;; These are plain procedures not parameters. Therefore to reset them -;; for each user program run, we must call them each time with the -;; original value. What original value? It suffices to use the value -;; in effect when this back end starts, i.e. the default -;; port-xxx-handler. - -(define the-default-output-handlers - (for/hash ([get/set (in-list (list port-write-handler - port-display-handler - port-print-handler))]) - (values get/set (get/set (current-output-port))))) - -(define (set-output-handlers) - (for ([(get/set v) (in-hash the-default-output-handlers)]) - (get/set (current-output-port) v))) diff --git a/racket/util.rkt b/racket/util.rkt index 00227d4b..70990dbf 100644 --- a/racket/util.rkt +++ b/racket/util.rkt @@ -51,7 +51,7 @@ (define (time-apply/log what proc args) (define-values (vs cpu real gc) (time-apply proc args)) - (define (fmt n) (~v #:align 'right #:min-width 4 n)) + (define (fmt n) (~s #:align 'right #:min-width 4 n)) (log-racket-mode-debug "~a cpu | ~a real | ~a gc :: ~a" (fmt cpu) (fmt real) (fmt gc) what) (apply values vs))