Revisiting this old topic, (string-to-number (format-mode-line "%l")
still holds up quite well against (line-number-at-position)
. I found it is also highly cached for "nearby" line positions. For example, operating on /usr/dict/words
(236k lines here):
(let* ((buf (get-buffer "words"))
(win (get-buffer-window buf))
(len (buffer-size buf))
(off 0)
(cnt 20000)
(step (floor (/ (float len) cnt))) line)
(set-window-point win 0)
(redisplay) ; we must start at the top; see note [1]
(measure-time
(dotimes (i cnt)
(set-window-point win (cl-incf off (+ step (random 5))));(random len))
(setq line (string-to-number (format-mode-line "%l" 0 win)))))
(message "Final line: %d (step %d cnt %d)" line step cnt))
This takes about 20s to run through, visiting 20,000 positions spanning the entire file in order. If instead you just look in the vicinity of a position (here ±5000 characters):
(let* ((buf (get-buffer "words"))
(win (get-buffer-window buf))
(len (buffer-size buf))
(off (/ len 2))
(cnt 20000)
(step (floor (/ (float len) cnt))) line)
(set-window-point win off)
(redisplay) ; we must start at the top
(measure-time
(dotimes (i cnt)
(let ((pos (+ off (- (random 10000) 5000))))
(set-window-point win pos);(random len))
(setq line (string-to-number (format-mode-line "%l" 0 win))))))
(message "Final line: %d (step %d cnt %d)" line step cnt))
This only takes ~1/5s or so! So (as you'd expect) it's highly optimized for scrolling to nearby locations (vs jumping across the entire file randomly).
In contrast:
(let* ((buf (get-buffer "words"))
(win (get-buffer-window buf))
(len (buffer-size buf))
(off 0)
(cnt 1000)
(step (floor (/ (float len) cnt))) line)
(measure-time
(dotimes (i cnt)
(with-current-buffer buf
(setq line (line-number-at-pos (cl-incf off step))))))
(message "Final line: %d" line))
takes over 10s (for 20x fewer positions) each and every time. So to summarize, format-mode-line
is ~10x faster than line-number-at-pos
, on a full and fast run through a long file. And for nearby positions, it gives near instantaneous results through local caching.
[1] Before I brought the point back to the top and redisplayed, subsequent full-file runs were taking << 1 second. At first I thought this was some miraculous internal caching of format-mode-line
, but then I noticed that this only happened if you left point at the end of the buffer. In that case set-window-point
doesn't actually move point over such long distances, and format-mode-line
just quickly returns the same line, over and over.