mirror of
https://github.com/masscollaborationlabs/emacs.git
synced 2025-07-03 19:03:24 +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:
|
;;; Code:
|
||||||
|
|
||||||
(defconst ansi-osc-control-seq-regexp
|
;; According to ECMA 48, section 8.3.89 "OSC - OPERATING SYSTEM COMMAND"
|
||||||
;; See ECMA 48, section 8.3.89 "OSC - OPERATING SYSTEM COMMAND".
|
;; OSC control sequences match:
|
||||||
"\e\\][\x08-\x0D]*[\x20-\x7E]*\\(\a\\|\e\\\\\\)"
|
;; "\e\\][\x08-\x0D]*[\x20-\x7E]*\\(\a\\|\e\\\\\\)"
|
||||||
"Regexp matching an OSC control sequence.")
|
|
||||||
|
(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)
|
(defun ansi-osc-filter-region (begin end)
|
||||||
"Filter out all OSC control sequences from region between BEGIN and END."
|
"Filter out all OSC control sequences from region between BEGIN and END.
|
||||||
(save-excursion
|
When an unfinished escape sequence is found, the start position is saved
|
||||||
(goto-char begin)
|
to `ansi-osc--marker'. Later call will override BEGIN with the position
|
||||||
;; Delete escape sequences.
|
pointed by `ansi-osc--marker'."
|
||||||
(while (re-search-forward ansi-osc-control-seq-regexp end t)
|
(let ((end-marker (copy-marker end)))
|
||||||
(delete-region (match-beginning 0) (match-end 0)))))
|
(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)
|
(defvar-local ansi-osc-handlers '(("2" . ansi-osc-window-title-handler)
|
||||||
("7" . ansi-osc-directory-tracker)
|
("7" . ansi-osc-directory-tracker)
|
||||||
|
@ -54,10 +68,6 @@
|
||||||
"Alist of handlers for OSC escape sequences.
|
"Alist of handlers for OSC escape sequences.
|
||||||
See `ansi-osc-apply-on-region' for details.")
|
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)
|
(defun ansi-osc-apply-on-region (begin end)
|
||||||
"Interpret OSC escape sequences in region between BEGIN and END.
|
"Interpret OSC escape sequences in region between BEGIN and END.
|
||||||
This function searches for escape sequences of the forms
|
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 BEL
|
||||||
ESC ] command ; text ESC \\
|
ESC ] command ; text ESC \\
|
||||||
|
|
||||||
Every occurrence of such escape sequences is removed from the
|
Every occurrence of such escape sequences is removed from the buffer.
|
||||||
buffer. Then, if `command' is a key in the alist that is the
|
Then, if `command' is a key in the alist that is the value of the local
|
||||||
value of the local variable `ansi-osc-handlers', that key's
|
variable `ansi-osc-handlers', that key's value, which should be a
|
||||||
value, which should be a function, is called with `command' and
|
function, is called with `command' and `text' as arguments, with point
|
||||||
`text' as arguments, with point where the escape sequence was
|
where the escape sequence was located. When an unfinished escape
|
||||||
located."
|
sequence is identified, it's hidden and the start position is saved to
|
||||||
(save-excursion
|
`ansi-osc--marker'. Later call will override BEGIN with the position
|
||||||
(goto-char (or ansi-osc--marker begin))
|
pointed by `ansi-osc--marker'."
|
||||||
(when (eq (char-before) ?\e) (backward-char))
|
(let ((end-marker (copy-marker end)))
|
||||||
(while (re-search-forward "\e]" end t)
|
(save-excursion
|
||||||
(let ((pos0 (match-beginning 0))
|
(goto-char (or ansi-osc--marker begin))
|
||||||
(code (and (re-search-forward "\\=\\([0-9A-Za-z]*\\);" end t)
|
(when (eq (char-before) ?\e) (backward-char))
|
||||||
(match-string 1)))
|
(while (re-search-forward "\e]" end-marker t)
|
||||||
(pos1 (point)))
|
(let ((pos0 (match-beginning 0))
|
||||||
(if (re-search-forward "\a\\|\e\\\\" end t)
|
(code (and
|
||||||
(let ((text (buffer-substring-no-properties
|
(re-search-forward "\\=\\([0-9A-Za-z]*\\);" end-marker t)
|
||||||
pos1 (match-beginning 0))))
|
(match-string 1)))
|
||||||
(setq ansi-osc--marker nil)
|
(pos1 (point)))
|
||||||
(delete-region pos0 (point))
|
(if (re-search-forward "\a\\|\e\\\\" end-marker t)
|
||||||
(when-let* ((fun (cdr (assoc-string code ansi-osc-handlers))))
|
(let ((text (buffer-substring-no-properties
|
||||||
(funcall fun code text)))
|
pos1 (match-beginning 0))))
|
||||||
(put-text-property pos0 end 'invisible t)
|
(setq ansi-osc--marker nil)
|
||||||
(setq ansi-osc--marker (copy-marker pos0)))))))
|
(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)
|
;; Window title handling (OSC 2)
|
||||||
|
|
||||||
|
|
|
@ -30,8 +30,7 @@
|
||||||
(require 'ert)
|
(require 'ert)
|
||||||
|
|
||||||
(defvar ansi-osc-tests--strings
|
(defvar ansi-osc-tests--strings
|
||||||
`(
|
`(("Hello World" "Hello World")
|
||||||
("Hello World" "Hello World")
|
|
||||||
|
|
||||||
;; window title
|
;; window title
|
||||||
("Buffer \e]2;A window title\e\\content" "Buffer content")
|
("Buffer \e]2;A window title\e\\content" "Buffer content")
|
||||||
|
@ -44,6 +43,10 @@
|
||||||
|
|
||||||
;; hyperlink
|
;; hyperlink
|
||||||
("\e]8;;http://example.com\e\\This is a link\e]8;;\e\\" "This is a link")
|
("\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
|
;; Don't output those strings to stdout since they may have
|
||||||
;; side-effects on the environment
|
;; side-effects on the environment
|
||||||
|
@ -54,4 +57,44 @@
|
||||||
(with-temp-buffer
|
(with-temp-buffer
|
||||||
(insert input)
|
(insert input)
|
||||||
(ansi-osc-apply-on-region (point-min) (point-max))
|
(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