6

Q: How do I find the visual line number of point (so that I can restore it after an operation)?

By “visual line number”, I'm referring to the number N of lines between the top of the window (or the screen) and the cursor, not the top of the buffer.

At first I thought I could calculate N with the following snippet:

(- (line-number-at (point))
   (line-number-at (window-start)))

However, to complicate things, I have sections of the buffer which are invisible. So the snippet above usually returns values much larger than N.


Context:
I need to perform an operation that essentially erases the entire buffer and writes it again, but I want it to be mostly invisible to the user.

Since the buffer contents are created anew, save-excursion doesn't help in this situation. Still, I manage to preserve point position by saving it as a number, instead of a marker.

(let ((point (point)))
  (recreate-buffer)
  (goto-char point))

Q (alternative wording): Is there a similar method I can use to preserve the visual height of the cursor?
By that, I mean that if the cursor is initially on the Nth visible line of the screen, the window should be scrolled after the operation so that this remains true.

Malabarba
  • 22,878
  • 6
  • 78
  • 163
  • How about using the function `(set-window-start (selected-window) my-stored-window-start-pos)` and use the same point that you previously stored -- i.e., `(let ((my-stored-window-start-pos (window-start))) . . .)` – lawlist Nov 25 '14 at 23:49
  • @lawlist That's a valid answer, but it won't quite work at the moment because the invisible sections are no longer invisible after the rebuild. I probably will do that at some point, but it will take a lot more work so I was looking for a quick way of preserving height for now. – Malabarba Nov 25 '14 at 23:55
  • How about `forward-line` with test to see if its visible, and skip without counting if its invisible -- Goto predetermined window-start and then `foward-line` x number of visible lines. X number of visible lines is equal to your proposed test above before erasing the buffer. – lawlist Nov 26 '14 at 01:36
  • @lawlist sounds like it would work. But it'd have to be `next-line`, IIRC forward-line would count invisible lines. – Malabarba Nov 26 '14 at 01:38
  • I was thinking of something like using your calculation (e.g., `N`) and `set-window-start` at previously recorded position. Then goto that window start and commence: `(let ((count 0)) (catch 'done (while t (forward-line 1) (unless (invisible-p (point-at-bol)) (setq count (1+ count))) (when (= count N) (throw 'done nil)))))` – lawlist Nov 26 '14 at 01:47
  • @lawlist ah yes, something like that should work. – Malabarba Nov 26 '14 at 01:49

1 Answers1

5

You can use (count-screen-lines &optional beg end count-final-newline window) (manual page) to find the number of lines shown between two points.

You can do the following to re-create the point's position on the screen:

(let ((lines-down (count-screen-lines nil (point) t))
      (lines-from-top-of-window (count-screen-lines (window-start) (point) t))
      (diff-beg-of-line (- (point) (line-beginning-position))))
  ;; Code that redraws screen goes here:
  ;; <code></code>
  ;; Now, to find your old position:
  (if (equal lines-down 0)
      ;; When the point is 1, lines-down is 0. In all other cases,
      ;; `count-screen-lines' is 1-indexed by the line. Set
      ;; `lines-down' to 1 to compensate for this edge case.
      (setq lines-down 1))
  (move-to-window-line (- lines-down 1))
  (recenter lines-from-top-of-window)
  (goto-char (+ (point) diff-beg-of-line)))
nosefrog
  • 795
  • 6
  • 9
  • Thanks, `count-screen-lines` is exactly what I needed. But the implementation can be a lot simpler: `(let ((point (point)) (line (count-screen-lines (window-start) (point)))) (perform-operation) (goto-char point) (recenter line))` – Malabarba Nov 26 '14 at 12:13
  • Ah, does `(point)` not count invisible characters? If that's the case, I'll update my example. – nosefrog Nov 26 '14 at 15:53
  • point just counts everything (it's the logical coordinate, not the visible one), but that's not a problem. I want point to be exactly where it was, and then recenter the window so that the line in which point is located represents the same screen line which it was before. – Malabarba Nov 26 '14 at 16:00