Subject: Re: bug#63648: 29.0.90; project.el: with switch-use-entire-map, switch-project errors on non-project commands

From: Dmitry Gutov <[email-masked]>
Date: Thu, 26 Oct 2023 01:26:52 +0300

On 25/10/2023 19:53, Juri Linkov wrote:
>> +         ;; Variation: could be a separate command, or an option.
>> +         ;; (command (let ((overriding-local-map project-prefix-map))
>> +         ;;            (key-binding (read-key-sequence
>> +         ;;                          (format "[execute in %s]:" (project-root pr)))
>> +         ;;                         t)))
> 
> Thanks, it works nicely.
> Any reason not to use this by default?

Nothing critical, but it might not fit the expectations without 
additional instructions in the prompt, or it can be unnecessary if the 
user had reached this command through 'C-x p o'.

In the latter case there is also a small chance that the user had set up 
some advanced sub-maps inside project-prefix-map which would shadow some 
global bindings. So maybe a separate command is best. Please see how you 
like the attached new version together with

   (setq project-switch-commands #'project-prefix-or-any-command)

I'm not sure about project-prefix-or-any-command's prompt, though 
(phrasing feels awkward). Improvements welcome.

>> +      (if found
>> +          (let ((project-current-directory-override root))
>> +            (call-interactively command))
>> +        (let ((default-directory root))
>> +          (call-interactively command))))))
> 
> Not sure why need to let-bind project-current-directory-override here.
> It seems to work with and without it.

You mean with found=nil? Indeed, rebinding 
project-current-directory-override instead of just doing that with 
default-directory is critical for only a small subset of commands (such 
as project-switch-buffer or bug#58784). So this will be a 99% solution 
that, however, leads to more straightforward code.

BTW, let me know if you prefer the "prefix command" style from your last 
patch for this command. My main sticking point with it was the change of 
logic used to indicate a different project root, but it can just as well 
be transplanted there. So if the prefix command approach is better for 
some scenarios, we can switch to it.
diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el
index fda1081eb62..360137e67bb 100644
--- a/lisp/progmodes/project.el
+++ b/lisp/progmodes/project.el
@@ -847,6 +847,7 @@ project-prefix-map
     (define-key map "G" 'project-or-external-find-regexp)
     (define-key map "r" 'project-query-replace-regexp)
     (define-key map "x" 'project-execute-extended-command)
+    (define-key map "o" 'project-any-command)
     (define-key map "\C-b" 'project-list-buffers)
     map)
   "Keymap for project commands.")
@@ -1813,6 +1814,50 @@ project-execute-extended-command
   (let ((default-directory (project-root (project-current t))))
     (call-interactively #'execute-extended-command)))
 
+;;;###autoload
+(defun project-any-command (&optional overriding-map prompt-format)
+  "Run the next command in the current project.
+If the command is in `project-prefix-map', it gets passed that
+info with `project-current-directory-override'.  Otherwise,
+`default-directory' is temporarily set to the current project's
+root.
+
+If OVERRIDING-MAP is non-nil, it will be used as
+`overriding-local-map' to provide shorter bindings from that map
+which will take priority over the global ones."
+  (interactive)
+  (let* ((pr (project-current t))
+         (prompt-format (or prompt-format "[execute in %s]:"))
+         (command (let ((overriding-local-map overriding-map))
+                    (key-binding (read-key-sequence
+                                  (format prompt-format (project-root pr)))
+                                 t)))
+         (root (project-root pr))
+         found)
+    (when command
+      ;; We could also check the command name against "\\`project-",
+      ;; and/or (get command 'project-command).
+      (map-keymap
+       (lambda (_evt cmd) (if (eq cmd command) (setq found t)))
+       project-prefix-map)
+      (if found
+          (let ((project-current-directory-override root))
+            (call-interactively command))
+        (let ((default-directory root))
+          (call-interactively command))))))
+
+;;;###autoload
+(defun project-prefix-or-any-command ()
+  "Run the next command in the current project.
+Works like `project-any-command', but also mixes in the shorter
+bindings from `project-prefix-map'."
+  (interactive)
+  (project-any-command project-prefix-map
+                       (concat
+                        "[in %s] Press "
+                        (project--keymap-prompt)
+                        " or other:")))
+
 (defun project-remember-projects-under (dir &optional recursive)
   "Index all projects below a directory DIR.
 If RECURSIVE is non-nil, recurse into all subdirectories to find
@@ -1891,7 +1936,8 @@ project-switch-commands
     (project-find-regexp "Find regexp")
     (project-find-dir "Find directory")
     (project-vc-dir "VC-Dir")
-    (project-eshell "Eshell"))
+    (project-eshell "Eshell")
+    (project-any-command "Other"))
   "Alist mapping commands to descriptions.
 Used by `project-switch-project' to construct a dispatch menu of
 commands available upon \"switching\" to another project.