Rename prompt mode to line mode and generalize it

* eat.el: Rename prompt mode to line mode, also rename related
symbols to have 'eat-line-' or 'eat--line-' prefix.
* eat.el (eat-enable-native-shell-prompt-editing): Rename to
'eat-enable-auto-line-mode'.
* eat.el (eat--line-mode-enter-auto)
(eat--line-mode-exit-auto): New function.
* eat.el (eat--post-prompt, eat--post-cont-prompt): Call
'eat--line-mode-enter-auto' to enter line mode.
* eat.el (eat--pre-cmd): Call 'eat--line-mode-exit-auto'.
* eat.el (eat--get-shell-history): Populate input ring
unconditionally.
* eat.el (eat-emacs-mode, eat-semi-char-mode, eat-char-mode):
Call 'eat--line-mode-exit' to exit line mode.
* eat.el (eat--line-mode): Move all logic to 'eat-line-mode',
'eat--line-mode-exit' and 'eat--line-mode-exit-auto'.
* eat.el (eat-line-send): Make non-interactive.  Reset input
history cycling variables.
* eat.el (eat-line-send-input): New argument NO-NEWLINE.
* eat.el (eat-mode): Don't disable undo information recording.
* eat.el (eat--process-output-queue): Disable undo information
recording while process output.
* eat.texi (Line Mode): New section in chapter 'Input Modes'.
* eat.texi (Line Mode Integration): New section in chapter
'Shell Integration'.
* eat.texi (Native Shell Prompt Editing): Remove section.
* README.org (Usage): Avoid the term "keybinding mode", use
"input mode" instead as the manual uses it.  Document line
mode.
* README.org (NonGNU ELPA): Emacs 28 has NonGNU ELPA enabled by
default, so remove unnecessary instructions.
This commit is contained in:
Akib Azmain Turja 2023-09-22 22:25:12 +06:00
parent 3f4d4a9c36
commit 44e9a6f5de
No known key found for this signature in database
GPG key ID: 5535FCF54D88616B
4 changed files with 346 additions and 328 deletions

View file

@ -4,4 +4,5 @@
((nil . ((fill-column . 70)
(indent-tabs-mode . nil)))
(sh-mode . ((sh-basic-offset . 2)))
(makefile-mode . ((indent-tabs-mode . t))))
(makefile-mode . ((indent-tabs-mode . t)))
(".git" . ((text-mode . ((fill-column . 63))))))

View file

