commit 64c289590b56ea08d646b74f1a4b5de0a1faa2e2 (HEAD, refs/remotes/origin/master) Author: Juri Linkov Date: Wed Nov 27 09:45:19 2024 +0200 Add command symbol property 'repeat-continue-only' for 'repeat-mode' * lisp/repeat.el: (repeat-post-hook): Ignore commands with 'repeat-continue-only' symbol property when repeat was not in progress (bug#74140). * test/lisp/repeat-tests.el (repeat-tests-continue-only): New test. (repeat-tests-bind-keys): Prepare for :continue-only. diff --git a/etc/NEWS b/etc/NEWS index 7ca711959df..e2fbfa41abf 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -944,6 +944,9 @@ provide a ':columns' spec, so that the number of columns and their widths can be determined. Columns widths can be set explicitly, or they will be calculated based on the window width. +** New symbol propery 'repeat-continue-only' for 'repeat-mode'. +A command with this symbol propery will not activate the repeat map, +but will only continue the already activated repeating sequence. * Changes in Emacs 31.1 on Non-Free Operating Systems diff --git a/lisp/repeat.el b/lisp/repeat.el index f13fa489cae..11d26a477b6 100644 --- a/lisp/repeat.el +++ b/lisp/repeat.el @@ -504,7 +504,9 @@ See `describe-repeat-maps' for a list of all repeatable commands." (let ((was-in-progress repeat-in-progress)) (setq repeat-in-progress nil) (let ((map (repeat-get-map))) - (when (repeat-check-map map) + (when (and (repeat-check-map map) + (or (null (repeat--command-property 'repeat-continue-only)) + was-in-progress)) ;; Messaging (funcall repeat-echo-function map) diff --git a/test/lisp/repeat-tests.el b/test/lisp/repeat-tests.el index 5b3a72a37ae..c560a283039 100644 --- a/test/lisp/repeat-tests.el +++ b/test/lisp/repeat-tests.el @@ -25,7 +25,7 @@ (require 'repeat) ;; Key mnemonics: a - Activate (enter, also b), c - Continue (also d), -;; q - Quit (exit) +;; o - continue-Only (not activate), q - Quit (exit) (defvar repeat-tests-calls nil) @@ -45,6 +45,10 @@ (interactive "p") (push `(,arg d) repeat-tests-calls)) +(defun repeat-tests-call-o (&optional arg) + (interactive "p") + (push `(,arg o) repeat-tests-calls)) + (defun repeat-tests-call-q (&optional arg) (interactive "p") (push `(,arg q) repeat-tests-calls)) @@ -54,7 +58,8 @@ :doc "Keymap for keys that initiate repeating sequences." "C-x w a" 'repeat-tests-call-a "C-M-a" 'repeat-tests-call-a - "C-M-b" 'repeat-tests-call-b) + "C-M-b" 'repeat-tests-call-b + "C-M-o" 'repeat-tests-call-o) (defvar-keymap repeat-tests-repeat-map :doc "Keymap for repeating sequences." @@ -63,8 +68,12 @@ "a" 'ignore ;; for non-nil repeat-check-key only "c" 'repeat-tests-call-c "d" 'repeat-tests-call-d + "C-M-o" 'repeat-tests-call-o "q" 'repeat-tests-call-q) +;; TODO: add new keyword ':continue-only (repeat-tests-call-o)' +(put 'repeat-tests-call-o 'repeat-continue-only t) + ;; Test using a variable instead of the symbol: (put 'repeat-tests-call-b 'repeat-map repeat-tests-repeat-map) @@ -171,6 +180,19 @@ ;; TODO: :tags '(:expensive-test) for repeat-exit-timeout +(ert-deftest repeat-tests-continue-only () + (with-repeat-mode repeat-tests-map + (let ((repeat-echo-function 'ignore) + (repeat-check-key nil)) + ;; 'C-M-o' used as continue-only + (repeat-tests--check + "C-M-a c C-M-o c z" + '((1 a) (1 c) (1 o) (1 c)) "z") + ;; 'C-M-o' should not activate + (repeat-tests--check + "C-M-o c z" + '((1 o)) "cz")))) + (require 'use-package) @@ -186,6 +208,10 @@ (interactive "p") (push `(,arg d) repeat-tests-calls)) +(defun repeat-tests-bind-call-o (&optional arg) + (interactive "p") + (push `(,arg o) repeat-tests-calls)) + (defun repeat-tests-bind-call-q (&optional arg) (interactive "p") (push `(,arg q) repeat-tests-calls)) @@ -195,9 +221,12 @@ (bind-keys :map repeat-tests-bind-keys-map ("C-M-a" . repeat-tests-bind-call-a) + ("C-M-o" . repeat-tests-bind-call-o) :repeat-map repeat-tests-bind-keys-repeat-map :continue ("c" . repeat-tests-bind-call-c) + ;; :continue-only + ("C-M-o" . repeat-tests-bind-call-o) :exit ("q" . repeat-tests-bind-call-q) ) @@ -208,6 +237,14 @@ (with-repeat-mode repeat-tests-bind-keys-map (let ((repeat-echo-function 'ignore) (repeat-check-key nil)) + ;; 'C-M-o' used as continue-only + (repeat-tests--check + "C-M-a c C-M-o c z" + '((1 a) (1 c) (1 o) (1 c)) "z") + ;; 'C-M-o' should not activate + ;; (repeat-tests--check + ;; "C-M-o c z" + ;; '((1 o)) "cz") ;; 'q' should exit (repeat-tests--check "C-M-a c q c" commit 9784c20dba1f00b583c92f83c08156ef000ec0b9 Author: Juri Linkov Date: Wed Nov 27 09:39:19 2024 +0200 * test/lisp/repeat-tests.el: Improve using new key mnemonics (bug#74140). (repeat-tests-repeat-map): Use :repeat keywords with :enter and :exit for 'defvar-keymap'. (with-repeat-mode): Add new arg 'map' to defmacro. (repeat-tests-check-key, repeat-tests-exit-key) (repeat-tests-keep-prefix): Use new key mnemonics. (repeat-tests-exit-command): New test. (repeat-tests-bind-keys): New test for 'bind-keys' from 'use-package'. diff --git a/test/lisp/repeat-tests.el b/test/lisp/repeat-tests.el index 4e86510f19b..5b3a72a37ae 100644 --- a/test/lisp/repeat-tests.el +++ b/test/lisp/repeat-tests.el @@ -24,6 +24,9 @@ (require 'ert) (require 'repeat) +;; Key mnemonics: a - Activate (enter, also b), c - Continue (also d), +;; q - Quit (exit) + (defvar repeat-tests-calls nil) (defun repeat-tests-call-a (&optional arg) @@ -34,31 +37,51 @@ (interactive "p") (push `(,arg b) repeat-tests-calls)) +(defun repeat-tests-call-c (&optional arg) + (interactive "p") + (push `(,arg c) repeat-tests-calls)) + +(defun repeat-tests-call-d (&optional arg) + (interactive "p") + (push `(,arg d) repeat-tests-calls)) + +(defun repeat-tests-call-q (&optional arg) + (interactive "p") + (push `(,arg q) repeat-tests-calls)) + +;; Global keybindings (defvar-keymap repeat-tests-map :doc "Keymap for keys that initiate repeating sequences." "C-x w a" 'repeat-tests-call-a "C-M-a" 'repeat-tests-call-a - "C-M-z" 'repeat-tests-call-a) + "C-M-b" 'repeat-tests-call-b) (defvar-keymap repeat-tests-repeat-map :doc "Keymap for repeating sequences." - "a" 'repeat-tests-call-a - "b" 'repeat-tests-call-b) -(put 'repeat-tests-call-a 'repeat-map 'repeat-tests-repeat-map) + :repeat ( :enter (repeat-tests-call-a) + :exit (repeat-tests-call-q)) + "a" 'ignore ;; for non-nil repeat-check-key only + "c" 'repeat-tests-call-c + "d" 'repeat-tests-call-d + "q" 'repeat-tests-call-q) + +;; Test using a variable instead of the symbol: (put 'repeat-tests-call-b 'repeat-map repeat-tests-repeat-map) -(defmacro with-repeat-mode (&rest body) +(defmacro with-repeat-mode (map &rest body) "Create environment for testing `repeat-mode'." + (declare (indent 1) (debug (symbol body))) `(unwind-protect - (progn - (repeat-mode +1) - (with-temp-buffer - (save-window-excursion - ;; `execute-kbd-macro' applied to window only - (set-window-buffer nil (current-buffer)) - (use-local-map repeat-tests-map) - ,@body))) - (repeat-mode -1))) + (progn + (repeat-mode +1) + (with-temp-buffer + (save-window-excursion + ;; `execute-kbd-macro' applied to window only + (set-window-buffer nil (current-buffer)) + (use-local-map ,map) + ,@body))) + (repeat-mode -1) + (use-local-map nil))) (defun repeat-tests--check (keys calls inserted) (setq repeat-tests-calls nil) @@ -69,75 +92,126 @@ (should (equal (buffer-string) inserted))) (ert-deftest repeat-tests-check-key () - (with-repeat-mode - (let ((repeat-echo-function 'ignore)) - (let ((repeat-check-key t)) - (repeat-tests--check - "C-x w a b a c" - '((1 a) (1 b) (1 a)) "c") - (repeat-tests--check - "C-M-a b a c" - '((1 a) (1 b) (1 a)) "c") - (repeat-tests--check - "C-M-z b a c" - '((1 a)) "bac") - (unwind-protect - (progn - (put 'repeat-tests-call-a 'repeat-check-key 'no) - (repeat-tests--check - "C-M-z b a c" - '((1 a) (1 b) (1 a)) "c")) - (put 'repeat-tests-call-a 'repeat-check-key nil))) - (let ((repeat-check-key nil)) - (repeat-tests--check - "C-M-z b a c" - '((1 a) (1 b) (1 a)) "c") - (unwind-protect - (progn - (put 'repeat-tests-call-a 'repeat-check-key t) - (repeat-tests--check - "C-M-z b a c" - '((1 a)) "bac")) - (put 'repeat-tests-call-a 'repeat-check-key nil)))))) + (with-repeat-mode repeat-tests-map + (let ((repeat-echo-function 'ignore)) + (let ((repeat-check-key t)) + (repeat-tests--check + "C-x w a c d z" + '((1 a) (1 c) (1 d)) "z") + (repeat-tests--check + "C-M-a c d z" + '((1 a) (1 c) (1 d)) "z") + (repeat-tests--check + "C-M-b c d z" + '((1 b)) "cdz") + (unwind-protect + (progn + (put 'repeat-tests-call-b 'repeat-check-key 'no) + (repeat-tests--check + "C-M-b c d z" + '((1 b) (1 c) (1 d)) "z")) + (put 'repeat-tests-call-b 'repeat-check-key nil))) + (let ((repeat-check-key nil)) + (repeat-tests--check + "C-M-b c d z" + '((1 b) (1 c) (1 d)) "z") + (unwind-protect + (progn + (put 'repeat-tests-call-b 'repeat-check-key t) + (repeat-tests--check + "C-M-b c d z" + '((1 b)) "cdz")) + (put 'repeat-tests-call-b 'repeat-check-key nil)))))) + +(ert-deftest repeat-tests-exit-command () + (with-repeat-mode repeat-tests-map + (let ((repeat-echo-function 'ignore)) + ;; 'c' doesn't continue since 'q' exited + (repeat-tests--check + "C-x w a c d q c" + '((1 a) (1 c) (1 d) (1 q)) "c")))) (ert-deftest repeat-tests-exit-key () - (with-repeat-mode - (let ((repeat-echo-function 'ignore)) - (let ((repeat-exit-key nil)) - (repeat-tests--check - "C-x w a b a b RET c" - '((1 a) (1 b) (1 a) (1 b)) "\nc")) - (let ((repeat-exit-key [return])) - (repeat-tests--check - "C-x w a b a b c" - '((1 a) (1 b) (1 a) (1 b)) "c"))))) + (with-repeat-mode repeat-tests-map + (let ((repeat-echo-function 'ignore)) + (let ((repeat-exit-key nil)) + (repeat-tests--check + "C-x w a c d c RET z" + '((1 a) (1 c) (1 d) (1 c)) "\nz")) + (let ((repeat-exit-key [return])) + (repeat-tests--check + "C-x w a c d c z" + '((1 a) (1 c) (1 d) (1 c)) "z"))))) (ert-deftest repeat-tests-keep-prefix () - (with-repeat-mode - (let ((repeat-echo-function 'ignore)) - (repeat-tests--check - "C-x w a b a b c" - '((1 a) (1 b) (1 a) (1 b)) "c") - (let ((repeat-keep-prefix nil)) - (repeat-tests--check - "C-2 C-x w a b a b c" - '((2 a) (1 b) (1 a) (1 b)) "c") - (repeat-tests--check - "C-2 C-x w a C-3 c" - '((2 a)) "ccc")) - ;; Fixed in bug#51281 and bug#55986 - (let ((repeat-keep-prefix t)) - ;; Re-enable to take effect. - (repeat-mode -1) (repeat-mode +1) - (repeat-tests--check - "C-2 C-x w a b a b c" - '((2 a) (2 b) (2 a) (2 b)) "c") - ;; (repeat-tests--check - ;; "C-2 C-x w a C-1 C-2 b a C-3 C-4 b c" - ;; '((2 a) (12 b) (12 a) (34 b)) "c") - )))) + (with-repeat-mode repeat-tests-map + (let ((repeat-echo-function 'ignore)) + (repeat-tests--check + "C-x w a c d c z" + '((1 a) (1 c) (1 d) (1 c)) "z") + (let ((repeat-keep-prefix nil)) + (repeat-tests--check + "C-2 C-x w a c d c z" + '((2 a) (1 c) (1 d) (1 c)) "z") + (repeat-tests--check + "C-2 C-x w a C-3 z" + '((2 a)) "zzz")) + ;; Fixed in bug#51281 and bug#55986 + (let ((repeat-keep-prefix t)) + ;; Re-enable to take effect. + (repeat-mode -1) (repeat-mode +1) + (repeat-tests--check + "C-2 C-x w a c d c z" + '((2 a) (2 c) (2 d) (2 c)) "z") + ;; Unimplemented feature (maybe unnecessary): + ;; (repeat-tests--check + ;; "C-2 C-x w a C-1 C-2 c d C-3 C-4 c z" + ;; '((2 a) (12 c) (12 d) (34 c)) "z") + )))) ;; TODO: :tags '(:expensive-test) for repeat-exit-timeout + +(require 'use-package) + +(defun repeat-tests-bind-call-a (&optional arg) + (interactive "p") + (push `(,arg a) repeat-tests-calls)) + +(defun repeat-tests-bind-call-c (&optional arg) + (interactive "p") + (push `(,arg c) repeat-tests-calls)) + +(defun repeat-tests-bind-call-d (&optional arg) + (interactive "p") + (push `(,arg d) repeat-tests-calls)) + +(defun repeat-tests-bind-call-q (&optional arg) + (interactive "p") + (push `(,arg q) repeat-tests-calls)) + +(ert-deftest repeat-tests-bind-keys () + (defvar repeat-tests-bind-keys-map (make-sparse-keymap)) + (bind-keys + :map repeat-tests-bind-keys-map + ("C-M-a" . repeat-tests-bind-call-a) + :repeat-map repeat-tests-bind-keys-repeat-map + :continue + ("c" . repeat-tests-bind-call-c) + :exit + ("q" . repeat-tests-bind-call-q) + ) + + ;; TODO: it seems there is no :entry, so need to do explicitly: + (put 'repeat-tests-bind-call-a 'repeat-map 'repeat-tests-bind-keys-repeat-map) + + (with-repeat-mode repeat-tests-bind-keys-map + (let ((repeat-echo-function 'ignore) + (repeat-check-key nil)) + ;; 'q' should exit + (repeat-tests--check + "C-M-a c q c" + '((1 a) (1 c) (1 q)) "c")))) + (provide 'repeat-tests) ;;; repeat-tests.el ends here commit e4abb06e5bf982a4688de0638b1eeecf4ff38d95 Author: Sean Whitton Date: Wed Nov 27 10:36:53 2024 +0800 * lisp/subr.el (when-let): Reimplement so as to avoid bug#74530. diff --git a/lisp/subr.el b/lisp/subr.el index a959c6a9810..02cc84c04b7 100644 --- a/lisp/subr.el +++ b/lisp/subr.el @@ -2699,8 +2699,14 @@ If all are non-nil, return the value of the last form in BODY. The variable list SPEC is the same as in `if-let'." (declare (indent 1) (debug if-let) (obsolete "use `when-let*' or `and-let*' instead." "31.1")) - `(with-suppressed-warnings ((obsolete if-let)) - (if-let ,spec ,(macroexp-progn body)))) + ;; Previously we expanded to `if-let', and then required a + ;; `with-suppressed-warnings' to avoid doubling up the obsoletion + ;; warnings. But that triggers a bytecompiler bug; see bug#74530. + ;; So for now we reimplement `if-let' here. + (when (and (<= (length spec) 2) + (not (listp (car spec)))) + (setq spec (list spec))) + (list 'if-let* spec (macroexp-progn body))) (defmacro while-let (spec &rest body) "Bind variables according to SPEC and conditionally evaluate BODY. commit 0624fe6f8497a677ae354da0a604dbf82e69400a Author: Dmitry Gutov Date: Wed Nov 27 03:43:22 2024 +0200 Make Xref commands follow 'display-buffer' customizations * lisp/progmodes/xref.el (xref--show-pos-in-buf): Append '(category . xref-jump)' to display-buffer action argument, when the target window or frame is not made explicit by the command. (xref--switch-to-buffer): New function (bug#74361). Do the switch through 'pop-to-buffer' and use the new category. (xref-go-back, xref-go-forward, xref-pop-to-location): Use it. * etc/NEWS: Describe the change. diff --git a/etc/NEWS b/etc/NEWS index d7047d0923f..7ca711959df 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -781,6 +781,17 @@ removing packages. When invoked with a prefix argument, 'package-install-selected-packages' will not prompt the user for confirmation before installing packages. +** Xref + +The commands that jump to some location use 'display-buffer' and specify +the category 'xref-jump'. As a result you can customize how the +destination window is chosen using 'display-buffer-alist'. Example: + + (setq display-buffer-alist '(((category . xref) + (display-buffer-reuse-window + display-buffer-use-some-window) + (some-window . mru)))) + * New Modes and Packages in Emacs 31.1 diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el index cf061a18ee0..e6f029f3fa8 100644 --- a/lisp/progmodes/xref.el +++ b/lisp/progmodes/xref.el @@ -513,6 +513,9 @@ Erase the stack slots following this one." ;;;###autoload (define-obsolete-function-alias 'xref-pop-marker-stack #'xref-go-back "29.1") +(defun xref--switch-to-buffer (buf) + (pop-to-buffer buf '((display-buffer-same-window) (category . xref-jump)))) + ;;;###autoload (defun xref-go-back () "Go back to the previous position in xref history. @@ -523,8 +526,8 @@ To undo, use \\[xref-go-forward]." (user-error "At start of xref history") (let ((marker (pop (car history)))) (xref--push-forward (point-marker)) - (switch-to-buffer (or (marker-buffer marker) - (user-error "The marked buffer has been deleted"))) + (xref--switch-to-buffer (or (marker-buffer marker) + (user-error "The marked buffer has been deleted"))) (goto-char (marker-position marker)) (set-marker marker nil nil) (run-hooks 'xref-after-return-hook))))) @@ -538,8 +541,8 @@ To undo, use \\[xref-go-forward]." (user-error "At end of xref history") (let ((marker (pop (cdr history)))) (xref--push-backward (point-marker)) - (switch-to-buffer (or (marker-buffer marker) - (user-error "The marked buffer has been deleted"))) + (xref--switch-to-buffer (or (marker-buffer marker) + (user-error "The marked buffer has been deleted"))) (goto-char (marker-position marker)) (set-marker marker nil nil) (run-hooks 'xref-after-return-hook))))) @@ -612,7 +615,7 @@ If SELECT is non-nil, select the target window." (xref-location-marker (xref-item-location item)))) (buf (marker-buffer marker))) (cl-ecase action - ((nil) (switch-to-buffer buf)) + ((nil) (xref--switch-to-buffer buf)) (window (pop-to-buffer buf t)) (frame (let ((pop-up-frames t)) (pop-to-buffer buf t)))) (xref--goto-char marker)) @@ -688,7 +691,10 @@ and finally return the window." (or (not (window-dedicated-p xref--original-window)) (eq (window-buffer xref--original-window) buf))) `((xref--display-buffer-in-window) - (window . ,xref--original-window)))))) + (category . xref-jump) + (window . ,xref--original-window))) + (t + '(nil (category . xref-jump)))))) (with-selected-window (display-buffer buf action) (xref--goto-char pos) (run-hooks 'xref-after-jump-hook) commit 298bbf3cd76ffaf0c88a6a9ec56aa33c4103b8e6 Author: Dmitry Gutov Date: Tue Nov 26 23:30:36 2024 +0200 * lisp/files.el (major-mode-remap-alist): Add :tag annotations to :type. diff --git a/lisp/files.el b/lisp/files.el index 72f3604054e..a65bc4a4ea2 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -3621,7 +3621,10 @@ instead. FUNCTION is typically a major mode which \"does the same thing\" as MODE, but can also be nil to hide other entries (either in this var or in `major-mode-remap-defaults') and means that we should call MODE." - :type '(alist (symbol) (function))) + :type '(alist + :tag "Remappings" + :key-type (symbol :tag "From major mode") + :value-type (function :tag "To mode (or function)"))) (defvar major-mode-remap-defaults nil "Alist mapping file-specified modes to alternative modes. commit 775290efdf18e67bd0726190fe86326c112a27bf Author: Robert Pluim Date: Tue Nov 26 17:57:47 2024 +0100 ; * doc/misc/tramp.texi (Frequently Asked Questions): Minor edits. diff --git a/doc/misc/tramp.texi b/doc/misc/tramp.texi index b573b32e044..e1d50972583 100644 --- a/doc/misc/tramp.texi +++ b/doc/misc/tramp.texi @@ -5589,14 +5589,14 @@ disable @command{ssh-agent} for such keys. @item -Does @value{tramp} support a fingerprint reader? +Does @value{tramp} support fingerprint readers? -Yes. A fingerprint reader could be used as additional authentication +Yes. A fingerprint reader can be used as an additional authentication method for @option{sudo}-based logins. @value{tramp} supports the -additional handshaking messages for it@footnote{It supports +required additional handshaking messages@footnote{It supports fingerprint readers driven by @command{fprintd}.}. If the fingerprint -isn't recognized by the fingerprint reader in time, the authentication -falls back to request a password. +isn't recognized by the fingerprint reader in time, authentication +falls back to requesting a password. @vindex tramp-use-fingerprint If the user option @code{tramp-use-fingerprint} is @code{nil}, commit 3fe787ad4d5894df5b540dbd195128118c949c7c Author: Michael Albinus Date: Tue Nov 26 12:45:06 2024 +0100 Support fingerprint readers in Tramp * doc/misc/tramp.texi (Frequently Asked Questions): Speak about fingerprint readers. * lisp/net/tramp-sh.el (tramp-actions-before-shell): Use `tramp-fingerprint-prompt-regexp'. * lisp/net/tramp.el (tramp-wrong-passwd-regexp): Add fingerprint messages. (tramp-fingerprint-prompt-regexp, tramp-use-fingerprint): New defcustoms. (tramp-action-fingerprint, tramp-action-show-message): New defuns. (tramp-action-show-and-confirm-message): Start check at (point-min). * test/lisp/net/tramp-tests.el (tramp-test47-read-fingerprint): New test. diff --git a/doc/misc/tramp.texi b/doc/misc/tramp.texi index ca2cb0d1e91..b573b32e044 100644 --- a/doc/misc/tramp.texi +++ b/doc/misc/tramp.texi @@ -5587,6 +5587,23 @@ nitrokey, or titankey. (residential) keys by @command{ssh-agent}. As workaround, you might disable @command{ssh-agent} for such keys. + +@item +Does @value{tramp} support a fingerprint reader? + +Yes. A fingerprint reader could be used as additional authentication +method for @option{sudo}-based logins. @value{tramp} supports the +additional handshaking messages for it@footnote{It supports +fingerprint readers driven by @command{fprintd}.}. If the fingerprint +isn't recognized by the fingerprint reader in time, the authentication +falls back to request a password. + +@vindex tramp-use-fingerprint +If the user option @code{tramp-use-fingerprint} is @code{nil}, +@value{tramp} interrupts the fingerprint request, falling back to +password authentication immediately. + + @item @value{tramp} does not connect to Samba or MS Windows hosts running SMB1 connection protocol diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el index e9b11090022..46108b48768 100644 --- a/lisp/net/tramp-sh.el +++ b/lisp/net/tramp-sh.el @@ -601,6 +601,7 @@ shell from reading its init file." '((tramp-login-prompt-regexp tramp-action-login) (tramp-password-prompt-regexp tramp-action-password) (tramp-otp-password-prompt-regexp tramp-action-otp-password) + (tramp-fingerprint-prompt-regexp tramp-action-fingerprint) (tramp-wrong-passwd-regexp tramp-action-permission-denied) (shell-prompt-pattern tramp-action-succeed) (tramp-shell-prompt-pattern tramp-action-succeed) diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el index 7b14469fb55..12ad14e0900 100644 --- a/lisp/net/tramp.el +++ b/lisp/net/tramp.el @@ -723,13 +723,52 @@ The regexp should match at end of buffer." "No supported authentication methods left to try!" (: "Login " (| "Incorrect" "incorrect")) (: "Connection " (| "refused" "closed")) - (: "Received signal " (+ digit))) + (: "Received signal " (+ digit)) + ;; Fingerprint. + "Verification timed out" + "Failed to match fingerprint" + "An unknown error occurred") (* nonl)) "Regexp matching a `login failed' message. The regexp should match at end of buffer." :type 'regexp :link '(tramp-info-link :tag "Tramp manual" tramp-wrong-passwd-regexp)) +;; +(defcustom tramp-fingerprint-prompt-regexp + (rx (| "Place your finger on" + "Swipe your finger across" + "Place your left thumb on" + "Swipe your left thumb across" + "Place your left index finger on" + "Swipe your left index finger across" + "Place your left middle finger on" + "Swipe your left middle finger across" + "Place your left ring finger on" + "Swipe your left ring finger across" + "Place your left little finger on" + "Swipe your left little finger across" + "Place your right thumb on" + "Swipe your right thumb across" + "Place your right index finger on" + "Swipe your right index finger across" + "Place your right middle finger on" + "Swipe your right middle finger across" + "Place your right ring finger on" + "Swipe your right ring finger across" + "Place your right little finger on" + "Swipe your right little finger across" + "Place your finger on the reader again" + "Swipe your finger again" + "Swipe was too short, try again" + "Your finger was not centred, try swiping your finger again" + "Remove your finger, and try swiping your finger again") + (* nonl) (* (any "\r\n"))) + "Regexp matching fingerprint prompts. +The regexp should match at end of buffer." + :version "30.2" + :type 'regexp) + (defcustom tramp-yesno-prompt-regexp (rx "Are you sure you want to continue connecting (yes/no" (? "/[fingerprint]") ")?" @@ -5742,6 +5781,23 @@ of." (narrow-to-region (point-max) (point-max)))) t) +(defcustom tramp-use-fingerprint t + "Whether fingerprint prompts shall be used for authentication." + :version "30.2" + :type 'boolean + :link '(tramp-info-link :tag "Tramp manual" tramp-use-fingerprint)) + +(defun tramp-action-fingerprint (proc vec) + "Query the user for a fingerprint verification. +Interrupt the query if `tramp-use-fingerprint' is nil." + (with-current-buffer (process-buffer proc) + (if tramp-use-fingerprint + (tramp-action-show-message proc vec) + (interrupt-process proc) + ;; Hide message. + (narrow-to-region (point-max) (point-max)))) + t) + (defun tramp-action-succeed (_proc _vec) "Signal success in finding shell prompt." (throw 'tramp-action 'ok)) @@ -5788,6 +5844,26 @@ The terminal type can be configured with `tramp-terminal-type'." (tramp-send-string vec (concat tramp-terminal-type tramp-local-end-of-line)) t) +(defun tramp-action-show-message (proc vec) + "Show the user a message for confirmation. +Wait, until the connection buffer changes." + (with-current-buffer (process-buffer proc) + (let ((cursor-in-echo-area t) + set-message-function clear-message-function tramp-dont-suspend-timers) + (with-tramp-suspended-timers + ;; Silence byte compiler. + (ignore set-message-function clear-message-function) + (tramp-message vec 6 "\n%s" (buffer-string)) + (goto-char (point-min)) + (tramp-check-for-regexp proc tramp-process-action-regexp) + (with-temp-message (concat (string-trim (match-string 0)) " ") + ;; Hide message in buffer. + (narrow-to-region (point-max) (point-max)) + ;; Wait for new output. + (while (length= (buffer-string) 0) + (tramp-accept-process-output proc)))))) + t) + (defun tramp-action-confirm-message (_proc vec) "Return RET in order to confirm the message." (tramp-message @@ -5805,6 +5881,7 @@ Wait, until the connection buffer changes." ;; Silence byte compiler. (ignore set-message-function clear-message-function) (tramp-message vec 6 "\n%s" (buffer-string)) + (goto-char (point-min)) (tramp-check-for-regexp proc tramp-process-action-regexp) (with-temp-message (concat (string-trim (match-string 0)) " ") ;; Hide message in buffer. diff --git a/test/lisp/net/tramp-tests.el b/test/lisp/net/tramp-tests.el index 5df3dadf5c9..032ddaef953 100644 --- a/test/lisp/net/tramp-tests.el +++ b/test/lisp/net/tramp-tests.el @@ -8123,6 +8123,49 @@ process sentinels. They shall not disturb each other." (should-error (file-exists-p ert-remote-temporary-file-directory))))))))) +(ert-deftest tramp-test47-read-fingerprint () + "Check Tramp fingerprint handling." + :tags '(:expensive-test) + (skip-unless (tramp--test-mock-p)) + + (let (;; Suppress "exec". + (tramp-restricted-shell-hosts-alist `(,tramp-system-name))) + + ;; Reading fingerprint works. + (tramp-cleanup-connection tramp-test-vec 'keep-debug) + (let ((tramp-connection-properties + `((nil "login-args" + (("-c") + (,(tramp-shell-quote-argument + "echo Place your finger on the fingerprint reader")) + (";") ("sleep" "1") + (";") ("sh" "-i")))))) + (should (file-exists-p ert-remote-temporary-file-directory))) + + ;; Falling back after a timeout works. + (tramp-cleanup-connection tramp-test-vec 'keep-debug) + (let ((tramp-connection-properties + `((nil "login-args" + (("-c") + (,(tramp-shell-quote-argument + "echo Place your finger on the fingerprint reader")) + (";") ("sleep" "1") + (";") ("echo" "Failed to match fingerprint") + (";") ("sh" "-i")))))) + (should (file-exists-p ert-remote-temporary-file-directory))) + + ;; Interrupting the fingerprint handshaking works. + (tramp-cleanup-connection tramp-test-vec 'keep-debug) + (let ((tramp-connection-properties + `((nil "login-args" + (("-c") + (,(tramp-shell-quote-argument + "echo Place your finger on the fingerprint reader")) + (";") ("sleep" "1") + (";") ("sh" "-i"))))) + tramp-use-fingerprint) + (should (file-exists-p ert-remote-temporary-file-directory))))) + ;; This test is inspired by Bug#29163. (ert-deftest tramp-test48-auto-load () "Check that Tramp autoloads properly."