Add auto-margin enable/disable to term

* test/lisp/term-tests.el (term-line-wrap-no-auto-margins): add test

* lisp/term.el (term-auto-margins): new variable
(term-mode): documentation
(term-termcap-format): mention auto-margins flag
(term-emulate-terminal): support it
(term-reset-terminal): reset it
(term-handle-ansi-escape): notice it

* etc/e/eterm-color.ti: add auto margin capability

* etc/e/README: fix build documentation

* etc/NEWS: mention auto-margins
This commit is contained in:
Daniel Colascione 2025-03-02 15:53:17 -05:00
parent 83e2e5e24b
commit 13b1436d97
7 changed files with 115 additions and 38 deletions

View file

@ -488,6 +488,19 @@ content.
When non-nil, buffer sizes are shown in human readable format. The
default is nil, which retains the old format.
** Term
*** The terminal emulator now supports auto-margins control.
Term mode now handles DECAWM escape sequences that control whether text
automatically wraps at the right margin:
- \e[?7h enables auto-margins (default)
- \e[?7l disables auto-margins
When auto-margins is disabled, characters that would go beyond the right margin
are discarded, which matches the behavior of physical terminals and other
terminal emulators. Control sequences and escape sequences are still processed
correctly regardless of margin position.
** Smerge
*** New command 'smerge-extend' extends a conflict over surrounding lines.

View file

@ -7,6 +7,9 @@ version. If it is necessary, use:
tic -o ../ ./eterm-color.ti
(Sometimes tic puts output in etc/65 instead of etc/e. Move it to etc/e
yourself if it does that.)
The compiled files are used by lisp/term.el, so if they are moved,
term.el needs to be changed. terminfo requires them to be stored in
an 'e' subdirectory (the first character of the file name).

Binary file not shown.

View file

@ -4,8 +4,8 @@ eterm-color|Emacs term.el terminal emulator term-protocol-version 0.96,
# copyright, constituting the only possible expression of the algorithm
# in this format.
#
# When updating this file, etc/e/eterm-color should be regenerated by
# running "make e/eterm-color" in the etc directory.
# When updating this file, etc/e/eterm-color should be regenerated by
# following the instructions in etc/e/README.
# Any change to this file should be done at the same time with a
# corresponding change to the TERMCAP environment variable in term.el.
# Comments in term.el specify where each of these capabilities is implemented.
@ -80,6 +80,8 @@ eterm-color|Emacs term.el terminal emulator term-protocol-version 0.96,
u7=\E[6n,
smcup=\E[47h,
rmcup=\E[47l,
smam=\E[?7h,
rmam=\E[?7l,
# rs2 may need to be added
eterm-direct|Emacs term.el with direct-color indexing term-protocol-version 0.96,

Binary file not shown.

View file

@ -349,6 +349,10 @@ contains saved `term-home-marker' from original sub-buffer.")
"Current vertical row (relative to home-marker) or nil if unknown.")
(defvar term-insert-mode nil)
(defvar term-vertical-motion)
(defvar term-auto-margins t
"When non-nil, terminal will automatically wrap lines at the right margin.
This can be toggled by the application using DECAWM escape sequences.")
(defvar term-do-line-wrapping nil
"Last character was a graphic in the last column.
If next char is graphic, first move one column right
@ -1148,6 +1152,7 @@ Entry to this mode runs the hooks on `term-mode-hook'."
(setq-local term-last-input-start (make-marker))
(setq-local term-last-input-end (make-marker))
(setq-local term-last-input-match "")
(setq-local term-auto-margins t)
;; Always display the onscreen keyboard.
(setq-local touch-screen-display-keyboard t)
@ -1682,7 +1687,7 @@ Using \"emacs\" loses, because bash disables editing if $TERM == emacs.")
:mk=\\E[8m:cb=\\E[1K:op=\\E[39;49m:Co#256:pa#32767\
:AB=\\E[48;5;%%dm:AF=\\E[38;5;%%dm:cr=^M\
:bl=^G:do=^J:le=^H:ta=^I:se=\\E[27m:ue=\\E[24m\
:kb=^?:kD=^[[3~:sc=\\E7:rc=\\E8:r1=\\Ec:"
:kb=^?:kD=^[[3~:sc=\\E7:rc=\\E8:r1=\\Ec:RA=\\E[?7l:SA=\\E[?7h:"
;; : -undefine ic
;; don't define :te=\\E[2J\\E[?47l\\E8:ti=\\E7\\E[?47h\
"Termcap capabilities supported.")
@ -3128,19 +3133,24 @@ See `term-prompt-regexp'."
(unless term-suppress-hard-newline
(while (> (+ (length decoded-substring) old-column)
term-width)
(insert (substring decoded-substring 0
(- term-width old-column)))
;; Since we've enough text to fill the whole line,
;; delete previous text regardless of
;; `term-insert-mode's value.
(delete-region (point) (line-end-position))
(term-down 1 t)
(term-move-columns (- (term-current-column)))
(add-text-properties (1- (point)) (point)
'(term-line-wrap t rear-nonsticky t))
(setq decoded-substring
(substring decoded-substring (- term-width old-column)))
(setq old-column 0)))
(let* ((here-length (- term-width old-column))
(to-insert (substring decoded-substring 0 here-length)))
(setf decoded-substring (substring decoded-substring here-length))
(insert to-insert)
(setf term-current-column nil)
;; Since we've enough text to fill the whole line,
;; delete previous text regardless of
;; `term-insert-mode's value.
(delete-region (point) (line-end-position))
(if term-auto-margins
(progn
(term-move-to-column 0)
(term-down 1 t)
(add-text-properties (1- (point)) (point)
'(term-line-wrap t rear-nonsticky t))
(setq old-column 0))
(term-move-columns -1)
(setf old-column (term-current-column))))))
(insert decoded-substring)
(setq term-current-column (current-column)
columns (- term-current-column old-column))
@ -3162,14 +3172,18 @@ See `term-prompt-regexp'."
(put-text-property old-point (point)
'font-lock-face term-current-face))
;; If the last char was written in last column,
;; If the last char was written in last column and auto-margins is enabled,
;; back up one column, but remember we did so.
;; Thus we emulate xterm/vt100-style line-wrapping.
;; If auto-margins is disabled, the cursor stays at the last column
;; and further output is discarded until a cursor movement occurs.
(when (eq (term-current-column) term-width)
(term-move-columns -1)
;; We check after ctrl sequence handling if point
;; was moved (and leave line-wrapping state if so).
(setq term-do-line-wrapping (point)))
;; Only set line-wrapping if auto-margins is enabled
(when term-auto-margins
;; We check after ctrl sequence handling if point
;; was moved (and leave line-wrapping state if so).
(setq term-do-line-wrapping (point))))
(setq term-current-column nil)
(setq i funny))
(pcase-exhaustive (and (<= ctl-end str-length) (aref str i))
@ -3205,15 +3219,19 @@ See `term-prompt-regexp'."
;; We only handle control sequences with a single
;; "Final" byte (see [ECMA-48] section 5.4).
(when (eq ctl-params-end (1- ctl-end))
(term-handle-ansi-escape
proc
(mapcar ;; We don't distinguish empty params
;; from 0 (according to [ECMA-48] we
;; should, but all commands we support
;; default to 0 values anyway).
#'string-to-number
(split-string ctl-params ";"))
(aref str (1- ctl-end)))))
(let* ((private (string-prefix-p "?" ctl-params))
(ctl-params
(if private (substring ctl-params 1) ctl-params)))
(term-handle-ansi-escape
proc
(mapcar ;; We don't distinguish empty params
;; from 0 (according to [ECMA-48] we
;; should, but all commands we support
;; default to 0 values anyway).
#'string-to-number
(split-string ctl-params ";"))
(aref str (1- ctl-end))
private))))
(?D ;; Scroll forward (apparently not documented in
;; [ECMA-48], [ctlseqs] mentions it as C1
;; character "Index" though).
@ -3426,7 +3444,8 @@ option is enabled. See `term-set-goto-process-mark'."
(setq term-current-row 0)
(setq term-current-column 1)
(term--reset-scroll-region)
(setq term-insert-mode nil))
(setq term-insert-mode nil)
(setq term-auto-margins t))
(defun term--color-as-hex (for-foreground)
"Return the current ANSI color as a hexadecimal color string.
@ -3569,8 +3588,11 @@ color is unset in the terminal state."
;; Handle a character assuming (eq terminal-state 2) -
;; i.e. we have previously seen Escape followed by ?[.
(defun term-handle-ansi-escape (proc params char)
(defun term-handle-ansi-escape (proc params char &optional private)
(cond
((and private (not (memq char '(?h ?l))))
;; Recognize private capabilities only for mode entry and exit
nil)
((or (eq char ?H) ;; cursor motion (terminfo: cup,home)
;; (eq char ?f) ;; xterm seems to handle this sequence too, not
;; needed for now
@ -3633,17 +3655,30 @@ color is unset in the terminal state."
((eq char ?@)
(term-insert-spaces (max 1 (car params))))
;; \E[?h - DEC Private Mode Set
;; N.B. we previously had a bug in which we'd decode \e[?<NR>h or
;; \e[?<NR>l as a command with zero in the params field and so
;; didn't recognize DEC private escape sequences. However, the
;; termcap and terminfo files had the non-? (question mark means DEC
;; private) versions, so things kind of worked anyway. To preserve
;; compatibility, we recognize both private- and non-private
;; messages for capabilities we added before we fixed the bug but
;; require the private flag for capabilities we added after.
((eq char ?h)
(cond ((eq (car params) 4) ;; (terminfo: smir)
(setq term-insert-mode t))
((eq (car params) 47) ;; (terminfo: smcup)
(term-switch-to-alternate-sub-buffer t))))
(cond ((eq (car params) 4) ;; (terminfo: smir)
(setq term-insert-mode t))
((and private (eq (car params) 7)) ;; (terminfo: smam)
(setq term-auto-margins t))
((eq (car params) 47) ;; (terminfo: smcup)
(term-switch-to-alternate-sub-buffer t))))
;; \E[?l - DEC Private Mode Reset
((eq char ?l)
(cond ((eq (car params) 4) ;; (terminfo: rmir)
(setq term-insert-mode nil))
(cond ((eq (car params) 4) ;; (terminfo: rmir)
(setq term-insert-mode nil))
((and private (eq (car params) 7)) ;; (terminfo: rmam)
(setq term-auto-margins nil))
((eq (car params) 47) ;; (terminfo: rmcup)
(term-switch-to-alternate-sub-buffer nil))))
(term-switch-to-alternate-sub-buffer nil))))
;; Modified to allow ansi coloring -mm
;; \E[m - Set/reset modes, set bg/fg

View file

@ -129,6 +129,30 @@ first line\r_next line\r\n"))
(term-test-screen-from-input 40 12 (let ((str (make-string 30 ?a)))
(list str str))))))
(ert-deftest term-line-wrap-no-auto-margins ()
(skip-when (memq system-type '(windows-nt ms-dos)))
(let* ((width 40)
(line (cl-loop for i upfrom 0 to 60
collect (+ ?a (% i 26)) into chars
finally return (apply #'string chars)))
(expected (concat (substring line 0 (1- width))
(substring line (1- (length line)))))
(rmam "\e[?7l"))
(should
(equal (term-test-screen-from-input width 12 (concat rmam line))
expected))
;; Again, but split input into chunks.
(should (equal
(term-test-screen-from-input
width 12
(cl-loop
with step = 3
with n = (length line)
for i upfrom 0 below n by step
collect (substring line i (min n (+ i step))) into parts
finally return (cons rmam parts)))
expected))))
(ert-deftest term-colors ()
(skip-when (memq system-type '(windows-nt ms-dos)))
(pcase-dolist (`(,str ,expected) ansi-test-strings)