From 60bf9ceffe3e5bd57a52d32922647c038097a0db Mon Sep 17 00:00:00 2001
From: Michael Albinus <michael.albinus@gmx.de>
Date: Fri, 14 Mar 2025 16:31:51 +0100
Subject: [PATCH] Improve tramp-*-with-sudo commands

* doc/emacs/dired.texi (Dired Visiting):
Add tramp-dired-find-file-with-sudo.

* doc/emacs/files.texi (Reverting):
Add tramp-revert-buffer-with-sudo.

* doc/misc/tramp.texi (Ad-hoc multi-hops): Extend wrt
`tramp-*-with-sudo' commands.

* etc/NEWS: Add tramp-dired-find-file-with-sudo.
Fix typos.

* lisp/bindings.el (ctl-x-x-map):
* lisp/dired.el (dired-mode-map): Add "@" binding.  (Bug#76974)

* lisp/net/tramp-cmds.el (dired-get-file-for-visit): Declare.
(with-tramp-file-name-with-method): New macro.
(tramp-revert-buffer-with-sudo): Autoload.  Preserve position.
Use `with-tramp-file-name-with-method'.
(tramp-dired-find-file-with-sudo): New command.

(cherry picked from commit 24ffcbb3da9a010cf564bb496af3f5ce0b805f17)
---
 doc/emacs/dired.texi   |  8 ++++++
 doc/emacs/files.texi   |  9 +++++++
 doc/misc/tramp.texi    | 24 ++++++++++++------
 lisp/bindings.el       |  3 ++-
 lisp/dired.el          |  1 +
 lisp/net/tramp-cmds.el | 55 ++++++++++++++++++++++++++++++------------
 6 files changed, 76 insertions(+), 24 deletions(-)

diff --git a/doc/emacs/dired.texi b/doc/emacs/dired.texi
index 07142e71713..f52b001c121 100644
--- a/doc/emacs/dired.texi
+++ b/doc/emacs/dired.texi
@@ -460,6 +460,14 @@ View the file described on the current line, with View mode
 (@code{dired-view-file}).  View mode provides convenient commands to
 navigate the buffer but forbids changing it; @xref{View Mode}.
 
+@item @@
+@kindex @@ @r{(Dired)}
+@findex tramp-dired-find-file-with-sudo
+Open the file described on the current line, with root permissions
+(@code{tramp-dired-find-file-with-sudo}).  Calling it with the @kbd{C-u}
+prefix argument asks for another Tramp method interactively but
+@option{sudo}.  @xref{Ad-hoc multi-hops, Tramp,, tramp, The Tramp Manual}.
+
 @item ^
 @kindex ^ @r{(Dired)}
 @findex dired-up-directory
diff --git a/doc/emacs/files.texi b/doc/emacs/files.texi
index d2889efc9d3..e7c5130c6b5 100644
--- a/doc/emacs/files.texi
+++ b/doc/emacs/files.texi
@@ -1172,6 +1172,15 @@ the major mode actually turned on as result of reverting a buffer
 depends on mode remapping, and could be different from the original mode
 if you customized @code{major-mode-remap-alist} in-between.
 
+@cindex reverting with root permissions
+@findex tramp-revert-buffer-with-sudo
+@kindex C-x x @@
+A variant of reverting a buffer is visiting it by the
+@code{tramp-revert-buffer-with-sudo} (@kbd{C-x x @@}) command. It
+reopens the file or Dired buffer with root permissions.  With a prefix
+argument of @kbd{C-u}, you could change the default Tramp method
+(@option{sudo}).  @xref{Ad-hoc multi-hops, Tramp,, tramp, The Tramp Manual}.
+
 @node Auto Revert
 @section Auto Revert: Keeping buffers automatically up-to-date
 @cindex Global Auto Revert mode
diff --git a/doc/misc/tramp.texi b/doc/misc/tramp.texi
index aa2dd3a9351..26143da9501 100644
--- a/doc/misc/tramp.texi
+++ b/doc/misc/tramp.texi
@@ -3918,23 +3918,31 @@ containers on the remote host.
 
 A common use case for ad-hoc specifications is to visit a file or a
 directory with proper permissions, for example with the @option{sudo}
-method.  The command @code{tramp-revert-buffer-with-sudo} supports
-this.
+method.  The commands @code{tramp-revert-buffer-with-sudo} (@kbd{C-x x
+@@}), and @code{tramp-dired-find-file-with-sudo} (@kbd{@@} in
+@code{dired-mode}) support this.
 
+@kindex C-x x @@
 @deffn Command tramp-revert-buffer-with-sudo
 This command shows the current buffer with @option{sudo} permissions.
 The buffer must either visit a file, or a directory
 (@code{dired-mode}).
 @end deffn
 
+@kindex @@ @r{(in dired})
+@deffn Command tramp-dired-find-file-with-sudo
+In @code{dired-mode}, visit the file or directory named on this line.
+This is performed with @option{sudo} permissions.
+@end deffn
+
 @defopt tramp-file-name-with-method
-The method @code{tramp-revert-buffer-with-sudo} shows an alternate
-buffer.  It defaults to @option{sudo}, other valid methods are
-@option{su}, @option{doas}, @option{run0}, and @option{ksu}.
+The method used in @code{tramp-revert-buffer-with-sudo} and
+@code{tramp-dired-find-file-with-sudo}.  It defaults to @option{sudo},
+other valid methods are @option{su}, @option{doas}, @option{run0}, and
+@option{ksu}.
 
-@lisp
-(customize-set-variable 'tramp-file-name-with-method "doas")
-@end lisp
+If a command is called with a prefix argument @kbd{C-u}, the option's
+value is read interactively.
 @end defopt
 
 These methods apply the user @samp{root} as default.  If another user
diff --git a/lisp/bindings.el b/lisp/bindings.el
index e509b5e8b57..427e018d321 100644
--- a/lisp/bindings.el
+++ b/lisp/bindings.el
@@ -1549,7 +1549,8 @@ if `inhibit-field-text-motion' is non-nil."
   "u" #'rename-uniquely
   "n" #'clone-buffer
   "i" #'insert-buffer
-  "t" #'toggle-truncate-lines)
+  "t" #'toggle-truncate-lines
+  "@" #'tramp-revert-buffer-with-sudo)
 (define-key ctl-x-map "x" ctl-x-x-map)
 
 (define-key esc-map "\C-l" 'reposition-window)
diff --git a/lisp/dired.el b/lisp/dired.el
index 8bb05a4540e..8ffa9b1db18 100644
--- a/lisp/dired.el
+++ b/lisp/dired.el
@@ -2409,6 +2409,7 @@ Do so according to the former subdir alist OLD-SUBDIR-ALIST."
   "x"       #'dired-do-flagged-delete
   "y"       #'dired-show-file-type
   "+"       #'dired-create-directory
+  "@"       #'tramp-dired-find-file-with-sudo
   ;; moving
   "<"       #'dired-prev-dirline
   ">"       #'dired-next-dirline
diff --git a/lisp/net/tramp-cmds.el b/lisp/net/tramp-cmds.el
index 65f03f49885..2789d00380c 100644
--- a/lisp/net/tramp-cmds.el
+++ b/lisp/net/tramp-cmds.el
@@ -32,6 +32,7 @@
 
 ;; Pacify byte-compiler.
 (declare-function dired-advertise "dired")
+(declare-function dired-get-file-for-visit "dired")
 (declare-function dired-unadvertise "dired")
 (declare-function mml-mode "mml")
 (declare-function mml-insert-empty-tag "mml")
@@ -635,6 +636,19 @@ For details, see `tramp-rename-files'."
 		 (const "ksu"))
   :link '(tramp-info-link :tag "Tramp manual" tramp-file-name-with-method))
 
+(defmacro with-tramp-file-name-with-method (&rest body)
+  "Ask user for `tramp-file-name-with-method' if needed.
+Run BODY."
+  (declare (indent 0) (debug t))
+  `(let ((tramp-file-name-with-method
+          (if current-prefix-arg
+	      (completing-read
+	       "Tramp method: "
+               (mapcar #'cadr (cdr (get 'tramp-file-name-with-method 'custom-type)))
+               nil t tramp-file-name-with-method)
+            tramp-file-name-with-method)))
+     ,@body))
+
 (defun tramp-file-name-with-sudo (filename)
   "Convert FILENAME into a multi-hop file name with \"sudo\".
 An alternative method could be chosen with `tramp-file-name-with-method'."
@@ -667,27 +681,38 @@ An alternative method could be chosen with `tramp-file-name-with-method'."
      (make-tramp-file-name
       :method tramp-file-name-with-method :localname filename))))
 
-;;;###tramp-autoload
+;;;###autoload
 (defun tramp-revert-buffer-with-sudo ()
   "Revert current buffer to visit with \"sudo\" permissions.
 An alternative method could be chosen with `tramp-file-name-with-method'.
 If the buffer visits a file, the file is replaced.
 If the buffer runs `dired', the buffer is reverted."
   (interactive)
-  (cond
-   ((buffer-file-name)
-    (find-alternate-file (tramp-file-name-with-sudo (buffer-file-name))))
-   ((tramp-dired-buffer-p)
-    (dired-unadvertise (expand-file-name default-directory))
-    (setq default-directory (tramp-file-name-with-sudo default-directory)
-	  list-buffers-directory
-	  (tramp-file-name-with-sudo list-buffers-directory))
-    (if (consp dired-directory)
-	(setcar
-	 dired-directory (tramp-file-name-with-sudo (car dired-directory)))
-      (setq dired-directory (tramp-file-name-with-sudo dired-directory)))
-    (dired-advertise)
-    (revert-buffer))))
+  (with-tramp-file-name-with-method
+    (cond
+     ((buffer-file-name)
+      (let ((pos (point)))
+        (find-alternate-file (tramp-file-name-with-sudo (buffer-file-name)))
+        (goto-char pos)))
+     ((tramp-dired-buffer-p)
+      (dired-unadvertise (expand-file-name default-directory))
+      (setq default-directory (tramp-file-name-with-sudo default-directory)
+	    list-buffers-directory
+	    (tramp-file-name-with-sudo list-buffers-directory))
+      (if (consp dired-directory)
+	  (setcar
+	   dired-directory (tramp-file-name-with-sudo (car dired-directory)))
+        (setq dired-directory (tramp-file-name-with-sudo dired-directory)))
+      (dired-advertise)
+      (revert-buffer)))))
+
+;;;###autoload
+(defun tramp-dired-find-file-with-sudo ()
+  "In Dired, visit the file or directory named on this line.
+This is performed with \"sudo\" permissions."
+  (interactive)
+  (with-tramp-file-name-with-method
+    (find-file (tramp-file-name-with-sudo (dired-get-file-for-visit)))))
 
 ;;; Recompile on ELPA
 
-- 
2.39.5