1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-05 22:20:24 -08:00

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 When non-nil, buffer sizes are shown in human readable format. The
default is nil, which retains the old format. 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 ** Smerge
*** New command 'smerge-extend' extends a conflict over surrounding lines. *** 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 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, 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 term.el needs to be changed. terminfo requires them to be stored in
an 'e' subdirectory (the first character of the file name). an 'e' subdirectory (the first character of the file name).

Binary file not shown.

View file

@ -5,7 +5,7 @@ eterm-color|Emacs term.el terminal emulator term-protocol-version 0.96,
# in this format. # in this format.
# #
# When updating this file, etc/e/eterm-color should be regenerated by # When updating this file, etc/e/eterm-color should be regenerated by
# running "make e/eterm-color" in the etc directory. # following the instructions in etc/e/README.
# Any change to this file should be done at the same time with a # Any change to this file should be done at the same time with a
# corresponding change to the TERMCAP environment variable in term.el. # corresponding change to the TERMCAP environment variable in term.el.
# Comments in term.el specify where each of these capabilities is implemented. # 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, u7=\E[6n,
smcup=\E[47h, smcup=\E[47h,
rmcup=\E[47l, rmcup=\E[47l,
smam=\E[?7h,
rmam=\E[?7l,
# rs2 may need to be added # rs2 may need to be added
eterm-direct|Emacs term.el with direct-color indexing term-protocol-version 0.96, 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.") "Current vertical row (relative to home-marker) or nil if unknown.")
(defvar term-insert-mode nil) (defvar term-insert-mode nil)
(defvar term-vertical-motion) (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 (defvar term-do-line-wrapping nil
"Last character was a graphic in the last column. "Last character was a graphic in the last column.
If next char is graphic, first move one column right 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-start (make-marker))
(setq-local term-last-input-end (make-marker)) (setq-local term-last-input-end (make-marker))
(setq-local term-last-input-match "") (setq-local term-last-input-match "")
(setq-local term-auto-margins t)
;; Always display the onscreen keyboard. ;; Always display the onscreen keyboard.
(setq-local touch-screen-display-keyboard t) (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\ :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\ :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\ :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 ;; : -undefine ic
;; don't define :te=\\E[2J\\E[?47l\\E8:ti=\\E7\\E[?47h\ ;; don't define :te=\\E[2J\\E[?47l\\E8:ti=\\E7\\E[?47h\
"Termcap capabilities supported.") "Termcap capabilities supported.")
@ -3128,19 +3133,24 @@ See `term-prompt-regexp'."
(unless term-suppress-hard-newline (unless term-suppress-hard-newline
(while (> (+ (length decoded-substring) old-column) (while (> (+ (length decoded-substring) old-column)
term-width) term-width)
(insert (substring decoded-substring 0 (let* ((here-length (- term-width old-column))
(- term-width old-column))) (to-insert (substring decoded-substring 0 here-length)))
;; Since we've enough text to fill the whole line, (setf decoded-substring (substring decoded-substring here-length))
;; delete previous text regardless of (insert to-insert)
;; `term-insert-mode's value. (setf term-current-column nil)
(delete-region (point) (line-end-position)) ;; Since we've enough text to fill the whole line,
(term-down 1 t) ;; delete previous text regardless of
(term-move-columns (- (term-current-column))) ;; `term-insert-mode's value.
(add-text-properties (1- (point)) (point) (delete-region (point) (line-end-position))
'(term-line-wrap t rear-nonsticky t)) (if term-auto-margins
(setq decoded-substring (progn
(substring decoded-substring (- term-width old-column))) (term-move-to-column 0)
(setq old-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) (insert decoded-substring)
(setq term-current-column (current-column) (setq term-current-column (current-column)
columns (- term-current-column old-column)) columns (- term-current-column old-column))
@ -3162,14 +3172,18 @@ See `term-prompt-regexp'."
(put-text-property old-point (point) (put-text-property old-point (point)
'font-lock-face term-current-face)) '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. ;; back up one column, but remember we did so.
;; Thus we emulate xterm/vt100-style line-wrapping. ;; 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) (when (eq (term-current-column) term-width)
(term-move-columns -1) (term-move-columns -1)
;; We check after ctrl sequence handling if point ;; Only set line-wrapping if auto-margins is enabled
;; was moved (and leave line-wrapping state if so). (when term-auto-margins
(setq term-do-line-wrapping (point))) ;; 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 term-current-column nil)
(setq i funny)) (setq i funny))
(pcase-exhaustive (and (<= ctl-end str-length) (aref str i)) (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 ;; We only handle control sequences with a single
;; "Final" byte (see [ECMA-48] section 5.4). ;; "Final" byte (see [ECMA-48] section 5.4).
(when (eq ctl-params-end (1- ctl-end)) (when (eq ctl-params-end (1- ctl-end))
(term-handle-ansi-escape (let* ((private (string-prefix-p "?" ctl-params))
proc (ctl-params
(mapcar ;; We don't distinguish empty params (if private (substring ctl-params 1) ctl-params)))
;; from 0 (according to [ECMA-48] we (term-handle-ansi-escape
;; should, but all commands we support proc
;; default to 0 values anyway). (mapcar ;; We don't distinguish empty params
#'string-to-number ;; from 0 (according to [ECMA-48] we
(split-string ctl-params ";")) ;; should, but all commands we support
(aref str (1- ctl-end))))) ;; 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 (?D ;; Scroll forward (apparently not documented in
;; [ECMA-48], [ctlseqs] mentions it as C1 ;; [ECMA-48], [ctlseqs] mentions it as C1
;; character "Index" though). ;; character "Index" though).
@ -3426,7 +3444,8 @@ option is enabled. See `term-set-goto-process-mark'."
(setq term-current-row 0) (setq term-current-row 0)
(setq term-current-column 1) (setq term-current-column 1)
(term--reset-scroll-region) (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) (defun term--color-as-hex (for-foreground)
"Return the current ANSI color as a hexadecimal color string. "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) - ;; Handle a character assuming (eq terminal-state 2) -
;; i.e. we have previously seen Escape followed by ?[. ;; 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 (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) ((or (eq char ?H) ;; cursor motion (terminfo: cup,home)
;; (eq char ?f) ;; xterm seems to handle this sequence too, not ;; (eq char ?f) ;; xterm seems to handle this sequence too, not
;; needed for now ;; needed for now
@ -3633,17 +3655,30 @@ color is unset in the terminal state."
((eq char ?@) ((eq char ?@)
(term-insert-spaces (max 1 (car params)))) (term-insert-spaces (max 1 (car params))))
;; \E[?h - DEC Private Mode Set ;; \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) ((eq char ?h)
(cond ((eq (car params) 4) ;; (terminfo: smir) (cond ((eq (car params) 4) ;; (terminfo: smir)
(setq term-insert-mode t)) (setq term-insert-mode t))
((eq (car params) 47) ;; (terminfo: smcup) ((and private (eq (car params) 7)) ;; (terminfo: smam)
(term-switch-to-alternate-sub-buffer t)))) (setq term-auto-margins t))
((eq (car params) 47) ;; (terminfo: smcup)
(term-switch-to-alternate-sub-buffer t))))
;; \E[?l - DEC Private Mode Reset ;; \E[?l - DEC Private Mode Reset
((eq char ?l) ((eq char ?l)
(cond ((eq (car params) 4) ;; (terminfo: rmir) (cond ((eq (car params) 4) ;; (terminfo: rmir)
(setq term-insert-mode nil)) (setq term-insert-mode nil))
((and private (eq (car params) 7)) ;; (terminfo: rmam)
(setq term-auto-margins nil))
((eq (car params) 47) ;; (terminfo: rmcup) ((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 ;; Modified to allow ansi coloring -mm
;; \E[m - Set/reset modes, set bg/fg ;; \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))) (term-test-screen-from-input 40 12 (let ((str (make-string 30 ?a)))
(list str str)))))) (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 () (ert-deftest term-colors ()
(skip-when (memq system-type '(windows-nt ms-dos))) (skip-when (memq system-type '(windows-nt ms-dos)))
(pcase-dolist (`(,str ,expected) ansi-test-strings) (pcase-dolist (`(,str ,expected) ansi-test-strings)