diff --git a/CHANGELOG.md b/CHANGELOG.md index aa9d3aa..2f23dc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Bugfix: a forward deletion of the first prompt character now works instead of dinging. `eca-chat--key-pressed-deletion` guarded the prompt boundary by rejecting any deletion at the prompt-field start regardless of direction, so `C-d` and, under evil, the `~` (`evil-invert-char`), `r`, `x` and `s` operators left the first char in place (`~` prepended the inverted char instead of replacing it). At the prompt-field start the guard now blocks only backward-deleting commands (`delete-backward-char`, `backward-kill-word`, `evil-delete-backward-char`, ...); deletions above the prompt (context/progress area) and everywhere else are unchanged. - Show the shell/git command breakdown in tool call approvals: raw command plus per-command lines colored by remember state (green saved, yellow not yet), and `Accept and remember` lists the command keys it would newly save (e.g. `git status, rg`), hiding when nothing new would be remembered. - Support TRAMP / remote hosts: the eca server starts on the remote host, the binary is resolved from the remote `PATH`, and paths are translated both ways using mappings derived from TRAMP workspace folders (explicit `eca-local-to-remote-prefix-map` entries win). Auto-install is local only: install `eca` on the remote or point `eca-custom-command` at it. #270 diff --git a/eca-chat.el b/eca-chat.el index 989cfaa..654b3b8 100644 --- a/eca-chat.el +++ b/eca-chat.el @@ -1857,6 +1857,23 @@ scrolling is suppressed so the view does not jump." (-some-> (eca-chat--prompt-context-field-ov) (overlay-start))) +(defconst eca-chat--prompt-boundary-guard-commands + '(delete-backward-char + backward-delete-char + backward-delete-char-untabify + backward-kill-word + evil-delete-backward-word + evil-delete-back-to-indentation + evil-delete-backward-char) + "Backward-deleting commands blocked at the prompt-field start. +At the start itself, `eca-chat--key-pressed-deletion' dings only for +these commands (and for a negative delete count). Forward deletions +there - `delete-char' with a positive count, as used by +`evil-invert-char' (~), `evil-replace' (r), `evil-delete-char' (x) and +`evil-substitute' (s) - remove the first prompt character and fall +through to the wrapped command. Above the prompt field the guard is +unconditional.") + (defun eca-chat--key-pressed-deletion (side-effect-fn &rest args) "Apply SIDE-EFFECT-FN with ARGS before point. Unless at the prompt field boundary. @@ -1896,9 +1913,19 @@ the prompt/context line." (setf (nth 2 args) nil) ;; do not delete prompt line passing nil argument (apply side-effect-fn args)) - ;; start of the prompt + ;; start of the prompt - keep the guard everywhere above the + ;; prompt-field start, but at the start itself block only *backward* + ;; deletions. A forward deletion there (`delete-char' from + ;; evil-invert-char ~, evil-replace r, evil-delete-char x, + ;; evil-substitute s, or C-d) removes the first prompt char, which + ;; is legitimate; everything else is unchanged. A negative count + ;; (e.g. `C-u - C-d') makes `delete-char' delete backward, so still + ;; guard that. ((and prompt-ov - (or (<= (point) (overlay-start prompt-ov)) + (or (< (point) (overlay-start prompt-ov)) + (and (= (point) (overlay-start prompt-ov)) + (or (memq this-command eca-chat--prompt-boundary-guard-commands) + (and (integerp (car args)) (< (car args) 0)))) (and (eq 'backward-kill-word this-command) (string-blank-p (buffer-substring-no-properties (overlay-start prompt-ov) (point)))))) diff --git a/test/eca-chat-test.el b/test/eca-chat-test.el index dc5447a..82d6d2b 100644 --- a/test/eca-chat-test.el +++ b/test/eca-chat-test.el @@ -133,7 +133,31 @@ does not treat the first line as metadata. Returns FN's value." (expect side-effect-called :to-be nil) (expect (eca-chat-test--prompt-text buf) :to-equal "hello"))) - (kill-buffer buf)))))) + (kill-buffer buf)))) + + (it "allows forward deletion at prompt field start" + ;; Regression: a forward `delete-char' at the prompt start - plain C-d + ;; and, under evil, `evil-invert-char' (~), `evil-replace' (r), + ;; `evil-delete-char' (x) and `evil-substitute' (s) - must remove the + ;; first prompt char. The boundary guard used to block it too, so ~ + ;; prepended the inverted char instead of replacing it in place. + (dolist (cmd '(delete-char evil-invert-char evil-replace + evil-delete-char evil-substitute)) + (let ((buf (eca-chat-test--make-prompt-buffer "hello"))) + (unwind-protect + (with-current-buffer buf + (goto-char (eca-chat--prompt-field-start-point)) + (let ((this-command cmd) + (side-effect-called nil)) + (eca-chat--key-pressed-deletion + (lambda (n &optional _) + (setq side-effect-called t) + (delete-char n)) + 1) + (expect side-effect-called :to-be t) + (expect (eca-chat-test--prompt-text buf) + :to-equal "ello"))) + (kill-buffer buf))))))) (describe "eca-chat--protect-non-prompt"