@ -20,12 +20,12 @@ To get the most out of Eat, you should also setup shell integration.
* Usage
To start Eat, run =M-x eat=. Eat has four keybinding modes:
To start Eat, run =M-x eat=. Eat has four input modes:
- "semi-char" mode: This is the default keybinding mode. Most keys
are bound to send the key to the terminal, except the following
keys: =C-\=, =C-c=, =C-x=, =C-g=, =C-h=, =C-M-c=, =C-u=, =C-q=,
=M-x=, =M-:=, =M-!=, =M-&= and some other keys (see the user option
- "semi-char" mode: This is the default input mode. Most keys are
bound to send the key to the terminal, except the following keys:
=C-\=, =C-c=, =C-x=, =C-g=, =C-h=, =C-M-c=, =C-u=, =C-q=, =M-x=,
=M-:=, =M-!=, =M-&= and some other keys (see the user option
~eat-semi-char-non-bound-keys~ for the complete list). The
following special keybinding are available:
@ -33,25 +33,28 @@ To start Eat, run =M-x eat=. Eat has four keybinding modes:
- =C-y=: Like `yank', but send the text to the terminal.
- =M-y=: Like `yank-pop', but send the text to the terminal.
- =C-c C-k=: Kill process.
- =C-c C-e=: Switch to "emacs" keybinding mode.
- =C-c M-d=: Switch to "char" keybinding mode.
- =C-c C-e=: Switch to "emacs" input mode.
- =C-c M-d=: Switch to "char" input mode.
- =C-c C-e=: Switch to "line" input mode.
- "emacs" mode: No special keybinding, except the following:
- =C-c C-j=: Switch to "semi-char" keybinding mode.
- =C-c M-d=: Switch to "char" keybinding mode.
- =C-c C-j=: Switch to "semi-char" input mode.
- =C-c M-d=: Switch to "char" input mode.
- =C-c C-e=: Switch to "line" input mode.
- =C-c C-k=: Kill process.
- "char" mode: All supported keys are bound to send the key to the
terminal, except =C-M-m= or =M-RET=, which is bound to switch to
"semi-char" keybinding mode.
"semi-char" input mode.
- "prompt" mode: Similar to Shell mode or Term line mode. You need to
enable shell integration to enable this feature.
- "line" mode: Similar to Comint, Shell mode and Term line mode. In
this input mode, terminal input is sent one line at once, and you
can edit input line using the usual Emacs commands.
- =C-c C-e=: Switch to "emacs" keybinding mode
- =C-c C-j=: Switch to "semi-char" keybinding mode.
- =C-c M-d=: Switch to "char" keybinding mode.
- =C-c C-e=: Switch to "emacs" input mode
- =C-c C-j=: Switch to "semi-char" input mode.
- =C-c M-d=: Switch to "char" input mode.
If you like Eshell, then there is a good news for you. Eat integrates
with Eshell. Eat has two global minor modes for Eshell:
@ -61,8 +64,9 @@ with Eshell. Eat has two global minor modes for Eshell:
- ~eat-eshell-mode~: Run Eat inside Eshell. After enabling this, you
can run full-screen terminal programs directly in Eshell. You have
the above three keybinding modes here too, except that =C-c C-k= is
not special (i.e. not bound by Eat) in "emacs" mode and "line" mode.
the above input modes here too, except line mode and that =C-c C-k=
is not special (i.e. not bound by Eat) in "emacs" mode and "line"
mode.
You can add any of these to ~eshell-load-hook~ like the following:
@ -98,13 +102,8 @@ Eat requires at least Emacs 28.1 or above.
** NonGNU ELPA
Eat is available on NonGNU ELPA. If you don't have the archive setup,
put something like the following in your init file:
#+begin_src emacs-lisp
(add-to-list 'package-archives
'("nongnu" . "https://elpa.nongnu.org/nongnu/"))
#+end_src
Eat is available on NonGNU ELPA. So you can just do
=M-x package-install RET eat RET=.
** Quelpa

483
eat.el
View file

@ -322,25 +322,22 @@ arguments, otherwise it's ignored."
:group 'eat-ui
:group 'eat-eshell)
(defcustom eat-enable-native-shell-prompt-editing nil
"Non-nil means allowing editing shell prompt using Emacs commands.
When non-nil, you can edit shell prompts with the normal Emacs editing
commands."
(defcustom eat-enable-auto-line-mode nil
"Non-nil means switch to line mode automatically on shell prompt."
:type 'boolean
:group 'eat-ui)
(defcustom eat-prompt-input-ring-size 1000
(defcustom eat-line-input-ring-size 1000
"Number of input history items to keep."
:type 'natnump
:group 'eat-ui)
(defcustom eat-prompt-move-point-for-matching-input 'after-input
(defcustom eat-line-move-point-for-matching-input 'after-input
"Controls where to place point after matching input.
\\<eat-prompt-mode-map>This influences the commands \
\\[eat-prompt-previous-matching-input-from-input] and \
\\[eat-prompt-next-matching-input-from-input].
\\<eat-line-mode-map>This influences the commands \
\\[eat-line-previous-matching-input-from-input] and \
\\[eat-line-next-matching-input-from-input].
If `after-input', point will be positioned after the input typed
by the user, but before the rest of the history entry that has
been inserted. If `end-of-line', point will be positioned at the
@ -349,14 +346,13 @@ end of the current logical (not visual) line after insertion."
(const :tag "Move to end of line" end-of-line))
:group 'eat-ui)
(defcustom eat-prompt-input-send-function
#'eat-prompt-send-default
(defcustom eat-line-input-send-function #'eat-line-send-default
"Function to send the shell prompt input.
The function is called without any argument. The buffer is narrowed
to the input. The function may modify the input but mustn't modify
the buffer restrictions. It should call
`eat-prompt-send-default' to send the final output."
`eat-line-send-default' to send the final output."
:type 'function
:group 'eat-ui)
@ -4970,8 +4966,11 @@ return \"eat-color\", otherwise return \"eat-mono\"."
(defvar eat--shell-prompt-mark-overlays nil
"List of overlay used to put marks before shell prompts.")
(defvar eat--inhibit-prompt-mode nil
"Non-nil means don't enter prompt mode.")
(defvar eat--inhibit-auto-line-mode nil
"Non-nil means don't enter line mode.")
(defvar eat--auto-line-mode-prev-mode nil
"The input mode active before line mode.")
(defun eat-reset ()
"Perform a terminal reset."
@ -5081,8 +5080,39 @@ If HOST isn't the host Emacs is running on, don't do anything."
(set-process-query-on-exit-flag
(eat-term-parameter eat--terminal 'eat--process) nil)))
(defvar eat--line-mode)
(defvar eat--semi-char-mode)
(defvar eat--char-mode)
(defun eat--line-mode-enter-auto ()
"Enter line mode."
(unless (or eat--inhibit-auto-line-mode eat--line-mode)
(unless eat--line-mode
(setq eat--auto-line-mode-prev-mode
(cond (eat--semi-char-mode 'semi-char)
(eat--char-mode 'char)
(t 'emacs)))
(eat-line-mode)
;; We're entering automatically, so we should be able to exit it
;; automatically.
(setq eat--inhibit-auto-line-mode nil))))
(defun eat--line-mode-exit-auto ()
"Exit line mode."
(when (and (not eat--inhibit-auto-line-mode)
eat--auto-line-mode-prev-mode)
(pcase eat--auto-line-mode-prev-mode
('emacs (eat-emacs-mode))
('semi-char (eat-semi-char-mode))
('char (eat-char-mode)))
(setq eat--auto-line-mode-prev-mode nil)
(when (/= (eat-term-end eat--terminal) (point-max))
(eat-line-send))
(eat--line-mode -1)
(setq buffer-undo-list nil)))
(defun eat--post-prompt ()
"Put a mark in the marginal area and enter prompt mode."
"Put a mark in the marginal area and enter line mode."
(when eat-enable-shell-prompt-annotation
(let ((indicator
(if (zerop eat--shell-command-status)
@ -5127,15 +5157,13 @@ If HOST isn't the host Emacs is running on, don't do anything."
(put-text-property (1- (point)) (point)
'eat--shell-prompt-end t)))
(setq eat--shell-prompt-begin nil)
(when (and eat-enable-native-shell-prompt-editing
(not eat--inhibit-prompt-mode))
(eat--prompt-mode +1)))
(when eat-enable-auto-line-mode
(eat--line-mode-enter-auto)))
(defun eat--post-cont-prompt ()
"Enter prompt mode."
(when (and eat-enable-native-shell-prompt-editing
(not eat--inhibit-prompt-mode))
(eat--prompt-mode +1)))
"Enter line mode."
(when eat-enable-auto-line-mode
(eat--line-mode-enter-auto)))
(defun eat--correct-shell-prompt-mark-overlays (buffer)
"Correct all overlays used to add mark before shell prompt.
@ -5212,7 +5240,9 @@ BUFFER is the terminal buffer."
(setf (cadr eat--shell-prompt-mark)
(propertize
eat-shell-prompt-annotation-running-margin-indicator
'face '(eat-shell-prompt-annotation-running default)))))
'face '(eat-shell-prompt-annotation-running default))))
(when eat-enable-auto-line-mode
(eat--line-mode-exit-auto)))
(defun eat--set-cmd-status (code)
"Set CODE as the current shell command's exit status."
@ -5221,8 +5251,8 @@ BUFFER is the terminal buffer."
(setq eat--shell-command-status code)))
(defun eat--before-new-prompt ()
"Allow entering prompt mode."
(setq eat--inhibit-prompt-mode nil))
"Allow entering line mode."
(setq eat--inhibit-auto-line-mode nil))
(defun eat--get-shell-history (hist format)
"Get shell history from HIST in format FORMAT."
@ -5234,23 +5264,20 @@ BUFFER is the terminal buffer."
(setq file (ignore-errors
(decode-coding-string (base64-decode-string file)
locale-coding-system)))
(if (not eat-enable-native-shell-prompt-editing)
(eat-term-send-string eat--terminal "\e]51;e;I;0\e\\")
(if (and host file
(string= host (system-name))
(file-readable-p file))
(let ((str nil))
(eat-term-send-string eat--terminal "\e]51;e;I;0\e\\")
(with-temp-buffer
(insert-file-contents file)
(setq str (buffer-string)))
(eat--prompt-populate-input-ring str format))
(eat-term-send-string
eat--terminal
(format "\e]51;e;I;%s\e\\"
eat-prompt-input-ring-size)))))
(if (and host file
(string= host (system-name))
(file-readable-p file))
(let ((str nil))
(eat-term-send-string eat--terminal "\e]51;e;I;0\e\\")
(with-temp-buffer
(insert-file-contents file)
(setq str (buffer-string)))
(eat--line-populate-input-ring str format))
(eat-term-send-string
eat--terminal
(format "\e]51;e;I;%s\e\\" eat-line-input-ring-size))))
((pred stringp)
(eat--prompt-populate-input-ring
(eat--line-populate-input-ring
(ignore-errors
(decode-coding-string (base64-decode-string hist)
locale-coding-system))
@ -5620,6 +5647,7 @@ EVENT is the mouse event."
(let ((map (make-sparse-keymap)))
(define-key map [?\C-c ?\M-d] #'eat-char-mode)
(define-key map [?\C-c ?\C-j] #'eat-semi-char-mode)
(define-key map [?\C-c ?\C-l] #'eat-line-mode)
(define-key map [?\C-c ?\C-k] #'eat-kill-process)
(define-key map [?\C-c ?\C-p] #'eat-previous-shell-prompt)
(define-key map [?\C-c ?\C-n] #'eat-next-shell-prompt)
@ -5665,26 +5693,26 @@ EVENT is the mouse event."
map)
"Keymap for Eat char mode.")
(defvar eat-prompt-mode-map
(defvar eat-line-mode-map
(let ((map (make-sparse-keymap)))
(define-key map [?\C-c ?\C-e] #'eat-emacs-mode)
(define-key map [?\t] #'completion-at-point)
(define-key map [?\C-m] #'eat-prompt-newline)
(define-key map [?\C-d] #'eat-prompt-delchar-or-eof)
(define-key map [?\C-c ?\C-c] #'eat-prompt-send-interrupt)
(define-key map [?\C-m] #'eat-line-send-input)
(define-key map [?\C-d] #'eat-line-delchar-or-eof)
(define-key map [?\C-c ?\C-c] #'eat-line-send-interrupt)
(define-key map [?\C-c ?\s] #'newline)
(define-key map [?\M-p] #'eat-prompt-previous-input)
(define-key map [?\M-n] #'eat-prompt-next-input)
(define-key map [C-up] #'eat-prompt-previous-input)
(define-key map [C-down] #'eat-prompt-next-input)
(define-key map [?\M-r] #'eat-prompt-previous-matching-input)
(define-key map [?\C-c ?\C-r] #'eat-prompt-find-input)
(define-key map [?\M-p] #'eat-line-previous-input)
(define-key map [?\M-n] #'eat-line-next-input)
(define-key map [C-up] #'eat-line-previous-input)
(define-key map [C-down] #'eat-line-next-input)
(define-key map [?\M-r] #'eat-line-previous-matching-input)
(define-key map [?\C-c ?\C-r] #'eat-line-find-input)
(define-key map [?\C-c ?\M-r]
#'eat-prompt-previous-matching-input-from-input)
#'eat-line-previous-matching-input-from-input)
(define-key map [?\C-c ?\M-s]
#'eat-prompt-next-matching-input-from-input)
#'eat-line-next-matching-input-from-input)
map)
"Keymap for Eat native shell prompt mode.")
"Keymap for Eat line mode.")
(defvar eat--mouse-click-mode-map
(eat-term-make-keymap #'eat-self-input '(:mouse-click) nil)
@ -5720,20 +5748,10 @@ EVENT is the mouse event."
"Minor mode for mouse movement keymap."
:interactive nil)
(defvar eat--prompt-mode)
(defvar eat--prompt-mode-previous-mode)
(defun eat-emacs-mode ()
"Switch to Emacs keybindings mode."
(interactive)
(when (and eat--prompt-mode
(/= (eat-term-end eat--terminal) (point-max)))
(user-error "Can't switch to Emacs mode from prompt mode when\
input is non-empty"))
(when eat--prompt-mode
(setq eat--prompt-mode-previous-mode 'dont-restore)
(eat--prompt-mode -1)
(setq eat--inhibit-prompt-mode t))
(eat--line-mode-exit)
(eat--semi-char-mode -1)
(eat--char-mode -1)
(setq buffer-read-only t)
@ -5745,15 +5763,8 @@ EVENT is the mouse event."
(interactive)
(unless eat--terminal
(error "Process not running"))
(when (and eat--prompt-mode
(/= (eat-term-end eat--terminal) (point-max)))
(user-error "Can't switch to semi-char mode from prompt mode when\
input is non-empty"))
(setq buffer-read-only nil)
(when eat--prompt-mode
(setq eat--prompt-mode-previous-mode 'dont-restore)
(eat--prompt-mode -1)
(setq eat--inhibit-prompt-mode t))
(eat--line-mode-exit)
(eat--char-mode -1)
(eat--semi-char-mode +1)
(eat--grab-mouse nil eat--mouse-grabbing-type)
@ -5764,15 +5775,8 @@ EVENT is the mouse event."
(interactive)
(unless eat--terminal
(error "Process not running"))
(when (and eat--prompt-mode
(/= (eat-term-end eat--terminal) (point-max)))
(user-error "Can't switch to char mode from prompt mode when\
input is non-empty"))
(setq buffer-read-only nil)
(when eat--prompt-mode
(setq eat--prompt-mode-previous-mode 'dont-restore)
(eat--prompt-mode -1)
(setq eat--inhibit-prompt-mode t))
(eat--line-mode-exit)
(eat--semi-char-mode -1)
(eat--char-mode +1)
(eat--grab-mouse nil eat--mouse-grabbing-type)
@ -5824,37 +5828,38 @@ MODE should one of:
(eat--mouse-movement-mode -1))))
;;;;; Prompt Mode.
;;;;; Line Mode.
(defvar eat--prompt-mode-previous-mode nil
"The input mode active before prompt mode.")
(define-minor-mode eat--prompt-mode
"Minor mode for prompt mode."
(define-minor-mode eat--line-mode
"Minor mode for line mode."
:interactive nil
:keymap eat-prompt-mode-map
(if eat--prompt-mode
(unless eat--prompt-mode-previous-mode
(setq eat--prompt-mode-previous-mode
(cond (eat--semi-char-mode 'semi-char)
(eat--char-mode 'char)
(t 'emacs)))
(eat--semi-char-mode -1)
(eat--char-mode -1)
(eat--grab-mouse nil eat--mouse-grabbing-type)
(setq buffer-read-only nil)
(eat--prompt-reset-input-ring-vars))
(when eat--prompt-mode-previous-mode
(pcase eat--prompt-mode-previous-mode
('emacs (eat-emacs-mode))
('semi-char (eat-semi-char-mode))
('char (eat-char-mode)))
(setq eat--prompt-mode-previous-mode nil)
;; Delete the undo list so that `undo' doesn't mess up with the
;; terminal.
(setq buffer-undo-list nil))))
:keymap eat-line-mode-map)
(defun eat-prompt-send-default ()
(defun eat-line-mode ()
"Switch to line mode."
(interactive)
(eat--line-mode +1)
(eat--semi-char-mode -1)
(eat--char-mode -1)
(eat--grab-mouse nil eat--mouse-grabbing-type)
(setq buffer-read-only nil)
;; Delete the undo list so that `undo' doesn't mess up with the
;; terminal.
(setq buffer-undo-list nil)
;; Don't let auto line mode exit line mode.
(setq eat--inhibit-auto-line-mode t))
(defun eat--line-mode-exit ()
"Exit line mode, called only by interactive commands."
(when eat--line-mode
(when (/= (eat-term-end eat--terminal) (point-max))
(eat-line-send))
(eat--line-mode -1)
(setq buffer-undo-list nil)
(setq eat--inhibit-auto-line-mode t)
(setq eat--auto-line-mode-prev-mode nil)))
(defun eat-line-send-default ()
"Send shell prompt input directly to the terminal."
(eat-term-send-string eat--terminal (buffer-string))
;; If output arrives after sending the string, new output may get
@ -5863,36 +5868,40 @@ MODE should one of:
;; terminal.
(narrow-to-region (eat-term-end eat--terminal) (point-max)))
(defun eat-prompt-send ()
(defun eat-line-send ()
"Send shell prompt input to the terminal."
(interactive)
(save-excursion
(save-restriction
(narrow-to-region (eat-term-end eat--terminal) (point-max))
(funcall eat-prompt-input-send-function)
(funcall eat-line-input-send-function)
(delete-region (point-min) (point-max))
(eat--prompt-mode -1)))
(eat--line-reset-input-ring-vars)
(setq buffer-undo-list nil)))
(goto-char (eat-term-display-cursor eat--terminal)))
(defvar eat--prompt-input-ring)
(defvar eat--line-input-ring)
(defun eat-prompt-newline ()
"Send shell prompt input to the terminal, with a newline."
(interactive)
(defun eat-line-send-input (&optional no-newline)
"Send shell prompt input to the terminal.
If called without any prefix argument, or if NO-NEWLINE is nil, append
a newline to the input before sending it."
(interactive "P")
(if (not (<= (eat-term-end eat--terminal) (point)))
(call-interactively #'newline)
(unless (= (eat-term-end eat--terminal) (point-max))
(unless eat--prompt-input-ring
(setq eat--prompt-input-ring
(make-ring eat-prompt-input-ring-size)))
(ring-insert eat--prompt-input-ring
(unless eat--line-input-ring
(setq eat--line-input-ring
(make-ring eat-line-input-ring-size)))
(ring-insert eat--line-input-ring
(buffer-substring-no-properties
(eat-term-end eat--terminal) (point-max))))
(goto-char (point-max))
(insert "\n")
(eat-prompt-send)))
(unless no-newline
(goto-char (point-max))
(insert "\n"))
(eat-line-send)))
(defun eat-prompt-delchar-or-eof (arg)
(defun eat-line-delchar-or-eof (arg)
"Delete character or send shell prompt input to the terminal.
ARG is the prefix arg, passed to `delete-char' when deleting
@ -5901,15 +5910,15 @@ character."
(if (not (= (eat-term-end eat--terminal) (point-max)))
(delete-char arg)
(insert "\C-d")
(eat-prompt-send)))
(eat-line-send)))
(defun eat-prompt-send-interrupt ()
(defun eat-line-send-interrupt ()
"Clear the input and send `C-c' to the shell."
(interactive)
(delete-region (eat-term-end eat--terminal) (point-max))
(goto-char (point-max))
(insert "\C-c")
(eat-prompt-send))
(eat-line-send))
;;;;;; History.
@ -5917,40 +5926,40 @@ character."
;; The following code in this page (or section) is adapted from
;; Comint source.
(defvar eat--prompt-input-ring nil
(defvar eat--line-input-ring nil
"Ring holding the history of inputs.")
(defvar eat--prompt-input-ring-index nil
(defvar eat--line-input-ring-index nil
"Index of last matched history element.")
(defvar eat--prompt-stored-incomplete-input nil
(defvar eat--line-stored-incomplete-input nil
"Stored input for history cycling.")
(defvar eat--prompt-matching-input-from-input-string ""
(defvar eat--line-matching-input-from-input-string ""
"Input previously used to match input history.")
(defun eat--prompt-reset-input-ring-vars ()
(defun eat--line-reset-input-ring-vars ()
"Reset variable after a new shell prompt."
(setq eat--prompt-input-ring-index nil)
(setq eat--prompt-stored-incomplete-input nil)
(setq eat--prompt-matching-input-from-input-string ""))
(setq eat--line-input-ring-index nil)
(setq eat--line-stored-incomplete-input nil)
(setq eat--line-matching-input-from-input-string ""))
(defun eat--prompt-populate-input-ring (hist format)
"Populate `eat--prompt-input-ring' from HIST in format FORMAT."
(setq eat--prompt-input-ring (make-ring eat-prompt-input-ring-size))
(defun eat--line-populate-input-ring (hist format)
"Populate `eat--line-input-ring' from HIST in format FORMAT."
(setq eat--line-input-ring (make-ring eat-line-input-ring-size))
(pcase format
("bash"
(dolist (item (string-split hist "\n" 'omit-nulls))
(when (/= (aref item 0) ?#)
(ring-insert eat--prompt-input-ring item))))
(ring-insert eat--line-input-ring item))))
("zsh"
(dolist (item (string-split hist "\n" 'omit-nulls))
(ring-insert eat--prompt-input-ring
(ring-insert eat--line-input-ring
(string-trim item (rx ": " (zero-or-more digit)
?: (zero-or-more digit)
?\;)))))))
(defun eat--prompt-ask-for-regexp-arg (prompt)
(defun eat--line-ask-for-regexp-arg (prompt)
"Return list of regexp and prefix arg using PROMPT."
(let* (;; Don't clobber this.
(last-command last-command)
@ -5965,92 +5974,92 @@ character."
regexp)
(prefix-numeric-value current-prefix-arg))))
(defun eat--prompt-search-arg (arg)
(defun eat--line-search-arg (arg)
"Check point, and return ARG, or one if ARG is zero."
;; First make sure there is a ring and that we are after the
;; terminal region.
(cond ((< (point) (eat-term-end eat--terminal))
(user-error "Not at command line"))
((or (null eat--prompt-input-ring)
(ring-empty-p eat--prompt-input-ring))
((or (null eat--line-input-ring)
(ring-empty-p eat--line-input-ring))
(user-error "Empty input ring"))
((zerop arg)
;; ARG zero resets search from beginning, and uses ARG 1.
(setq eat--prompt-input-ring-index nil)
(setq eat--line-input-ring-index nil)
1)
(t
arg)))
(defun eat-prompt-restore-input ()
(defun eat-line-restore-input ()
"Restore unfinished input."
(interactive)
(when eat--prompt-input-ring-index
(eat--prompt-delete-input)
(when (> (length eat--prompt-stored-incomplete-input) 0)
(insert eat--prompt-stored-incomplete-input)
(when eat--line-input-ring-index
(eat--line-delete-input)
(when (> (length eat--line-stored-incomplete-input) 0)
(insert eat--line-stored-incomplete-input)
(message "Input restored"))
(setq eat--prompt-input-ring-index nil)))
(setq eat--line-input-ring-index nil)))
(defun eat--prompt-search-start (arg)
(defun eat--line-search-start (arg)
"Index to start a directional search, ARG indicates the direction."
(if eat--prompt-input-ring-index
(if eat--line-input-ring-index
;; If a search is running, offset by 1 in direction of ARG.
(mod (+ eat--prompt-input-ring-index (if (> arg 0) 1 -1))
(ring-length eat--prompt-input-ring))
(mod (+ eat--line-input-ring-index (if (> arg 0) 1 -1))
(ring-length eat--line-input-ring))
;; For a new search, start from end if ARG is negative, or from
;; beginning otherwise.
(if (> arg 0)
0
(1- (ring-length eat--prompt-input-ring)))))
(1- (ring-length eat--line-input-ring)))))
(defun eat--prompt-prev-input-string (arg)
(defun eat--line-prev-input-string (arg)
"Return the string ARG places along the input ring.
Moves relative to `eat--prompt-input-ring-index'."
(ring-ref eat--prompt-input-ring
(if eat--prompt-input-ring-index
(mod (+ arg eat--prompt-input-ring-index)
(ring-length eat--prompt-input-ring))
Moves relative to `eat--line-input-ring-index'."
(ring-ref eat--line-input-ring
(if eat--line-input-ring-index
(mod (+ arg eat--line-input-ring-index)
(ring-length eat--line-input-ring))
arg)))
(defun eat-prompt-previous-input (arg)
(defun eat-line-previous-input (arg)
"Cycle backwards through input history, saving input.
Negative ARG means search forward instead."
(interactive "*p")
(if (and eat--prompt-input-ring-index
(if (and eat--line-input-ring-index
;; Are we leaving the "end" of the ring?
(or (and (< arg 0) ; going down
(eq eat--prompt-input-ring-index 0))
(eq eat--line-input-ring-index 0))
(and (> arg 0) ; going up
(eq eat--prompt-input-ring-index
(1- (ring-length eat--prompt-input-ring)))))
eat--prompt-stored-incomplete-input)
(eat-prompt-restore-input)
(eat-prompt-previous-matching-input "." arg)))
(eq eat--line-input-ring-index
(1- (ring-length eat--line-input-ring)))))
eat--line-stored-incomplete-input)
(eat-line-restore-input)
(eat-line-previous-matching-input "." arg)))
(defun eat-prompt-next-input (arg)
(defun eat-line-next-input (arg)
"Cycle forwards through input history, saving input.
Negative ARG means search backward instead."
(interactive "*p")
(eat-prompt-previous-input (- arg)))
(eat-line-previous-input (- arg)))
(defun eat--prompt-prev-matching-input-str (regexp arg)
(defun eat--line-prev-matching-input-str (regexp arg)
"Return the string matching REGEXP ARG places along the input ring.
Moves relative to `eat--prompt-input-ring-index'."
(let* ((pos (eat--prompt-prev-matching-input-str-pos regexp arg)))
(if pos (ring-ref eat--prompt-input-ring pos))))
Moves relative to `eat--line-input-ring-index'."
(let* ((pos (eat--line-prev-matching-input-str-pos regexp arg)))
(if pos (ring-ref eat--line-input-ring pos))))
(defun eat--prompt-prev-matching-input-str-pos
(defun eat--line-prev-matching-input-str-pos
(regexp arg &optional start)
"Return the index matching REGEXP ARG places along the input ring.
Moves relative to START, or `eat--prompt-input-ring-index'."
(when (or (not (ring-p eat--prompt-input-ring))
(ring-empty-p eat--prompt-input-ring))
Moves relative to START, or `eat--line-input-ring-index'."
(when (or (not (ring-p eat--line-input-ring))
(ring-empty-p eat--line-input-ring))
(user-error "No history"))
(let* ((len (ring-length eat--prompt-input-ring))
(let* ((len (ring-length eat--line-input-ring))
(motion (if (> arg 0) 1 -1))
(n (mod (- (or start (eat--prompt-search-start arg)) motion)
(n (mod (- (or start (eat--line-search-start arg)) motion)
len))
(tried-each-ring-item nil)
(prev nil))
@ -6063,22 +6072,22 @@ Moves relative to START, or `eat--prompt-input-ring-index'."
(while (and (< n len) (not tried-each-ring-item)
(not (string-match regexp
(ring-ref
eat--prompt-input-ring n))))
eat--line-input-ring n))))
(setq n (mod (+ n motion) len))
;; If we have gone all the way around in this search.
(setq tried-each-ring-item (= n prev)))
(setq arg (if (> arg 0) (1- arg) (1+ arg))))
;; Now that we know which ring element to use, if we found it,
;; return that.
(when (string-match regexp (ring-ref eat--prompt-input-ring n))
(when (string-match regexp (ring-ref eat--line-input-ring n))
n)))
(defun eat--prompt-delete-input ()
(defun eat--line-delete-input ()
"Delete all input between accumulation or process mark and point."
;; Can't use kill-region as it sets `this-command'.
(delete-region (eat-term-end eat--terminal) (point-max)))
(defun eat-prompt-previous-matching-input (regexp n &optional restore)
(defun eat-line-previous-matching-input (regexp n &optional restore)
"Search backwards through input history for match for REGEXP.
\(Previous history elements are earlier commands.)
@ -6086,43 +6095,43 @@ With prefix argument N, search for Nth previous match.
If N is negative, find the next or Nth next match.
If RESTORE is non-nil, restore input in case of wrap."
(interactive (eat--prompt-ask-for-regexp-arg
(interactive (eat--line-ask-for-regexp-arg
"Previous input matching (regexp): "))
(setq n (eat--prompt-search-arg n))
(let ((pos (eat--prompt-prev-matching-input-str-pos regexp n)))
(setq n (eat--line-search-arg n))
(let ((pos (eat--line-prev-matching-input-str-pos regexp n)))
;; Has a match been found?
(if (null pos)
(user-error "Not found")
(if (and eat--prompt-input-ring-index
(if (and eat--line-input-ring-index
restore
(or (and (< n 0)
(< eat--prompt-input-ring-index pos))
(< eat--line-input-ring-index pos))
(and (> n 0)
(> eat--prompt-input-ring-index pos))))
(> eat--line-input-ring-index pos))))
;; We have a wrap; restore contents.
(eat-prompt-restore-input)
(eat-line-restore-input)
;; If leaving the edit line, save partial input.
(if (null eat--prompt-input-ring-index) ;not yet on ring
(setq eat--prompt-stored-incomplete-input
(if (null eat--line-input-ring-index) ;not yet on ring
(setq eat--line-stored-incomplete-input
(buffer-substring-no-properties
(eat-term-end eat--terminal) (point-max))))
(setq eat--prompt-input-ring-index pos)
(setq eat--line-input-ring-index pos)
(unless isearch-mode
(let ((message-log-max nil)) ; Do not write to *Messages*.
(message "History item: %d" (1+ pos))))
(eat--prompt-delete-input)
(insert (ring-ref eat--prompt-input-ring pos))))))
(eat--line-delete-input)
(insert (ring-ref eat--line-input-ring pos))))))
(defun eat-prompt-next-matching-input (regexp n)
(defun eat-line-next-matching-input (regexp n)
"Search forwards through input history for match for REGEXP.
\(Later history elements are more recent commands.)
With prefix argument N, search for Nth following match.
If N is negative, find the previous or Nth previous match."
(interactive (eat--prompt-ask-for-regexp-arg
(interactive (eat--line-ask-for-regexp-arg
"Next input matching (regexp): "))
(eat-prompt-previous-matching-input regexp (- n)))
(eat-line-previous-matching-input regexp (- n)))
(defun eat-prompt-previous-matching-input-from-input (n)
(defun eat-line-previous-matching-input-from-input (n)
"Search backwards through input history for match for current input.
\(Previous history elements are earlier commands.)
With prefix argument N, search for Nth previous match.
@ -6130,49 +6139,49 @@ If N is negative, search forwards for the -Nth following match."
(interactive "p")
(let ((opoint (point)))
(unless (memq last-command
'(eat-prompt-previous-matching-input-from-input
eat-prompt-next-matching-input-from-input))
'(eat-line-previous-matching-input-from-input
eat-line-next-matching-input-from-input))
;; Starting a new search
(setq eat--prompt-matching-input-from-input-string
(setq eat--line-matching-input-from-input-string
(buffer-substring (eat-term-end eat--terminal)
(point-max)))
(setq eat--prompt-input-ring-index nil))
(eat-prompt-previous-matching-input
(setq eat--line-input-ring-index nil))
(eat-line-previous-matching-input
(concat "^" (regexp-quote
eat--prompt-matching-input-from-input-string))
eat--line-matching-input-from-input-string))
n t)
(when (eq eat-prompt-move-point-for-matching-input 'after-input)
(when (eq eat-line-move-point-for-matching-input 'after-input)
(goto-char opoint))))
(defun eat-prompt-next-matching-input-from-input (n)
(defun eat-line-next-matching-input-from-input (n)
"Search forwards through input history for match for current input.
\(Following history elements are more recent commands.)
With prefix argument N, search for Nth following match.
If N is negative, search backwards for the -Nth previous match."
(interactive "p")
(eat-prompt-previous-matching-input-from-input (- n)))
(eat-line-previous-matching-input-from-input (- n)))
(defun eat-prompt-find-input ()
(defun eat-line-find-input ()
"Find and insert input history using minibuffer."
(declare (interactive-only t))
(interactive)
(when (or (not (ring-p eat--prompt-input-ring))
(ring-empty-p eat--prompt-input-ring))
(when (or (not (ring-p eat--line-input-ring))
(ring-empty-p eat--line-input-ring))
(user-error "No history"))
(let ((str (completing-read
"Input: "
(seq-uniq (ring-elements eat--prompt-input-ring)) nil
(seq-uniq (ring-elements eat--line-input-ring)) nil
nil (buffer-substring (eat-term-end eat--terminal)
(point-max))))
(i 0)
(pos nil))
(while (and (< i (ring-length eat--prompt-input-ring)) (not pos))
(when (equal (ring-ref eat--prompt-input-ring i) str)
(while (and (< i (ring-length eat--line-input-ring)) (not pos))
(when (equal (ring-ref eat--line-input-ring i) str)
(setq pos i))
(cl-incf i))
(when pos
(setq eat--prompt-input-ring-index pos))
(eat--prompt-delete-input)
(setq eat--line-input-ring-index pos))
(eat--line-delete-input)
(insert str)))
@ -6259,12 +6268,12 @@ END if it's safe to do so."
eat--shell-prompt-begin
eat--shell-prompt-mark
eat--shell-prompt-mark-overlays
eat--inhibit-prompt-mode
eat--prompt-mode-previous-mode
eat--prompt-input-ring
eat--prompt-input-ring-index
eat--prompt-stored-incomplete-input
eat--prompt-matching-input-from-input-string
eat--inhibit-auto-line-mode
eat--auto-line-mode-prev-mode
eat--line-input-ring
eat--line-input-ring-index
eat--line-stored-incomplete-input
eat--line-matching-input-from-input-string
eat--pending-input-chunks
eat--process-input-queue-timer
eat--pending-output-chunks
@ -6273,8 +6282,6 @@ END if it's safe to do so."
eat--shell-prompt-annotation-correction-timer))
;; This is intended; input methods don't work on read-only buffers.
(setq buffer-read-only nil)
(unless eat-enable-native-shell-prompt-editing
(setq buffer-undo-list t))
(setq eat--synchronize-scroll-function #'eat--synchronize-scroll)
(setq filter-buffer-substring-function
#'eat--filter-buffer-substring)
@ -6290,13 +6297,14 @@ END if it's safe to do so."
(:propertize
"semi-char"
help-echo "mouse-1: Switch to char mode, \
mouse-3: Switch to emacs mode"
mouse-2: Switch to line mode, mouse-3: Switch to emacs mode"
mouse-face mode-line-highlight
local-map
(keymap
(mode-line
. (keymap
(down-mouse-1 . eat-char-mode)
(down-mouse-2 . eat-line-mode)
(down-mouse-3 . eat-emacs-mode)))))
"]"))
(eat--char-mode
@ -6304,19 +6312,20 @@ mouse-3: Switch to emacs mode"
(:propertize
"char"
help-echo "mouse-1: Switch to semi-char mode, \
mouse-3: Switch to emacs mode"
mouse-2: Switch to line mode, mouse-3: Switch to emacs mode"
mouse-face mode-line-highlight
local-map
(keymap
(mode-line
. (keymap
(down-mouse-1 . eat-semi-char-mode)
(down-mouse-2 . eat-line-mode)
(down-mouse-3 . eat-emacs-mode)))))
"]"))
(eat--prompt-mode
(eat--line-mode
'("["
(:propertize
"prompt"
"line"
help-echo "mouse-1: Switch to semi char mode, \
mouse-2: Switch to emacs mode, mouse-3: Switch to char mode"
mouse-face mode-line-highlight
@ -6340,6 +6349,7 @@ mouse-3: Switch to char mode"
(mode-line
. (keymap
(down-mouse-1 . eat-semi-char-mode)
(down-mouse-2 . eat-line-mode)
(down-mouse-3 . eat-char-mode)))))
"]")))))
":%s"))
@ -6462,7 +6472,7 @@ OS's."
(let ((inhibit-read-only t)
(inhibit-modification-hooks t)
;; Don't let `undo' mess up with the terminal.
(buffer-undo-list buffer-undo-list))
(buffer-undo-list t))
(when eat--process-output-queue-timer
(cancel-timer eat--process-output-queue-timer))
(setq eat--output-queue-first-chunk-time nil)
@ -6546,9 +6556,8 @@ to it."
(setq eat--shell-prompt-begin nil)
(setq eat--shell-prompt-mark nil)
(setq eat--shell-prompt-mark-overlays nil))
(when eat--prompt-mode
(setq eat--prompt-mode-previous-mode 'dont-restore)
(eat--prompt-mode -1)
(when eat--line-mode
(eat--line-mode -1)
(delete-region (eat-term-end eat--terminal)
(point-max)))
(eat-emacs-mode)

141
eat.texi
View file

@ -364,6 +364,68 @@ From ``emacs mode'', you can switch to ``semi-char mode'' with
command @command{eshell-toggle-direct-send} is remapped to enable
``char-mode'', which is usually bound to @kbd{C-c M-d}.
@anchor{Line Mode}
@cindex line mode
@cindex mode, line
@cindex keybindings, line mode
@cindex keybinding mode, line
@cindex input mode, line
@section Line Mode
In ``line mode'', input is sent one line at a line, similar to Comint
or Shell mode (@pxref{Interactive Shell,,, emacs, GNU Emacs Manual}).
Like ``emacs mode'', you can interact with buffer as you wish; but at
the very end of the buffer, you can edit the input line with usual
Emacs commands. This is not available in Eshell.
@kindex C-c C-l @r{(``semi-char mode'')}
@kindex C-c C-l @r{(``emacs mode'')}
To enter ``line mode'', press @kbd{C-c C-l} from ``semi-char mode'' or
``emacs mode''.
@kindex @key{RET} @r{(``line mode'')}
@kindex C-c @key{SPC} @r{(``line mode'')}
@kbd{@key{RET}} sends the current input with a trailing newline, and
clear the input area for new input to be written. When called with a
prefix argument, no newline is appended before sending input. To
input a newline character without actually sending it, you can press
@kbd{C-c @key{SPC}}.
@kindex C-c C-c @r{(``line mode'')}
@kindex C-d @r{(``line mode'')}
@kbd{C-c C-c} discards the input completely and sends an interrupt to
the terminal. @kbd{C-d} deletes the character after the point, or
sends EOF if the input is empty.
@kindex C-c C-e @r{(``line mode'')}
@kindex C-c C-j @r{(``line mode'')}
@kindex C-c M-d @r{(``line mode'')}
You can exit to ``emacs mode'', ``semi-char mode'' or ``char mode''
with @kbd{C-c C-e}, @kbd{C-c C-j} or @kbd{C-c M-d} respectively. If
there's any pending input when exiting line mode, it is sent as is to
the terminal.
@kindex M-p @r{(``line mode'')}
@kindex M-n @r{(``line mode'')}
@kindex C-@key{up} @r{(``line mode'')}
@kindex C-@key{down} @r{(``line mode'')}
@kindex M-r @r{(``line mode'')}
@kindex C-c M-r @r{(``line mode'')}
@kindex C-c M-s @r{(``line mode'')}
@kindex C-c C-r @r{(``line mode'')}
@kindex @key{TAB} @r{(``line mode'')}
The input history is recorded. You can cycle through the history with
@kbd{M-p} and @kbd{M-n}, and also with @kbd{C-@key{up}} and
@kbd{C-@key{down}}. @kbd{M-r} searches the input history, using the
minibuffer. @kbd{C-c M-r} and @kbd{C-c M-s} searches the input
history taking the current input as the query. @kbd{C-c M-r} searches
backward while @kbd{C-c M-s} searches forward. And @kbd{C-c C-r}
searches the input history using minibuffer with completion, useful
specially if you use any minibuffer completion UI/framework.
Input history is not loaded from the shell history file, to do that,
@xref{Line Mode Integration}.
@part Part III:@* Advanced Customizations
@node Shell Integration
@ -528,76 +590,23 @@ Beware, messages can be sent by malicious and/or buggy programs
running in the shell, therefore you should always verify the message
before doing anywhere.
@anchor{Native Shell Prompt Editing}
@cindex native shell prompt editing
@cindex shell prompt native editing
@cindex native prompt editing
@cindex prompt native editing
@cindex shell prompt, native editing
@cindex prompt, native editing
@cindex prompt mode
@cindex mode, prompt
@cindex keybindings, prompt mode
@cindex keybinding mode, prompt
@cindex input mode, prompt
@section Native Shell Prompt Editing
@anchor{Line Mode Integration}
@cindex line mode integration
@cindex line mode shell integration
@cindex integration, shell, line mode
@section Line Mode Integration
When this feature is enabled, you can edit shell command with regular
Emacs command, similar to Shell mode and Term line mode
(@pxref{Shell Mode,,, emacs, GNU Emacs Manual} and
@ref{Terminal emulator,,, emacs, GNU Emacs Manual}). This
feature isn't support in Eshell.
When shell integration is enabled, the input history of line mode is
automatically filled with the shell history when the shell starts.
Also you can make Eat automatically switch to ``line mode''
(@pxref{Line Mode}) for you when the shell prompt appears.
To enable native shell prompt editing, customize the user option
@code{eat-enable-native-shell-prompt-editing}.
@vindex eat-enable-native-shell-prompt-editing
@defopt eat-enable-native-shell-prompt-editing
When non-nil, enable support for editing shell commands with native
Emacs commands.
@vindex eat-enable-auto-line-mode
@defopt eat-enable-auto-line-mode
When non-nil, automatically switch to line mode the shell prompt
appears.
@end defopt
When the feature is enabled, Eat automatically switches to a input
mode named ``prompt mode'' when the shell prompt appears. In this
mode, you can write and edit your shell command input with Emacs. You
can't modify the text inside the terminal, however.
@kindex @key{RET} @r{(``prompt mode'')}
@kindex C-d @r{(``prompt mode'')}
@kindex C-c C-c @r{(``prompt mode'')}
@kindex C-c @key{SPC} @r{(``prompt mode'')}
@kindex @key{TAB} @r{(``prompt mode'')}
In ``prompt mode'', just like an ordinary shell, you can send the
input with @kbd{@key{RET}}, and exit shell with @kbd{C-d} when the
input is empty. You can cancel input with @kbd{C-c C-c}. If you want
to write a newline without sending it, you can do so with
@kbd{C-c @key{SPC}}, To perform completion, press @kbd{@key{TAB}}.
@kindex M-p @r{(``prompt mode'')}
@kindex M-n @r{(``prompt mode'')}
@kindex C-@key{up} @r{(``prompt mode'')}
@kindex C-@key{down} @r{(``prompt mode'')}
@kindex M-r @r{(``prompt mode'')}
@kindex C-c M-r @r{(``prompt mode'')}
@kindex C-c M-s @r{(``prompt mode'')}
@kindex C-c C-r @r{(``prompt mode'')}
@kindex @key{TAB} @r{(``prompt mode'')}
You can cycle through history with @kbd{M-p} and @kbd{M-n}, and also
with @kbd{C-@key{up}} and @kbd{C-@key{down}}. @kbd{M-r} searches the
input history, using minibuffer. @kbd{C-c M-r} and @kbd{C-c M-s}
searches the input history taking the current input as the query.
@kbd{C-c M-r} searches backward while @kbd{C-c M-s} searches forward.
And @kbd{C-c C-r} searches the input history using minibuffer with
completion.
@kindex C-c C-e @r{(``prompt mode'')}
@kindex C-c C-j @r{(``prompt mode'')}
@kindex C-c M-d @r{(``prompt mode'')}
You can exit to ``emacs mode'', ``semi-char mode'' or ``char mode''
with @kbd{C-c C-e}, @kbd{C-c C-j} or @kbd{C-c M-d} respectively. But
once exited, you can't return to ``prompt mode'' without getting a new
shell prompt.
@node Querying Before Kill
@cindex querying before kill
@cindex querying before kill terminal