abo-abo / avy

Jump to things in Emacs tree-style
1.71k stars 109 forks source link

Improve avy-linenum-mode #292

Open lane-s opened 4 years ago

lane-s commented 4 years ago

I'm curious about whether it's possible to make some improvements to avy-linenum-mode, because I think in theory it's actually the best way I can think of to do to line-based operations with a qwerty keyboard (in conjunction with evil-avy-goto-line). This is coming from the perspective of someone who originally used vim before switching to Emacs by way of Spacemacs.

My thought process:

  1. Showing line numbers is nice because you can quickly go to a line using nG where n is the line number. Because this is a vim motion, you can also use it in combination with editing operations in order to do things like "delete up through line 50". Now when you want to perform an operation from point to a given line, you just need to look at the number for the line.
  2. With larger files, using the absolute line number becomes cumbersome because you might have to type 500Gy when you really just want to yank the next 10 lines. This is where relative line numbers are introduced. This guarantees that the line number is small and therefore less cumbersome to type.
  3. avy-goto-line goes a step further: The key insight is that I don't actually care that a line is 10 lines below the current line. I just want to be able to look at a line and use it as a target for an editing operation. avy-goto-line recognizes this, but avy-linenum-mode allows you to mentally prepare to type the hint keys at the same time that you're entering the key combo for avy-goto-line. This makes the experience smoother, faster, and more consistent with showing relative line numbers.

But while this does seem like the best way to do line-based operations in theory, I think avy-linenum-mode still has some issues:

  1. It noticeably slows down navigation. I'm guessing this is due to the way the hints are displayed. I'm guessing it uses an overlay? I wonder if we could somehow use the same mechanism that line-number-mode uses.
  2. The hints are sometimes incorrect when you have multiple buffers open. Using avy-goto-line and typing the hint sometimes takes me to an entirely different line.

These two issues are preventing me from actually using avy-linenum-mode as I'd like to.

abo-abo commented 4 years ago
  1. It noticeably slows down navigation. I'm guessing this is due to the way the hints are displayed. I'm guessing it uses an overlay? I wonder if we could somehow use the same mechanism that line-number-mode uses.

line-number-mode just displays the line number in the mode line. avy-linum-mode re-uses the built-in linum-mode.

  1. The hints are sometimes incorrect when you have multiple buffers open. Using avy-goto-line and typing the hint sometimes takes me to an entirely different line.

Please add a reproduction scenario for this one.

lane-s commented 4 years ago

I'll work on reproducing the issue soon, but regarding performance I used the profiler while moving point and found that avy--linum-update-window is using the vast majority of CPU time.

I need to investigate more, but my guess is that maybe instead of updating the candidates every time point moves, we can just do it when the window layout changes.

lane-s commented 4 years ago

This makes scrolling with avy-linum-mode smooth for me:

(setq avy--linum-strings-cache nil)
(add-to-list 'window-size-change-functions (lambda (frame) (setq avy--linum-strings-cache nil)))

(defun avy--linum-strings ()
  "Get strings for `avy-linum-mode'."
  (when (not avy--linum-strings-cache)
    (setq avy--linum-strings-cache
          (let* ((lines (avy--line-cands))
                 (line-tree (avy-tree lines avy-keys))
                 (line-list nil))
            (avy-traverse
             line-tree
             (lambda (path _leaf)
               (let ((str (propertize (apply #'string (reverse path))
                                      'face 'avy-lead-face)))
                 (when (> (length str) 1)
                   (set-text-properties 0 1 '(face avy-lead-face-0) str))
                 (push str line-list))))
            (nreverse line-list))))
  avy--linum-strings-cache)

I also invalidate the cache when entering avy-linum-mode.

I'm still not really understanding how the line candidates work for multiple buffers, but this solution works pretty well with a single buffer.