branch: elpa/racket-mode
commit e8cbeff9553159f3fbe6760a119d83239eb5ff72
Author: Greg Hendershott <[email protected]>
Commit: Greg Hendershott <[email protected]>

    racket-repl: Add output filter and use for ansi-color; fixes #688
    
    Add racket-repl-output-filter-functions, which is similar to
    comint-preoutput-filter-functions but restricted to stdout and stderr
    kinds of output; it doesn't affect any other kinds like prompts,
    input, etc.
    
    Default this to use ansi-color-apply, which replaces SGR escapes with
    colors. A user could remove this to see raw SGR escapes, or change it
    to ansi-color-filer-apply to remove them.
    
    Also: Add a racket-before-run-hook item to reset the state, so that an
    escape from a previous run doesn't affect output for subsequent runs.
    
    TL;DR: We get ansi coloring, but without defects in comint like the
    coloring bleeding inappropriately into other kinds of output or
    persisting inappropriately.
---
 racket-repl.el | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 52 insertions(+), 4 deletions(-)

diff --git a/racket-repl.el b/racket-repl.el
index 6856011aef..36abf72985 100644
--- a/racket-repl.el
+++ b/racket-repl.el
@@ -22,6 +22,7 @@
 (require 'racket-visit)
 (require 'racket-cmd)
 (require 'racket-back-end)
+(require 'ansi-color)
 (require 'compile)
 (require 'easymenu)
 (require 'cl-lib)
@@ -222,7 +223,7 @@ live prompt this marker will be at `point-max'.")
       (set-marker racket--repl-output-mark (point-max)))))
 
 (defun racket--repl-prompt-mark-end ()
-  "May return when there is no live prompt."
+  "May return nil when there is no live prompt."
   (when racket--repl-prompt-mark
     (or (next-single-property-change racket--repl-prompt-mark 'racket-prompt)
         (point-max))))
@@ -248,7 +249,10 @@ live prompt this marker will be at `point-max'.")
         (remove-text-properties (point-min) (point) '(rear-nonsticky nil))
         (cl-flet*
             ((fresh-line () (unless (bolp) (newline)))
-             (insert-faced (str face) (insert (propertize str 'font-lock-face 
face))))
+             (faced (str face) (propertize str 'font-lock-face face))
+             (insert-faced (str face) (insert (faced str face)))
+             (insert-filtered (str face) (insert (racket--repl-filter-output
+                                                  (faced str face)))))
           (cl-case kind
             ((run)
              (racket--repl-delete-prompt-mark 'abandon)
@@ -302,9 +306,9 @@ live prompt this marker will be at `point-max'.")
                              (insert-faced name 'racket-repl-error-label)))
                          (newline)))))))
             ((stdout)
-             (insert-faced value 'racket-repl-stdout))
+             (insert-filtered value 'racket-repl-stdout))
             ((stderr)
-             (insert-faced value 'racket-repl-stderr))
+             (insert-filtered value 'racket-repl-stderr))
             (otherwise
              (fresh-line)
              (insert-faced value 'racket-repl-message))))
@@ -341,6 +345,48 @@ live prompt this marker will be at `point-max'.")
           (goto-char racket--repl-output-mark)
           (when win (set-window-point win racket--repl-output-mark)))))))
 
+(defvar racket-repl-output-filter-functions (list #'ansi-color-apply)
+  "List of functions to call before inserting stdout/stderr output.
+
+Similar to `comint-preoutput-filter-functions', but limited to
+stdout/stderr kinds of output.
+
+Each function gets one argument, a string propertized by default
+with a face for stdout or stderr. It should return a string to
+insert instead. The functions are composed.
+
+You can use `add-hook' to add functions to this list either
+globally or locally.
+
+If the function uses state that should be reset between runs, do
+that via `racket-before-run-hook'; for example see
+`racket-ansi-color-context-reset'.")
+
+;; Because we default `racket-repl-output-filter-functions' to
+;; `ansi-color-apply', we want to reset its state for a REPL before
+;; every run. Although we could hard-code that, use the before-run
+;; hook to set an example for users.
+(defun racket-ansi-color-context-reset ()
+  (with-racket-repl-buffer
+    (setq-local ansi-color-context nil)))
+(add-hook 'racket-before-run-hook #'racket-ansi-color-context-reset)
+
+(defun racket--repl-filter-output (string)
+  ;; Beause there is no run-hooks-xxx variant equivalent to function
+  ;; composition, we borrow the equivalent code from comint, which
+  ;; also handles the wrinkle of buffer-local values.
+  (let ((functions racket-repl-output-filter-functions))
+    (while (and functions string)
+      (if (eq (car functions) t)
+          (let ((functions
+                 (default-value 'racket-repl-output-filter-functions)))
+            (while (and functions string)
+              (setq string (funcall (car functions) string))
+              (setq functions (cdr functions))))
+        (setq string (funcall (car functions) string)))
+      (setq functions (cdr functions))))
+  string)
+
 (defun racket--repl-call-with-value-and-input-ranges (from upto proc)
   "Call PROC with sub-ranges of FROM..UPTO, saying whether each
 is a value or input since `racket--repl-run-mark'."
@@ -367,6 +413,8 @@ is a value or input since `racket--repl-run-mark'."
           (funcall proc from (min pos upto) in)
           (setq from pos)))))))
 
+;;; Submit
+
 (defalias 'racket-repl-eval-or-newline-and-indent #'racket-repl-submit)
 
 (defvar-local racket-repl-submit-function nil)

Reply via email to