When a directory contains many files, more than the visible lines of the window, use end-of-buffer to go to end of buffer, then press g to revert-buffer, one can see the last line is recentered to the middle of the window.

How I can prevent this?


g reverts the Dired buffer. It invokes function revert-buffer, which uses the function bound to variable revert-buffer-function. In a Dired buffer, that function is dired-revert.

Following what dired-revert does (e.g. using debug-on-entry) you find that it is dired-goto-file that scrolls the listing, to put the file where the cursor was when you hit g in the middle of the window vertically. It does this because the last thing it does is call search-forward, and that function does the recentering.

You can't prevent it from doing that - search-forward is coded in C, and there's no parameter to modify this part of its behavior (and I don't know of any global variable that will do so).

But you can make the buffer/window scroll, when dired-revert is done, that is, after search-forward centers the cursor vertically, to put the cursor's file where you want it in the window. You can do that by advising dired-revert with an :after function that scrolls the window the way you want.

The function you want, to scroll the window, is recenter. Its doc (C-h f recenter) tells you that you can pass it a numeric arg ARG, to put "putting point on screen line ARG relative to the selected window." And if ARG is negative then it does so relative to the bottom of the window. So an ARG value of -1 puts the line you want at the bottom of the window - which is where I guess you're asking to put it.

;; Function to use at end of `dired-revert'.
(defun foo (_arg _noconfirm)
  "Put cursor line at bottom of window."
  (recenter -1))

;; Do `foo' at the end of `dired-revert'.
(advice-add 'dired-goto-file :after 'foo)

Of course, maybe you don't always want the current line (where the cursor is) to end up at the bottom of the window. In that case, you'll have to do something else.

You can, for example, find out where in the window, vertically, the cursor is before you hit g, and then have a function such as foo recenter to that vertical window position, instead of always using -1 as the arg to recenter.

I don't think there's a function that gives you the window line, that is, the current line number relative to the window beginning. You can do that this way:

(defun window-line-at-point ()
  (let ((bow  (save-excursion (move-to-window-line 0) (point))))
    (count-screen-lines bow (point))))

And then you can move to that window line using function recenter.

But you need to use window-line-at-point at the outset, when dired-revert is called, so you need to advise that function (with :around) to get the window line, bind it to a variable, and then call recenter to move to that window line at the end. This does that:

(defun foo (old-fn _a _b)
  (let ((curr-line  (window-line-at-point)))
    (funcall old-fn)
    (recenter (1- curr-line))))

(advice-add 'dired-revert :around 'foo)

(Alternatively, you could define your own revert function, and bind that to revert-function in Dired buffers, instead of it being bound to dired-revert.)

  • Thanks for the great details! The last solution works perfectly in a dired buffer. But there's a small bug when opening a new dired buffer using dired-jump: save-excursion: move-to-window-line called from unrelated buffer. – stackunderflow Dec 1 '20 at 9:50
  • Then that would need to be debugged. Maybe the save-excursion just needs to be wrapped in a save-window-excursion; dunno. – Drew Dec 1 '20 at 17:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.