From c0ba8579ce95be6c5d59fbc1ca5f97b5043bad67 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Wed, 8 Jul 2026 14:33:55 +0300 Subject: [PATCH] Replace the grep/ack run-with-timer hack with helm-run-after-exit `helm-projectile-grep' (and `helm-projectile-ack') deferred the actual search through `(run-with-timer 0.01 ...)' - a fixed 10ms guess so that, when the command was invoked as an action from a Helm session, the new session wouldn't start before the current one finished tearing down. This dated back to the initial commit. Route both through `helm-projectile--run-grep-or-ack', which defers via `helm-run-after-exit' when a Helm session is live (the codebase's own idiom for this, already used for the etags binding) and runs the search directly otherwise. No more arbitrary latency on the standalone commands, and the reason for the deferral is now explicit. --- CHANGELOG.md | 1 + helm-projectile.el | 17 +++++++++++++---- test/helm-projectile-test-helper.el | 9 +++++---- test/helm-projectile-test.el | 20 ++++++++++++++++++++ 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3358cf9..759f6ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ ### Internal +* Replace the fixed `run-with-timer` delay in `helm-projectile-grep`/`helm-projectile-ack` (used so a search invoked as an action didn't start until the current Helm session tore down) with `helm-run-after-exit` when a session is live, and a direct call otherwise. No more arbitrary 10ms latency on the standalone commands, and the deferral is now explicit. * Tidy up top-level dependencies: `require` `grep` from the helpers that actually use `grep-find-ignored-files`/`-directories` (`helm-projectile--ignored-files`/`-directories`) instead of relying on it being pulled in transitively, and drop the unused `helm-locate` and `helm-global-bindings` requires. (No startup-time change - Helm loads all three transitively anyway; this just makes the dependencies honest.) * Drive `helm-projectile-toggle`'s command remaps from a single table (`helm-projectile--command-remaps`) instead of two hand-maintained copies of ~20 `define-key` calls. * Extract the remote virtual-Dired guard duplicated across the three Dired file actions into a `helm-projectile--with-virtual-dired` macro. diff --git a/helm-projectile.el b/helm-projectile.el index dd257e3..ea094ad 100644 --- a/helm-projectile.el +++ b/helm-projectile.el @@ -1558,6 +1558,17 @@ This function is obsolete; use `helm-projectile-mode' instead." (interactive) (helm-projectile-mode -1)) +(defun helm-projectile--run-grep-or-ack (&rest args) + "Call `helm-projectile-grep-or-ack' with ARGS. +When invoked from an action of a live Helm session, defer the search +until that session has exited (via `helm-run-after-exit'), because a new +Helm session must not start while the current one is tearing down. +Otherwise run it right away. This replaces an old `run-with-timer' that +guessed a fixed 10ms delay for the same purpose." + (if helm-alive-p + (apply #'helm-run-after-exit #'helm-projectile-grep-or-ack args) + (apply #'helm-projectile-grep-or-ack args))) + ;;;###autoload (defun helm-projectile-grep (&optional dir files) "Helm version of `projectile-grep'. @@ -1569,8 +1580,7 @@ prefix argument then ask for FILES." (include (if (equal current-prefix-arg '(4)) (read-string (projectile-prepend-project-name "Grep in: ")) files))) - (run-with-timer 0.01 nil - #'helm-projectile-grep-or-ack project-root nil nil nil include))) + (helm-projectile--run-grep-or-ack project-root nil nil nil include))) (defun helm-projectile--wildcard-to-ack-match (wildcard) "Transform WILDCARD into a form expected by \"match:\" filter of \"ack\"." @@ -1629,8 +1639,7 @@ Use `helm-projectile-rg' or `helm-projectile-ag' instead." (helm-grep-default-command helm-ack-grep-executable)) (helm-grep-read-ack-type)) types))) - (run-with-timer 0.01 nil - #'helm-projectile-grep-or-ack project-root t ignored helm-ack-grep-executable include))) + (helm-projectile--run-grep-or-ack project-root t ignored helm-ack-grep-executable include))) (make-obsolete 'helm-projectile-ack "use `helm-projectile-rg' or `helm-projectile-ag' instead." diff --git a/test/helm-projectile-test-helper.el b/test/helm-projectile-test-helper.el index a6b69fe..3d7b0a2 100644 --- a/test/helm-projectile-test-helper.el +++ b/test/helm-projectile-test-helper.el @@ -91,11 +91,12 @@ plain three-slot string so only the ignore globs vary." (defun helm-projectile-test--ack-args () "Return the args `helm-projectile-ack' would hand to `helm-projectile-grep-or-ack'. -`run-with-timer' is stubbed to capture them; the returned list is +The latter is stubbed to capture them (with no live Helm session, the +call runs straight through); the returned list is \(PROJECT-ROOT USE-ACK-P IGNORED ACK-EXECUTABLE INCLUDE)." - (let (args) - (cl-letf (((symbol-function 'run-with-timer) - (lambda (_delay _repeat _fn &rest rest) (setq args rest)))) + (let (args (helm-alive-p nil)) + (cl-letf (((symbol-function 'helm-projectile-grep-or-ack) + (lambda (&rest rest) (setq args rest)))) (helm-projectile-ack)) args)) diff --git a/test/helm-projectile-test.el b/test/helm-projectile-test.el index 5f6933c..8f67e73 100644 --- a/test/helm-projectile-test.el +++ b/test/helm-projectile-test.el @@ -406,6 +406,26 @@ (expect (helm-projectile-test--ag-command "ag" "--foo") :to-equal "ag --ignore TAGS --ignore build/ --foo %s %s %s"))) +(describe "helm-projectile--run-grep-or-ack" + ;; Replaces the old `run-with-timer' hack: run immediately when no Helm + ;; session is live, but defer via `helm-run-after-exit' when invoked as an + ;; action from one (so a new session doesn't start mid-teardown). + (it "runs the search immediately when no Helm session is live" + (spy-on 'helm-projectile-grep-or-ack) + (spy-on 'helm-run-after-exit) + (let ((helm-alive-p nil)) + (helm-projectile--run-grep-or-ack "/proj/" nil nil nil nil)) + (expect 'helm-projectile-grep-or-ack :to-have-been-called) + (expect 'helm-run-after-exit :not :to-have-been-called)) + + (it "defers via helm-run-after-exit when a Helm session is live" + (spy-on 'helm-projectile-grep-or-ack) + (spy-on 'helm-run-after-exit) + (let ((helm-alive-p t)) + (helm-projectile--run-grep-or-ack "/proj/" nil nil nil nil)) + (expect 'helm-run-after-exit :to-have-been-called) + (expect 'helm-projectile-grep-or-ack :not :to-have-been-called))) + (describe "helm-projectile-ack deprecation" ;; ack is superseded by rg/ag and Projectile dropped `projectile-ack'; ;; the command is kept working but marked obsolete.