From 44e9a6f5de4bcf1391d15fd47179942db1372e7e Mon Sep 17 00:00:00 2001 From: Akib Azmain Turja Date: Fri, 22 Sep 2023 22:25:12 +0600 Subject: [PATCH] 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. --- .dir-locals.el | 3 +- README.org | 47 +++-- eat.el | 483 +++++++++++++++++++++++++------------------------ eat.texi | 141 ++++++++------- 4 files changed, 346 insertions(+), 328 deletions(-) diff --git a/.dir-locals.el b/.dir-locals.el index 9f6a0ef..b510967 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -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)))))) diff --git a/README.org b/README.org index 67756c0..15396f9 100644 --- a/README.org +++ b/README.org @@ -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 diff --git a/eat.el b/eat.el index 38ad8f9..8cc0bdf 100644 --- a/eat.el +++ b/eat.el @@ -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. -\\This influences the commands \ -\\[eat-prompt-previous-matching-input-from-input] and \ -\\[eat-prompt-next-matching-input-from-input]. +\\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) diff --git a/eat.texi b/eat.texi index 212948b..e3db59e 100644 --- a/eat.texi +++ b/eat.texi @@ -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