diff --git a/CHANGELOG.md b/CHANGELOG.md index f50cfdb00..dddf9267c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ ### Bugs fixed +- [#2170](https://github.com/bbatsov/projectile/pull/2170): Navigating a search results buffer while the scan is still running is no longer undone by the next chunk. Each redraw ended by going to the top of the buffer, so point jumped back on every batch of matches; it now stays where you left it. - [#2167](https://github.com/bbatsov/projectile/pull/2167): The Emacs Lisp search scanner no longer decompresses or decrypts the files it reads. It went through `file-name-handler-alist`, so every archive in the candidate set was run through gzip and every `.gpg` handed to EPA - work discarded a moment later as binary, and on an encrypted file a passphrase prompt in the middle of a project search. - Matches inside compressed files are no longer reported by that scanner, which is what the ripgrep path already did. - [#2162](https://github.com/bbatsov/projectile/pull/2162): Listing another project's files now applies that project's own ignore rules instead of the rules of whichever project you happen to be visiting. `projectile-find-file-in-known-projects` and the sibling commands used to filter every other project by the current one's dirconfig, and with caching on they stored that wrong list under the other project's key - so an ordinary `projectile-find-file` there kept offering ignored files, and with persistent caching it survived a restart. diff --git a/doc/modules/ROOT/pages/usage.adoc b/doc/modules/ROOT/pages/usage.adoc index 61295ee4f..6cc50b2e9 100644 --- a/doc/modules/ROOT/pages/usage.adoc +++ b/doc/modules/ROOT/pages/usage.adoc @@ -325,6 +325,9 @@ results stream in rather than happening on every chunk. The redraw that settles a finished scan is unconditional, so throttling only ever delays an intermediate state. Set the option to nil to redraw on every chunk. +A redraw keeps point where you left it, so you can start reading and +navigating the results while the rest of them are still arriving. + kbd:[!] applies the enabled matches with no external dependency. If you'd rather edit the results as text, kbd:[e] exports the enabled matches (the same set kbd:[!] would act on) to a `+*projectile-grep*+` buffer in `grep-mode`, diff --git a/projectile.el b/projectile.el index d053901b8..e6de3686f 100644 --- a/projectile.el +++ b/projectile.el @@ -9799,7 +9799,7 @@ killed BUFFER (leaving no work behind) and against \\`C-g' during a chunk (with-current-buffer buffer (setq projectile-replace--scanning nil projectile-replace--scan-timer nil) - (funcall projectile-replace--render-function) + (projectile-replace--render-preserve) (when on-done (funcall on-done buffer))))) (quit (when (buffer-live-p buffer) @@ -9969,9 +9969,13 @@ async scan streams matches in." (goto-char (point-min)))) (defun projectile-replace--render-preserve () - "Redraw the results buffer, keeping point on the same line." + "Redraw the results buffer, keeping point on the same line. + +Restoring by line rather than by position is what makes this safe while a +scan is streaming: matches are appended and a file header keeps its line +count when its tally grows, so the lines already on screen do not move." (let ((line (line-number-at-pos))) - (projectile-replace--render) + (funcall projectile-replace--render-function) (goto-char (point-min)) (forward-line (1- line)))) @@ -10817,7 +10821,7 @@ against a killed BUFFER." (setq projectile-replace--scanning nil projectile-replace--scan-process nil projectile-replace--scan-timer nil) - (funcall projectile-replace--render-function) + (projectile-replace--render-preserve) (when on-done (funcall on-done buffer))))) (defun projectile-search--gather-rg (buffer term on-done) @@ -10957,7 +10961,7 @@ unconditional, so a skipped intermediate draw is never the last word." (>= (- now projectile-replace--last-render) projectile-search-render-interval)) (setq projectile-replace--last-render now) - (funcall projectile-replace--render-function)))) + (projectile-replace--render-preserve)))) (defun projectile-replace--start (buffer candidates regexp on-done) "Fill BUFFER's match list by scanning CANDIDATES for REGEXP. diff --git a/test/projectile-search-review-test.el b/test/projectile-search-review-test.el index 4245ff366..73193b555 100644 --- a/test/projectile-search-review-test.el +++ b/test/projectile-search-review-test.el @@ -590,6 +590,57 @@ REGEXP-P selects `projectile-search-regexp-review'." (dotimes (_ 5) (projectile-replace--render-progress)) (expect 'projectile-search--render :to-have-been-called-times 5)))) +(describe "point while results stream in" + (it "keeps point where the user put it across a streaming redraw" + ;; The buffer is redrawn from scratch, and the redraw ends by going to + ;; point-min - so navigating while a scan was still running used to be + ;; undone by the next chunk. + (projectile-test-with-project + (("a.txt" . "foo\nfoo\nfoo\n")) + (let ((buf (get-buffer-create projectile-search-buffer-name)) + (dir default-directory)) + (with-current-buffer buf + (projectile-replace--seed buf #'projectile-search-mode + dir "foo" "foo" nil t t) + (setq projectile-replace--matches + (mapcar (lambda (i) + (projectile-replace--match-create + :file (expand-file-name "a.txt" dir) + :line i :column 0 :string "foo" + :context "foo here" :enabled t)) + (number-sequence 1 6))) + (projectile-search--render) + (goto-char (point-min)) + (dotimes (_ 3) (projectile-replace--goto-next-match)) + (let ((line (line-number-at-pos)) + (match (projectile-replace--match-at-point))) + (expect match :to-be-truthy) + ;; another chunk lands and the buffer is redrawn + (setq projectile-replace--matches + (append projectile-replace--matches + (list (projectile-replace--match-create + :file (expand-file-name "a.txt" dir) + :line 7 :column 0 :string "foo" + :context "foo here" :enabled t)))) + (let ((projectile-search-render-interval nil)) + (projectile-replace--render-progress)) + (expect (line-number-at-pos) :to-equal line) + (expect (projectile-replace--match-line + (projectile-replace--match-at-point)) + :to-equal (projectile-replace--match-line match))))))) + + (it "leaves point at the top when the user has not moved it" + (assume (executable-find "rg") "ripgrep is not installed") + (projectile-test-with-project + (("a.txt" . "foo bar\n")) + (let ((buf (get-buffer-create projectile-search-buffer-name))) + (projectile-replace--seed buf #'projectile-search-mode + default-directory "foo" "foo" nil t t) + (projectile-search--gather-rg buf "foo" nil) + (projectile-search-review-test--wait buf) + (with-current-buffer buf + (expect (line-number-at-pos) :to-equal 1)))))) + (describe "streaming redraw throttling" (it "always redraws when a scan finishes, however long the interval" (assume (executable-find "rg") "ripgrep is not installed")