diff --git a/lisp/doom-ui.el b/lisp/doom-ui.el index 40cb8c327..dbc83ae68 100644 --- a/lisp/doom-ui.el +++ b/lisp/doom-ui.el @@ -348,8 +348,35 @@ windows, switch to `doom-fallback-buffer'. Otherwise, delegate to original (after! comint - (setq comint-prompt-read-only t - comint-buffer-maximum-size 2048)) ; double the default + (setq-default comint-buffer-maximum-size 2048) ; double the default + + ;; Protect prompts from accidental modifications. + (setq-default comint-prompt-read-only t) + + ;; UX: Prior output in shell and comint shells (like ielm) should be + ;; read-only. Otherwise, it's trivial to make edits in visual modes (like + ;; evil's or term's term-line-mode) and leave the buffer in a half-broken + ;; state (which you have to flush out with a couple RETs, which may execute + ;; the broken text in the buffer), + (defadvice! doom--comint-protect-output-in-visual-modes-a (process _string) + :after #'comint-output-filter + ;; Adapted from https://github.com/michalrus/dotfiles/blob/c4421e361400c4184ea90a021254766372a1f301/.emacs.d/init.d/040-terminal.el.symlink#L33-L49 + (let* ((start-marker comint-last-output-start) + (end-marker (or (if process (process-mark process)) + (point-max-marker)))) + (when (< start-marker end-marker) ;; Account for some of the IELM’s wilderness. + (let ((inhibit-read-only t)) + ;; Make all past output read-only (disallow buffer modifications) + (add-text-properties comint-last-input-start (1- end-marker) '(read-only t)) + ;; Disallow interleaving. + (remove-text-properties start-marker (1- end-marker) '(rear-nonsticky)) + ;; Make sure that at `max-point' you can always append. Important for + ;; bad REPLs that keep writing after giving us prompt (e.g. sbt). + (add-text-properties (1- end-marker) end-marker '(rear-nonsticky t)) + ;; Protect fence (newline of input, just before output). + (when (eq (char-before start-marker) ?\n) + (remove-text-properties (1- start-marker) start-marker '(rear-nonsticky)) + (add-text-properties (1- start-marker) start-marker '(read-only t)))))))) (after! compile diff --git a/modules/term/eshell/config.el b/modules/term/eshell/config.el index fc5adab49..00b0130a3 100644 --- a/modules/term/eshell/config.el +++ b/modules/term/eshell/config.el @@ -95,6 +95,26 @@ You should use `set-eshell-alias!' to change this.") (setq buffer-undo-list old-undo-list) (clrhash undo-equiv-table))) + ;; UX: Prior output in eshell buffers should be read-only. Otherwise, it's + ;; trivial to make edits in visual modes (like evil's or term's + ;; term-line-mode) and leave the buffer in a half-broken state (which you + ;; must flush out with a couple RETs, which may execute the broken text in + ;; the buffer), + (add-hook! 'eshell-pre-command-hook + (defun +eshell-protect-input-in-visual-modes-h () + (when (and eshell-last-input-start + eshell-last-input-end) + (add-text-properties eshell-last-input-start + (1- eshell-last-input-end) + '(read-only t))))) + (add-hook! 'eshell-post-command-hook + (defun +eshell-protect-output-in-visual-modes-h () + (when (and eshell-last-input-end + eshell-last-output-start) + (add-text-properties eshell-last-input-end + eshell-last-output-start + '(read-only t))))) + ;; Enable autopairing in eshell (add-hook 'eshell-mode-hook #'smartparens-mode) diff --git a/modules/term/term/config.el b/modules/term/term/config.el index d3738ebeb..e2030993f 100644 --- a/modules/term/term/config.el +++ b/modules/term/term/config.el @@ -13,3 +13,26 @@ ;; Remove hscroll-margin in shells, otherwise you get jumpiness when the cursor ;; comes close to the left/right edges of the window. (setq-hook! 'term-mode-hook hscroll-margin 0) + +;; HACK: Prior output in (ansi-)term shells should be read-only. Otherwise, it's +;; trivial to make edits in visual modes (like evil's or term's +;; term-line-mode) and leave the buffer in a half-broken state (which you must +;; flush out with a couple RETs, which may execute the broken text in the +;; buffer), Note that this does not protect the prompt in (ansi-)term buffers +;; unless you set `term-prompt-regexp' buffer-locally! (e.g. with +;; `setq-hook!'). +(defadvice! +term--protect-process-output-in-visual-modes-a (&rest _) + :before #'term-line-mode + (when (term-in-char-mode) + (let* ((prompt?) + (prompt-end + (save-excursion + (goto-char (process-mark (get-buffer-process (current-buffer)))) + (or (and (not (equal term-prompt-regexp "^")) + (setq prompt? (re-search-backward term-prompt-regexp (line-beginning-position) t)) + (match-end 0)) + (line-beginning-position))))) + (with-silent-modifications + (when prompt? + (put-text-property (1- prompt-end) prompt-end 'read-only 'fence)) + (add-text-properties (point-min) prompt-end '(read-only t))))))