abo-abo / avy

Jump to things in Emacs tree-style
1.7k stars 108 forks source link

Some useful functions (with suggested optimizations) #373

Open nameiwillforget opened 5 months ago

nameiwillforget commented 5 months ago

I've written some functions that I think have the potential to be very useful and that I thought could be incorporated:

(defun avy-select-by-same-function-and-apply (x y)
  "This function asks for a command and an avy function as chosen using the avy-function-map, 
then uses the chosen avy function twice to select an interval and apply the input command to it. 
Through this method, simple editing of areas on the screen can be done without having to move point. 
If you delete an area surrounding point, have the first selected position be before the second one."
  (interactive "kCommand: \nkAvy function: ")
  (let ((initpos (point)) (initend (buffer-size)))
    (call-interactively (lookup-key avy-function-map y))
    (call-interactively #'set-mark-command)
    (let ((firstpos (point)))
      (call-interactively (lookup-key avy-function-map y))
      (let ((secondpos (point)))
        (call-interactively (lookup-key (make-composed-keymap (current-active-maps t)) x))
        (if (or (= initend (buffer-size)) (< initpos firstpos))
            (goto-char initpos)
          (if (< secondpos initpos)
              (goto-char (- initpos (- secondpos firstpos)))
            (goto-char (- initpos (- initpos firstpos)))
            ))
        ))))

(defun avy-select-by-different-functions-and-apply (x y z)
  "This function asks for a command and two avy functions as chosen using the avy-function-map, 
then uses the chosen avy functions to select an interval and apply the input command to it. 
Through this method, simple editing of areas on the screen can be done without having to move point. 
If you delete an area surrounding point, have the first selected position be before the second one."
  (interactive "kCommand: \nkFirst avy function: \nkSecond avy function: ")
  (let ((initpos (point)) (initend (buffer-size)))
    (call-interactively (lookup-key avy-function-map y))
    (call-interactively #'set-mark-command)
    (let ((firstpos (point)))
      (call-interactively (lookup-key avy-function-map z))
      (let ((secondpos (point)))
        (call-interactively (lookup-key (make-composed-keymap (current-active-maps t)) x))
        (if (or (= initend (buffer-size)) (< initpos firstpos))
            (goto-char initpos)
          (if (< secondpos initpos)
              (goto-char (- initpos (- secondpos firstpos)))
            (goto-char (- initpos (- initpos firstpos)))
            ))
        ))))

(defun avy-select-to-point-and-apply (x y)
  "This function asks for a command and an avy function as chosen using the avy-function-map, 
then uses the chosen avy function to select the interval between the current position of point and 
the position chosen through avy, and then apply the input command to it. Through this method, 
simple editing of areas before or after point can be done without having to move point."
  (interactive "kCommand: \nkAvy function: ")
  (let ((initpos (point)) (initend (buffer-size)))    
    (call-interactively #'set-mark-command)
    (call-interactively (lookup-key avy-function-map y))
    (let ((firstpos (point)))
      (call-interactively (lookup-key (current-global-map) x))
      (if (or (= initend (buffer-size)) (< initpos firstpos))
          (goto-char initpos)
        (goto-char (- initpos (- initpos firstpos))))))
  )

(defun avy-goto-char-after-timer ()
  "This command goes to a position indicated by avy-goto-char-timer, then goes one character forward. 
This is useful when avy is used to, for instance, delete a letter in a word, so that one doesn't have to think 
about targeting the letter after to arrive at the right position."
  (interactive)
  (call-interactively #'avy-goto-char-timer)
  (forward-char))

(defvar-keymap avy-function-map
  :doc "Map for choosing avy-functions in extended avy-functions like avy-select-by-same-function-and-apply and its variants."
  "f" #'avy-goto-char-after-timer
  "r" #'avy-goto-char-timer

  "j" #'avy-goto-word-1

  "i" #'avy-goto-char-in-line

  "r" #'avy-goto-line
  "l" #'avy-goto-end-of-line

  "c" #'avy-position-by-word-1)

When I tested them they worked but seemed to stress the CPU to a surprising degree. I looked a bit into the internals of avy and thought the code could simplify by introducing variants of the avy command that use the action-argument of avy-jump to print out a position, like

(defun avy-position-by-word-1 (char &optional arg beg end symbol)
  "Select the region between point and a chosen visible CHAR at a word start.
The window scope is determined by `avy-all-windows'.
When ARG is non-nil, do the opposite of `avy-all-windows'.
BEG and END narrow the scope where candidates are searched.
When SYMBOL is non-nil, jump to symbol start instead of word start."
  (interactive (list (read-char "char: " t)
                     current-prefix-arg))
  (avy-with avy-goto-word-1
    (let* ((str (string char))
           (regex (cond ((string= str ".")
                         "\\.")
                        ((and avy-word-punc-regexp
                              (string-match avy-word-punc-regexp str))
                         (regexp-quote str))
                        ((<= char 26)
                         str)
                        (t
                         (concat
                          (if symbol "\\_<" "\\b")
                          str)))))
      (avy-jump regex
                :window-flip arg
                :beg beg
                :end end
                :action 'print))))

However, when I tried to use this function in a simplification of avy-select-by-same-function-and-apply like this

(defun avy-select-by-same-function-and-apply-variant (x y)
  "This function asks for a command and an avy function as chosen using the avy-function-map, 
then executes the chosen avy function twice to select an interval and apply the input command to it. 
Through this method, simple editing of areas on the screen can be done without having to move point."
  (interactive "kCommand: \nkAvy function: ")
  (let ((bgn (call-interactively (lookup-key avy-function-map y)))
        (end (call-interactively (lookup-key avy-function-map y))))
    (call-interactively (lookup-key (make-composed-keymap (current-active-maps t)) x) bgn end)
    ))

where (call-interactively (lookup-key avy-function-map y)) is used to choose avy-position-by-word-1 in the avy-function-map to define the position of bgn and end, I receive the error

let: Wrong type argument: vectorp, (8077 . 8078)

And at that point I'm not sure what to do anymore. In any case, the functions as I gave them above work and I think are extremely convenient: instead of jumping to a position, activating the mark, then jumping to another, applying some command to the selected region, just stay at the initial position, use avy to select an interval and apply a command to it through a keybind in your current keymaps! The many ifs and lets in the commands I wrote are mostly to ensure point lands in the right position if some text is deleted before point.

nameiwillforget commented 5 months ago

I managed to simplify the functions a bit by using the local mark ring to remember the initial positions:

(defun avy-act-by-same-function (x y)
  "This function asks for a command and an avy function as chosen
using the avy-function-map, then uses the chosen avy function twice to
select an interval and apply the input command to it. Through this
method, simple editing of areas on the screen can be done without
having to move point. If you delete an area surrounding point, have
the first selected position be before the second one."
  (interactive "kCommand: \nkAvy function: ")
  (call-interactively (lookup-key avy-function-map y))
  (call-interactively #'set-mark-command)
  (call-interactively (lookup-key avy-function-map y))
  (call-interactively (lookup-key (make-composed-keymap (current-active-maps t)) x))
  (call-interactively #'pop-to-mark-command)
  (call-interactively #'pop-to-mark-command)
  )

(defun avy-act-by-different-functions (x y z)
  "This function asks for a command and two avy functions as chosen
using the avy-function-map, then uses the chosen avy functions to
select an interval and apply the input command to it. Through this
method, simple editing of areas on the screen can be done without
having to move point. If you delete an area surrounding point, have
the first selected position be before the second one."
  (interactive "kCommand: \nkFirst avy function: \nkSecond avy function: ")
  (call-interactively (lookup-key avy-function-map y))
  (call-interactively #'set-mark-command)
  (call-interactively (lookup-key avy-function-map z))
  (call-interactively (lookup-key (make-composed-keymap (current-active-maps t)) x))
  (call-interactively #'pop-to-mark-command)
  (call-interactively #'pop-to-mark-command)
  )

(defun avy-act-to-point (x y)
  "This function asks for a command and an avy function as chosen
using the avy-function-map, then uses the chosen avy function to
select the interval between the current position of point and the
position chosen through avy, and then apply the input command to it.
Through this method, simple editing of areas before or after point can
be done without having to move point."
  (interactive "kCommand: \nkAvy function: ")
  (call-interactively #'set-mark-command)
  (call-interactively (lookup-key avy-function-map y))
  (call-interactively (lookup-key (make-composed-keymap (current-active-maps t)) x))
  (call-interactively #'pop-to-mark-command)
  )

Edit: I just simplified the names a bit.