7

Visual Studio has this nice feature where you can put the point on something, press C-f3 and it'll both find the next instance of the thing under the point and it'll remember it. You can then press f3 to find the next instance, and S-f3 to search for that same thing, but backwards.

I can do the same thing in Emacs using some elisp that I wrote. (It's bulky, and I'm sure bad, so I'll omit it).

What I would like to do is to enable similar functionality for, say, f2, f3, f4, and f5. Thus, pressing C-f2 searches for the thing under the point by saving that thing into a variable / association array / symbol somewhere, pressing C-f3 on a different thing causes emacs to save that second thing to be saved in to a distinct variable, and so I can search for the first thing by pressing f2 in the future, and I can search for the second thing by pressing f3 in the future.

I'd love to create a single function for the Control-X, another for just plain X, and a third for Shift-X, but I'm not sure how to approach this.

Is it better to have to have the function ask for the current key press (a la this-single-command-keys), or to find a way to pass a parameter from to the interactive function (without prompting the user, since they've already pressed a key)?

How does one pass additional information into an interactive function?

  • The question is not very clear, IMO. What info and how do you want to pass it? You can use a prefix argument to pass info interactively. A command can prompt for info (you don't want that, you said). A function can get info from global variables or from a file or... What is it that you want to pass, when, and how? If you have some code, maybe show it - that's likely to be more clear than your question (so far), IMO. – Drew Jun 30 '18 at 4:38
  • 4
    @Drew I think once you get past the misleadingly general title, there's a specific design problem there: multiple commands that do exactly the same thing except that they use a different variable to store state from one invocation to the next. – Gilles Jun 30 '18 at 7:56
  • @Gilles: Which is why I didn't vote to close it as unclear. It could still be clearer. For example, if variables are to be used that way, then what really is in question? – Drew Jun 30 '18 at 14:14
  • Q: "How does one pass additional information into an interactive function?" A: This is normally done with a command prefix such as C-u; or, by prompting the user to enter a selection from the minibuffer, or ask the user to choose a particular key; e.g., press 1 for foo, press 2 for bar, press 3 for baz. [About 90% of the entire question is interesting, but not relevant to the very last question/answer (in my opinion). The second to the last question seeks an opinion.] – lawlist Jun 30 '18 at 17:01
4

You can bind one command to several key-sequences and access the calling key-sequence within the command with this-command-keys. That is demonstrated with the following code. There the same command my-command is indirectly bound to the key sequences F9, C-c a, and C-c b. Thereby "indirectly" means that there is a thin wrapper my-command-wrapper. That wrapper is used as interactive command and is kept very simple. It should not be instrumented with edebug-defun because the return value of this-command-keys in the debugging session does not make sense.

The example code demonstrates how you can keep separate histories for the separate calling key sequences.

If you call the key sequences the first time, or with prefix arg, or with shift-modifier, then you can change the argument value for my-command otherwise the last argument value from the history is used.

(defvar my-command-history-alist nil
  "Association of key sequence")
(defvar my-command--history nil)

(defun my-command-wrapper (&rest args)
  "Thin wrapper for `my-command'.
Adds stringified `this-command-keys' as the first argument to ARGS.
Don't instrument this function with `edebug-defun' otherwise
`this-command-keys' gives the wrong answer!"
  (interactive)
  (apply #'my-command (format "%s" (this-command-keys))
     args))

(defun my-command (keys &optional what)
  "Depending on the call sequence KEYS and PREFIX for this command do something with argument WHAT."
  (unless what
   (let* ((key-history (assoc-string keys my-command-history-alist))
      (my-command--history (cdr key-history)))
     (if (or (null my-command--history)
         current-prefix-arg
         this-command-keys-shift-translated)
     (progn
       (setq what (read-string (format "Input string for key sequence \"%s\":" keys) (car my-command--history) 'my-command--history))
       (if key-history
           (setcdr key-history my-command--history)
         (push (cons keys my-command--history) my-command-history-alist)))
       (setq what (car my-command--history)))))
  (message "Doing something with \"%s\"" what))

(global-set-key (kbd "<f9>") #'my-command-wrapper)
(global-set-key (kbd "C-c a") #'my-command-wrapper)
(global-set-key (kbd "C-c b") #'my-command-wrapper)
3

Let's look at this logically: you want to have near-identical commands bound to C-f2 and C-f3. The only difference between these commands is whether they store the thing under point in the f2 memory or in the f3 memory. Then you either need to construct different commands, or you need to have a single command whose behavior depends on what key it's bound to.

You can bind a key to a command that's constructed at the time you create the binding. The command argument to define-key and friends doesn't have to be a command name in the form of a symbol, it can be a lambda expression.

(global-set-key [C-f3] (lambda ()
                         (interactive)
                         …))

This works, but it isn't very nice. For example help commands and command histories won't show you a command name.

You can put the bulk of the code in a function and define small wrapper functions. To avoid repeating a lot of code, have a function or macro generate the wrapper functions.

(defun repeat-search-thing-at-point-forward (memory)
  (search-forward (symbol-value memory)))
(defun repeat-search-thing-at-point-backward (memory)
  (search-backward (symbol-value memory)))
(defun search-thing-at-point (memory)
  "Search the thing at point.
Store the thing in MEMORY for a future search with
`repeat-search-thing-at-point-forward' and
`repeat-search-thing-at-point-backward'."
  (set memory (thing-at-point 'word))
  (repeat-search-thing-at-point-forward memory))
(defun define-search-thing-at-point (map key)
  "Define commands to search a thing at point "
  (let* ((memory-variable (intern (format "search-memory-%s" key)))
         (set-function (intern (format "search-thing-at-point-%s" key)))
         (forward-function (intern (format "repeat-search-thing-at-point-forward-%s" key)))
         (backward-function (intern (format "repeat-search-thing-at-point-backward-%s" key)))
         (forward-key (vector key))
         (backward-key (vector (list 'shift key)))
         (set-key (vector (list 'control key))))
    (eval `(progn
             (defvar ,memory-variable nil
               ,(format "The last thing searched with \\[%s]." set-function))
             (defun ,set-function ()
               ,(format "Search the thing at point.
Use \\[%s] and \\[%s] to repeat the search forward and backward
respectively." forward-function backward-function)
               (interactive "@")
               (search-thing-at-point ',memory-variable))
             (defun ,forward-function ()
               ,(format "Search forward for the last thing searched with \\[%s]." set-function)
               (interactive "@")
               (repeat-search-thing-at-point-forward ',memory-variable))
             (defun ,backward-function ()
               ,(format "Search backward for the last thing searched with \\[%s]." set-function)
               (interactive "@")
               (repeat-search-thing-at-point-backward ',memory-variable))
             (define-key map ',set-key #',set-function)
             (define-key map ',forward-key #',forward-function)
             (define-key map ',backward-key #',backward-function)
             t))))

(define-search-thing-at-point global-map 'f2)
(define-search-thing-at-point global-map 'f3)
(define-search-thing-at-point global-map 'f4)

Alternatively, you can define a single command for each functionality (first search, repeat forward, repeat backward). This is slightly less flexible (e.g. you can't rebind `search-thing-at-point-f2 to H-s if it takes your fancy) but a lot less verbose.

A command can find which key invoked it. The easiest way for you is to use the variable last-command-event.

(defvar search-thing-memory nil
  "History of things searched with `search-thing-at-point'.")
(defun search-thing-at-point (key)
  "Search the thing at point.
Store the thing in MEMORY for a future search with
`repeat-search-thing-at-point-forward' and
`repeat-search-thing-at-point-backward'."
  (interactive (list (event-basic-type last-command-event)))
  (let ((thing (thing-at-point 'word))
    (memory (assq key search-thing-memory)))
    (if memory
    (setcdr memory thing)
      (setq search-thing-memory (cons (cons key thing)
                      search-thing-memory)))
    (search-forward thing)))
(defun repeat-search-thing-at-point-forward (key)
  "Repeat the last thing searched with `search-thing-at-point'
with a matching key binding."
  (interactive (list (event-basic-type last-command-event)))
  (search-forward (cdr (assq key search-thing-memory))))
(defun repeat-search-thing-at-point-backward (key)
  "Repeat the last thing searched with `search-thing-at-point'
with a matching key binding."
  (interactive (list (event-basic-type last-command-event)))
  (search-backward (cdr (assq key search-thing-memory))))

(global-set-key [C-f2] 'search-thing-at-point)
(global-set-key [C-f3] 'search-thing-at-point)
(global-set-key [C-f4] 'search-thing-at-point)
(global-set-key [f2] 'repeat-search-thing-at-point-forward)
(global-set-key [f3] 'repeat-search-thing-at-point-forward)
(global-set-key [f4] 'repeat-search-thing-at-point-forward)
(global-set-key [S-f2] 'repeat-search-thing-at-point-backward)
(global-set-key [S-f3] 'repeat-search-thing-at-point-backward)
(global-set-key [S-f4] 'repeat-search-thing-at-point-backward)

I don't think your proposed interface is a particularly useful addition to Emacs. Emacs's basic built-in search has easy ways to search the thing at point and to repeat past searches.

2

Addressing your original use case rather than the Elisp programming question, package highlight-symbol does what you want. I've been a happy user for many years.

http://nschum.de/src/emacs/highlight-symbol/

From the package description:

;; Add the following to your .emacs file:
;; (require 'highlight-symbol)
;; (global-set-key [(control f3)] 'highlight-symbol)
;; (global-set-key [f3] 'highlight-symbol-next)
;; (global-set-key [(shift f3)] 'highlight-symbol-prev)
;; (global-set-key [(meta f3)] 'highlight-symbol-query-replace)
;;
;; Use `highlight-symbol' to toggle highlighting of the symbol at
;; point throughout the current buffer.  Use `highlight-symbol-mode' to keep the
;; symbol at point highlighted.
;;
;; The functions `highlight-symbol-next', `highlight-symbol-prev',
;; `highlight-symbol-next-in-defun' and `highlight-symbol-prev-in-defun' allow
;; for cycling through the locations of any symbol at point.  Use
;; `highlight-symbol-nav-mode' to enable key bindings (M-p and M-p) for
;; navigation. When `highlight-symbol-on-navigation-p' is set, highlighting is
;; triggered regardless of `highlight-symbol-idle-delay'.
;;
;; `highlight-symbol-query-replace' can be used to replace the symbol.
  • Nice! I saw that there were several options for this sort of thing and actually used HiLock to do some highlighting (mostly because it's already built into Emacs (I forget which version). – MikeTheTall Jul 10 '18 at 17:57
  • Also - I love what you've got, but the question asked specifically to 'save' the thing that I'm looking for into multiple keys. So I'd need this to work for F3, and also to be able to separately search for something else at F4, and then search for a third thing at F5, etc. – MikeTheTall Jul 10 '18 at 17:58

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.