mirror of
https://github.com/masscollaborationlabs/emacs.git
synced 2025-07-03 10:53:23 +00:00
ansi-osc.el: Use marker (bug#78184)
* lisp/ansi-osc.el (ansi-osc-apply-on-region) (ansi-osc-filter-region): Use marker to properly handle unfinished escape sequence. * test/lisp/ansi-osc-tests.el (ansi-osc-tests--strings) (ansi-osc-tests-apply-region-no-handlers) (ansi-osc-tests-apply-region-no-handlers-multiple-calls) (ansi-osc-tests-filter-region) (ansi-osc-tests-filter-region-with-multiple-calls): Cover bug#78184.
This commit is contained in:
parent
32d911cddf
commit
6f8cee0331
2 changed files with 97 additions and 40 deletions
|
@ -35,18 +35,32 @@
|
|||
|
||||
;;; Code:
|
||||
|
||||
(defconst ansi-osc-control-seq-regexp
|
||||
;; See ECMA 48, section 8.3.89 "OSC - OPERATING SYSTEM COMMAND".
|
||||
"\e\\][\x08-\x0D]*[\x20-\x7E]*\\(\a\\|\e\\\\\\)"
|
||||
"Regexp matching an OSC control sequence.")
|
||||
;; According to ECMA 48, section 8.3.89 "OSC - OPERATING SYSTEM COMMAND"
|
||||
;; OSC control sequences match:
|
||||
;; "\e\\][\x08-\x0D]*[\x20-\x7E]*\\(\a\\|\e\\\\\\)"
|
||||
|
||||
(defvar-local ansi-osc--marker nil
|
||||
"Marker pointing to the start of an escape sequence.
|
||||
Used by `ansi-osc-filter-region' and `ansi-osc-apply-on-region' to store
|
||||
position of an unfinished escape sequence, for the complete sequence to
|
||||
be handled in next call.")
|
||||
|
||||
(defun ansi-osc-filter-region (begin end)
|
||||
"Filter out all OSC control sequences from region between BEGIN and END."
|
||||
(save-excursion
|
||||
(goto-char begin)
|
||||
;; Delete escape sequences.
|
||||
(while (re-search-forward ansi-osc-control-seq-regexp end t)
|
||||
(delete-region (match-beginning 0) (match-end 0)))))
|
||||
"Filter out all OSC control sequences from region between BEGIN and END.
|
||||
When an unfinished escape sequence is found, the start position is saved
|
||||
to `ansi-osc--marker'. Later call will override BEGIN with the position
|
||||
pointed by `ansi-osc--marker'."
|
||||
(let ((end-marker (copy-marker end)))
|
||||
(save-excursion
|
||||
(goto-char (or ansi-osc--marker begin))
|
||||
(when (eq (char-before) ?\e) (backward-char))
|
||||
(while (re-search-forward "\e]" end-marker t)
|
||||
(let ((pos0 (match-beginning 0)))
|
||||
(if (re-search-forward
|
||||
"\\=[\x08-\x0D]*[\x20-\x7E]*\\(\a\\|\e\\\\\\)"
|
||||
end-marker t)
|
||||
(delete-region pos0 (point))
|
||||
(setq ansi-osc--marker (copy-marker pos0))))))))
|
||||
|
||||
(defvar-local ansi-osc-handlers '(("2" . ansi-osc-window-title-handler)
|
||||
("7" . ansi-osc-directory-tracker)
|
||||
|
@ -54,10 +68,6 @@
|
|||
"Alist of handlers for OSC escape sequences.
|
||||
See `ansi-osc-apply-on-region' for details.")
|
||||
|
||||
(defvar-local ansi-osc--marker nil)
|
||||
;; The function `ansi-osc-apply-on-region' can set `ansi-osc--marker'
|
||||
;; to the start position of an escape sequence without termination.
|
||||
|
||||
(defun ansi-osc-apply-on-region (begin end)
|
||||
"Interpret OSC escape sequences in region between BEGIN and END.
|
||||
This function searches for escape sequences of the forms
|
||||
|
@ -65,29 +75,33 @@ This function searches for escape sequences of the forms
|
|||
ESC ] command ; text BEL
|
||||
ESC ] command ; text ESC \\
|
||||
|
||||
Every occurrence of such escape sequences is removed from the
|
||||
buffer. Then, if `command' is a key in the alist that is the
|
||||
value of the local variable `ansi-osc-handlers', that key's
|
||||
value, which should be a function, is called with `command' and
|
||||
`text' as arguments, with point where the escape sequence was
|
||||
located."
|
||||
(save-excursion
|
||||
(goto-char (or ansi-osc--marker begin))
|
||||
(when (eq (char-before) ?\e) (backward-char))
|
||||
(while (re-search-forward "\e]" end t)
|
||||
(let ((pos0 (match-beginning 0))
|
||||
(code (and (re-search-forward "\\=\\([0-9A-Za-z]*\\);" end t)
|
||||
(match-string 1)))
|
||||
(pos1 (point)))
|
||||
(if (re-search-forward "\a\\|\e\\\\" end t)
|
||||
(let ((text (buffer-substring-no-properties
|
||||
pos1 (match-beginning 0))))
|
||||
(setq ansi-osc--marker nil)
|
||||
(delete-region pos0 (point))
|
||||
(when-let* ((fun (cdr (assoc-string code ansi-osc-handlers))))
|
||||
(funcall fun code text)))
|
||||
(put-text-property pos0 end 'invisible t)
|
||||
(setq ansi-osc--marker (copy-marker pos0)))))))
|
||||
Every occurrence of such escape sequences is removed from the buffer.
|
||||
Then, if `command' is a key in the alist that is the value of the local
|
||||
variable `ansi-osc-handlers', that key's value, which should be a
|
||||
function, is called with `command' and `text' as arguments, with point
|
||||
where the escape sequence was located. When an unfinished escape
|
||||
sequence is identified, it's hidden and the start position is saved to
|
||||
`ansi-osc--marker'. Later call will override BEGIN with the position
|
||||
pointed by `ansi-osc--marker'."
|
||||
(let ((end-marker (copy-marker end)))
|
||||
(save-excursion
|
||||
(goto-char (or ansi-osc--marker begin))
|
||||
(when (eq (char-before) ?\e) (backward-char))
|
||||
(while (re-search-forward "\e]" end-marker t)
|
||||
(let ((pos0 (match-beginning 0))
|
||||
(code (and
|
||||
(re-search-forward "\\=\\([0-9A-Za-z]*\\);" end-marker t)
|
||||
(match-string 1)))
|
||||
(pos1 (point)))
|
||||
(if (re-search-forward "\a\\|\e\\\\" end-marker t)
|
||||
(let ((text (buffer-substring-no-properties
|
||||
pos1 (match-beginning 0))))
|
||||
(setq ansi-osc--marker nil)
|
||||
(delete-region pos0 (point))
|
||||
(when-let* ((fun (cdr (assoc-string code ansi-osc-handlers))))
|
||||
(funcall fun code text)))
|
||||
(put-text-property pos0 end-marker 'invisible t)
|
||||
(setq ansi-osc--marker (copy-marker pos0))))))))
|
||||
|
||||
;; Window title handling (OSC 2)
|
||||
|
||||
|
|
|
@ -30,8 +30,7 @@
|
|||
(require 'ert)
|
||||
|
||||
(defvar ansi-osc-tests--strings
|
||||
`(
|
||||
("Hello World" "Hello World")
|
||||
`(("Hello World" "Hello World")
|
||||
|
||||
;; window title
|
||||
("Buffer \e]2;A window title\e\\content" "Buffer content")
|
||||
|
@ -44,6 +43,10 @@
|
|||
|
||||
;; hyperlink
|
||||
("\e]8;;http://example.com\e\\This is a link\e]8;;\e\\" "This is a link")
|
||||
|
||||
;; multiple sequences
|
||||
("Escape \e]2;A window title\e\\sequence followed by \e]2;unfinished sequence"
|
||||
"Escape sequence followed by \e]2;unfinished sequence")
|
||||
))
|
||||
;; Don't output those strings to stdout since they may have
|
||||
;; side-effects on the environment
|
||||
|
@ -54,4 +57,44 @@
|
|||
(with-temp-buffer
|
||||
(insert input)
|
||||
(ansi-osc-apply-on-region (point-min) (point-max))
|
||||
(should (equal (buffer-string) text))))))
|
||||
(should (equal
|
||||
(buffer-substring-no-properties
|
||||
(point-min) (point-max))
|
||||
text))))))
|
||||
|
||||
(ert-deftest ansi-osc-tests-apply-region-no-handlers-multiple-calls ()
|
||||
(let ((ansi-osc-handlers nil))
|
||||
(with-temp-buffer
|
||||
(insert
|
||||
(concat "First set the window title \e]2;A window title\e\\"
|
||||
"then change it\e]2;Another "))
|
||||
(ansi-osc-apply-on-region (point-min) (point-max))
|
||||
(let ((pos (point)))
|
||||
(insert "title\e\\, and stop.")
|
||||
(ansi-osc-apply-on-region pos (point-max)))
|
||||
(should
|
||||
(equal
|
||||
(buffer-substring-no-properties (point-min) (point-max))
|
||||
"First set the window title then change it, and stop.")))))
|
||||
|
||||
(ert-deftest ansi-osc-tests-filter-region ()
|
||||
(pcase-dolist (`(,input ,text) ansi-osc-tests--strings)
|
||||
(with-temp-buffer
|
||||
(insert input)
|
||||
(ansi-osc-filter-region (point-min) (point-max))
|
||||
(should (equal (buffer-string) text)))))
|
||||
|
||||
|
||||
(ert-deftest ansi-osc-tests-filter-region-with-multiple-calls ()
|
||||
(with-temp-buffer
|
||||
(insert
|
||||
(concat "First set the window title \e]2;A window title\e\\"
|
||||
"then change it\e]2;Another "))
|
||||
(ansi-osc-filter-region (point-min) (point-max))
|
||||
(let ((pos (point)))
|
||||
(insert "title\e\\, and stop.")
|
||||
(ansi-osc-filter-region pos (point-max)))
|
||||
(should
|
||||
(equal
|
||||
(buffer-string)
|
||||
"First set the window title then change it, and stop."))))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue