Subject: Re: bug#79358: etags-regen-mode doesn't work for remote projects

From: Liu Hui <[email-masked]>
Date: Wed, 3 Sep 2025 18:10:00 +0800

On Tue, Sep 2, 2025 at 3:10 PM Michael Albinus <[email-masked]> wrote:
>
> Liu Hui <[email-masked]> writes:
>
> Hi,
>
> > I find etags-regen--tags-generate uses shell-command-on-region to run
> > the ctags command, but it seems shell-command-on-region doesn't call
> > the remote command correctly.
>
> You are right. shell-command-on-region uses call-process-region, which
> isn't aware of remote locations.
>
> I don't know whether we could change this simply. Other uses of
> shell-command-on-region, which trust that the command runs locally,
> could be broken.

Indeed. Maybe the docstring should be updated to clarify that
shell-command-on-region only runs local commands at the moment.

So I changed shell-command-on-region/shell-command to
process-file-shell-command in the attached patch, which should make
etags-regen-mode work for both local and remote projects.
From 1d37034582498c1ee28627bda557a0dd15687228 Mon Sep 17 00:00:00 2001
From: Liu Hui <[email-masked]>
Date: Wed, 3 Sep 2025 18:07:11 +0800
Subject: [PATCH] Fix etags-regen-mode for remote projects (bug#79358)

* lisp/progmodes/etags-regen.el (etags-regen--tags-generate)
(etags-regen--append-tags): Replace shell-command-on-region or
shell-command with process-file-shell-command.
* lisp/progmodes/etags.el (etags--ensure-file): Handle remote
files.
---
 lisp/progmodes/etags-regen.el | 38 ++++++++++++++++++++++++++---------
 lisp/progmodes/etags.el       |  4 +++-
 2 files changed, 31 insertions(+), 11 deletions(-)

diff --git a/lisp/progmodes/etags-regen.el b/lisp/progmodes/etags-regen.el
index cde82956c24..6055fee6a1a 100644
--- a/lisp/progmodes/etags-regen.el
+++ b/lisp/progmodes/etags-regen.el
@@ -273,6 +273,7 @@ etags-regen--ignore-regexp
 
 (defun etags-regen--tags-generate (proj)
   (let* ((root (project-root proj))
+         (remote (file-remote-p root))
          (default-directory root)
          (files (etags-regen--all-files proj))
          (tags-file (etags-regen--choose-tags-file proj))
@@ -284,13 +285,24 @@ etags-regen--tags-generate
                                      " ")
                           ;; ctags's etags requires '-L' for stdin input.
                           (if ctags-p "-L" "")
-                          (shell-quote-argument tags-file))))
+                          (shell-quote-argument (if remote
+                                                    (file-local-name tags-file)
+                                                  tags-file))))
+         (tempfile (make-temp-file "etags-regen"))
+         (error-file (make-temp-file "etags-regen-err")))
     (with-temp-buffer
       (mapc (lambda (f)
-              (insert f "\n"))
+              (insert (if remote (file-local-name f) f) "\n"))
             files)
-      (shell-command-on-region (point-min) (point-max) command
-                               nil nil etags-regen--errors-buffer-name t))
+      (write-region nil nil tempfile))
+    (process-file-shell-command command tempfile (list nil error-file))
+    (when (< 0 (file-attribute-size (file-attributes error-file)))
+      (with-current-buffer (get-buffer-create etags-regen--errors-buffer-name)
+        (erase-buffer)
+        (format-insert-file error-file nil)
+        (display-buffer (current-buffer))))
+    (delete-file tempfile)
+    (delete-file error-file)
     (etags-regen--visit-table tags-file root)))
 
 (defun etags-regen--visit-table (tags-file root)
@@ -376,15 +388,21 @@ etags-regen--remove-tag
 (defun etags-regen--append-tags (&rest file-names)
   (goto-char (point-max))
   (let ((options (etags-regen--build-program-options (etags-regen--ctags-p)))
-        (inhibit-read-only t))
+        (inhibit-read-only t)
+        (error-file (make-temp-file "etags-regen-err")))
     ;; XXX: call-process is significantly faster, though.
-    ;; Like 10ms vs 20ms here.  But `shell-command' makes it easy to
-    ;; direct stderr to a separate buffer.
-    (shell-command
+    ;; Like 10ms vs 20ms here.
+    (process-file-shell-command
      (format "%s %s -o - %s"
              etags-regen-program (mapconcat #'identity options " ")
-             (mapconcat #'identity file-names " "))
-     t etags-regen--errors-buffer-name))
+             (mapconcat #'file-local-name file-names " "))
+     nil (list nil error-file))
+    (when (< 0 (file-attribute-size (file-attributes error-file)))
+      (with-current-buffer (get-buffer-create etags-regen--errors-buffer-name)
+        (erase-buffer)
+        (format-insert-file error-file nil)
+        (display-buffer (current-buffer))))
+    (delete-file error-file))
   ;; FIXME: Is there a better way to do this?
   ;; Completion table is the only remaining place where the
   ;; update is not incremental.
diff --git a/lisp/progmodes/etags.el b/lisp/progmodes/etags.el
index f14d91504af..c65ba80334f 100644
--- a/lisp/progmodes/etags.el
+++ b/lisp/progmodes/etags.el
@@ -1745,7 +1745,9 @@ etags--ensure-file
 present in `tags-compression-info-list'.  If the file can't be found,
 signals an error.
 Else, returns the filename that can be visited for sure."
-  (let ((f (locate-file file nil (if auto-compression-mode
+  (let* ((remote (file-remote-p default-directory))
+         (file (if remote (concat remote file) file))
+         (f (locate-file file nil (if auto-compression-mode
 				     tags-compression-info-list
 				   '("")))))
     (unless f
-- 
2.25.1