Subject: bug#21609: bug#24837: 26.0.50; term.el: In char mode, displayed and executed commands can differ

From: Phil Sainty <[email-masked]>
Date: Sun, 3 Sep 2017 14:58:23 +1200

On 03/09/17 02:14, Eli Zaretskii wrote:
> This bug is currently one of those marked to block the release of
> Emacs 26.1.  Given that it existed for quite some time, I tend to
> remove the blocking status, but if someone has practical ideas how
> to fix this, I think we should do that now.

Well here's a starter for discussion.

I've performed only cursory testing, but at first glance this seems
to do the trick, so I'll see what other people think...

Firstly I'm using Philipp Stephani's suggestion that the buffer be
read-only in `term-char-mode' (and removing that in `term-line-mode';
this code doesn't attempt to remember the pre-existing states if
the user had changed it manually).  The default term process filter
`term-emulate-terminal' then binds `buffer-read-only' to nil.

Secondly, I've added a local `post-command-hook' function in
`term-char-mode' which simply moves point back to the local process
mark position.

Might such a simple approach be usable?  I'm not very familiar with
the code, so maybe there are glaring holes that I'm not seeing.

-Phil
>From edeb0ae7ae6fefe15f277029792617da030c5a9b Mon Sep 17 00:00:00 2001
From: Phil Sainty <[email-masked]>
Date: Sun, 3 Sep 2017 14:30:18 +1200
Subject: [PATCH] Avoid creating inconsistent buffer states in
 `term-char-mode'.

* lisp/term.el (term-char-mode, term-line-mode, term-emulate-terminal):
Make buffer read-only in `term-char-mode' except for the process filter,
and use post-command-hook function `term-goto-process-mark' to avoid
unexpected changes to point.

(term-goto-process-mark): New function.
---
 lisp/term.el | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/lisp/term.el b/lisp/term.el
index 12a37ca..3ba6ee7 100644
--- a/lisp/term.el
+++ b/lisp/term.el
@@ -1246,6 +1246,11 @@ term-char-mode
     (easy-menu-add term-terminal-menu)
     (easy-menu-add term-signals-menu)
 
+    ;; Don't allow changes to the buffer or to point which are not
+    ;; caused by the process filter.
+    (read-only-mode 1)
+    (add-hook 'post-command-hook #'term-goto-process-mark nil t)
+
     ;; Send existing partial line to inferior (without newline).
     (let ((pmark (process-mark (get-buffer-process (current-buffer))))
 	  (save-input-sender term-input-sender))
@@ -1265,6 +1270,8 @@ term-line-mode
 you type \\[term-send-input] which sends the current line to the inferior."
   (interactive)
   (when (term-in-char-mode)
+    (read-only-mode 0)
+    (remove-hook 'post-command-hook #'term-goto-process-mark t)
     (use-local-map term-old-mode-map)
     (term-update-mode-line)))
 
@@ -2711,6 +2718,7 @@ term-emulate-terminal
 	   count-bytes ; number of bytes
 	   decoded-substring
 	   save-point save-marker old-point temp win
+	   buffer-read-only
 	   (buffer-undo-list t)
 	   (selected (selected-window))
 	   last-win
@@ -3109,6 +3117,14 @@ term-emulate-terminal
     (when (get-buffer-window (current-buffer))
       (redisplay))))
 
+(defun term-goto-process-mark ()
+  "Move point to the current process-mark for the term buffer process.
+
+Used as a buffer-local `post-command-hook' in `term-char-mode' to
+prevent commands from putting the buffer into an inconsistent
+state by unexpectedly moving point."
+  (goto-char (process-mark (get-buffer-process (current-buffer)))))
+
 (defun term-handle-deferred-scroll ()
   (let ((count (- (term-current-row) term-height)))
     (when (>= count 0)
-- 
2.8.3