5

I am writing a major-mode which displays a list to the user. It is similar to tabulated-list-mode, except each entry takes up 3 lines, as opposed to just one:

  0   0 Ctrl-} key in emacs 
        5m ago [keymap] 

  6   1 Set the background of org-exported <code> blocks according to theme 
        40m ago [org-mode] [org-export] [html] 

  7  10 How to switch between windows quickly? 
        79m ago [window] [navigation] 

  3   1 Org-mode: Exclude specific headlines from TOC when exporting 
        96m ago [org-mode] [org-export] 
 

I've defined all movement keys so that the user navigates by entry instead of by line, but there's one thing I can't quite fix. Whenever the window is split below or above, there's a decent chance that the first line in the (original) window will not be the first line of an entry. This happens because the buffer is scrolled to make sure point is still visible after the split.

Here's an example of what I might get when splitting (note this can happen even if I ensure the size is a multiple of 3).

        5m ago [keymap] 

  6   1 Set the background of org-exported <code> blocks according to theme 
        40m ago [org-mode] [org-export] [html] 

  7  10 How to switch between windows quickly? 

I would like this buffer to reposition itself, every time the window size changes, so as to ensure the first visible line is always a (multiple of 3) minus 1.
(Whether it scrolls up or down is irrelevant, as long as point is not moved.)

So the example above should display something like.

  0   0 Ctrl-} key in emacs 
        5m ago [keymap] 

  6   1 Set the background of org-exported <code> blocks according to theme 
        40m ago [org-mode] [org-export] [html] 
 

Is this possible?

EDIT: Note, I'd like to scroll the window as little as possible---always 1 or 2 lines.

  • "Whenever the window is split below or above, there's a 2/3 chance that the first line in the window will not be the first line of an entry." Just to clarify: Do you want the buffer in the original window to reposition itself? Or do you want the repositioning to affect the buffer in the newly created window? – itsjeyd Nov 14 '14 at 12:38
  • @itsjeyd Original is what I need. Although, I think both should be simultaneously possible. – Malabarba Nov 14 '14 at 12:40
4

Here is an alternative function that respects the major mode and works with window-size-change-functions:

(defun reposition-buffer (frame)
  (when (eq major-mode 'fundamental-mode) ; Substitute name of your major-mode here
    (save-excursion
      (let* ((current-line (line-number-at-pos (point)))
             (offset (mod current-line 3)))
        (cond ((= offset 0) (previous-line 2))
              ((= offset 2) (previous-line 1))
              (t nil))
        (recenter-top-bottom 0)))))

(add-hook 'window-size-change-functions 'reposition-buffer)

The logic is as follows (<n> denotes a line number):

<1> 0   0 Ctrl-} key in emacs 
<2>       5m ago [keymap] 
<3>
<4> 6   1 Set the background of org-exported <code> blocks according to theme 
<5>       40m ago [org-mode] [org-export] [html]
<6>
  • If point is in line 1 or 4 (n modulo 3 == 1) , we don't have to move, as each of these lines starts a new entry.

  • If point is in line 2 or 5 (n modulo 3 == 2), we have to move up one line to get to the beginning of the corresponding entry.

  • If point is in line 3 or 6 (n modulo 3 == 0), we have to move up two lines to get to the beginning of the corresponding entry.

As a last step, we recenter the current line to the top of the window.

  • 2
    Nice. Minor suggestion: change your lowercase l variable to line (or something), as lowercase l looks a lot like a the number 1. – Dan Nov 14 '14 at 13:03
  • @Dan Yes, you're right! Fixed. – itsjeyd Nov 14 '14 at 13:11
  • Thanks. This is a good answer. But it always scrolls until point is at the top, and I'd really like to scroll as little as possible. I'll probably end up combining the two. :-) – Malabarba Nov 14 '14 at 13:13
  • @Malabarba I'm not sure what you mean by "it always scrolls until point is at the top"? For me, point stays in the same spot. Are you referring to the entry point is in? – itsjeyd Nov 14 '14 at 13:17
  • @itsjeyd Sorry if I expressed poorly. The position of point doesn't move in the buffer (that's great :-). However, if point is far from the top of the window, the buffer is scrolled all the way up, until the current entry is at the top of the window. I would like to just scroll up by just 0, 1, or 2 lines. Still, doing this is a very trivial extension from your code, and your answer explained what I needed, so I'm accepting it anyway. – Malabarba Nov 14 '14 at 13:24
3

The following function checks a window to see if it's top line is a multiple of three, and scrolls if needed to make it so:

(defun first-window-line-in-threes (&optional window)
  "Make sure that the first line in the window is a multiple of
three (0-based rather than 1-based).  Scroll if necessary, but
leave point where it is."
  (interactive)
  (let ((offset (mod (1- (line-number-at-pos (window-start window))) 3)))
    (unless (zerop offset)
      (scroll-down offset)
      (forward-line offset))))

It looks like the relevant hooks/functions are window-configuration-change-hook and window-size-change-functions. Using the former almost works, but it seems to respond to curious events like minibuffer resizing. The latter requires a bit of fiddling, and I hadn't quite gotten it right before dashing out the door.

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.