[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

OSC control sequences in comint

From: Augusto Stoffel
Subject: OSC control sequences in comint
Date: Sun, 22 Aug 2021 19:31:15 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/27.2 (gnu/linux)

I would like to suggest adding some (optional) support for "Operating
System Commands" [1] to comint.  (This is unrelated to having Emacs
talk OSC codes with the terminal inside of which its running.)

This class of control sequences serves various random purposes, for
instance adding clickable hyperlinks in terminal applications.  As an
example, compare `ls --hyperlink' in the Emacs shell versus some other
"modern" terminal emulator.

Now, I don't particularly care about those hyperlinks or any other
established use of OSC sequences, and I guess a well-made program will
avoid outputting them to Emacs, since it advertises itself as a dumb
terminal.  But OSCs seem to be good a way to create ad-hoc communication
protocols between Emacs and other programs, say to display images in a
REPL buffer (see [3] for a precedent).

Thus, concretely, the suggestion is to add the
`comint-osc-process-output' function attached below to comint.el or some
other appropriate place.  It would not be added to
`comint-output-filter-functions' by default.  The hyperlink handler is
added as an example, it might be kept or removed.

Anyway, as an illustration, after evaluating the attached file and calling

  (add-hook 'comint-output-filter-functions 'comint-osc-process-output nil t)

in a shell buffer, `ls --hyperlink' should produce clickable
hyperlinks, as it does in other capable terminals.

What do you think?

;; -*- lexical-binding: t -*-

(defvar-local comint-osc-handlers '(("8" . comint-osc-8-handler))
  "Alist of handlers for OSC escape sequences.
See `comint-osc-process-output' for details.")

(defvar-local comint-osc--marker nil)

(defun comint-osc-process-output (_)
  "Interpret OSC control sequences in comint output.

This function is intended to be added to
`comint-output-filter-functions' and interprets control sequences
of the forms


Specifically, every occurrence of such control sequences is
removed from the buffer.  Then, if the alist
`comint-osc-handlers' contains an entry <name>, the corresponding
value, which should be a function, is called with <name> and
<text> as arguments, with point where the escape sequence was
  (let ((bound (process-mark (get-buffer-process (current-buffer)))))
      (goto-char (or comint-osc--marker
                     (and (markerp comint-last-output-start)
                          (eq (marker-buffer comint-last-output-start)
      (when (eq (char-before) ?\e) (backward-char))
      (while (re-search-forward "\e]" bound t)
        (let ((pos0 (match-beginning 0))
              (code (and (re-search-forward "\\=\\([0-9A-Za-z]*\\);" bound t)
                         (match-string 1)))
              (pos1 (point)))
          (if (re-search-forward "\a\\|\e\\\\" bound t)
              (let ((text (buffer-substring-no-properties pos1 (match-beginning 
                (setq comint-osc--marker nil)
                (delete-region pos0 (point))
                (when-let ((fun (cdr (assoc-string code comint-osc-handlers))))
                  (funcall fun code text)))
            (put-text-property pos0 bound 'invisible t)
            (setq comint-osc--marker (copy-marker pos0))))))))

;;; Hyperlink handling

(defvar-local comint-osc-8--state nil)

(defun comint-osc-8-handler (_ text)
  (when comint-osc-8--state
    (let ((start (car comint-osc-8--state))
          (uri (cdr comint-osc-8--state)))
      (require 'shr)
      (shr-urlify start uri)))
  (setq comint-osc-8--state
        (and (string-match ";\\(.+\\)" text)
             (cons (point-marker) (match-string-no-properties 1 text)))))
[2]: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
[3]: https://iterm2.com/documentation-images.html

reply via email to

[Prev in Thread] Current Thread [Next in Thread]