Can You Do This in Vi?
Here’s a hack possible only with dynamic scoping. It’s ugly, but it works. It’s Emacs.
I use interactive search to navigate my source file. I might see piece of a text, and instead of up-up-up-up-left-left-left-left-left-up, I just search and jump directly to the spot. I’ve bound the search keys to
C-f C-j
C-f C-l
They coincide with my movement keys,
C-i
C-j C-k C-l
So there’s a nice directionality in my muscle memory. Searching left, or searching left, are spatially mapped. Interactive search is a surprising powerful way to move around. Using movement keys, you have to constantly ask yourself, “am I there yet?”, “am I there yet, now?”, “where will we get there”, and your conscious mind gets angry and wants to throw the kids out of the window.
If I want to go back to “muscle memory” in the previous paragraph, I type,
C-f C-j “muscle memory”
Interactive search highlights the matches when I’ve typed “mus”. Seeing there are two matches, I use C-a and C-z to move backward and forward through the matches. So all in all, I’ve typed
C-f C-j “mus” C-a
This is unthinking in practice. My visual system only has to check,
(1) Is the place I want to go highlighted by isearch.
(2) Is my cursor there yet?
It’s just visual reflex. I don’t have to plan my way to the point I want to get to.
This is all very nice, what’s the hack I was talking about? Sometimes there are too many matches in the buffer. Suppose I want to get to “a nice directionality”, and I naively started search with the string,
C-f C-j “a”
The buffer lights up like a Christmas tree. There are too many matches for ‘a’, so it’s inpractical to jump to the place I want to go. If I keep typing to extend the search string, though, isearch eventually narrows down to my target. Eventually.
That’s not good enough. I’d like a way to say, I want to get to ‘a’, but only matches such that after it I could see the string “directionality.” So I want a way to perform context-sensitive search, with additional contexts provided incrementally should the need arise.
I defined a set of advises for isearch to add to the current search string with contexts. I’ve bound to M-j and M-l to add context to the left or to the right of my target.
So now, I could go to the place by typing something like
C-f C-j “a”
hmmm, too many matches, let’s add context
M-l “dir”
ahh! only three matches. C-a C-a C-a.
Again, I don’t need think how to get there. In my mind, I know where I’d like to go, and I tell Emacs /that’s/ my destination. Imagine you are taking a Taxi, you tell the driver where you’d like to go. Only very rarely you tell the driver “turn left”, “turn right”, “turn right”, “go straight for 5 blocks”, “turn left” THE WHOLE TRIP!
Context sensitive search turned out to be a very nice way to jump around in a source file when you don’t even know where the target is, but you know it’s there. For example, I may have a global variable ‘foo’. I’d like to go to where it’s defined. I know it’s at the top of my file, so I do a backward search.
C-j “defvar”
Oops, too many matches. Narrow by adding the variable name as rightward context,
M-l “foo”
I am there.
I can extend the context indefinitely. To find my keybinding for ‘icicle-candidate-set-union’, I might type
C-l “ici”
Too many,
M-j “kbd”
So the context now looks like
(kbd …) … ici…
Still to many,
M-l “union”
Ah! Perfect match. Here’s the hack. It’s funny how I have different coding standards for different linguistic communities. In Erlang, I undergo a visceral sickness every time I use “put”, a per process global variable sort of thing. In Emacs, “defadvice” is not encouraged, nor is it frowned upon. It’s there if you want/need it.
To make context sensitive search possible, I had to dig into the source for isearch to see what variables isearch is using. I dynamically bind to those variables before isearch does its thing. It’s a very unhealthy way to program, but it works. And that’s Emacs.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Context Sensitive iSearch
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Extend isearch so for context-sensitive search.
;;
;; If an isearch query has too many results, you can refine it by calling
;; `isearch-constraint-forward' or `isearch-constraint-backward'
;; to constrain results by the context around the initial query.
;; For example, having searched `foo', you can refine the query
;; by specifying only those foo's that are followed by a bar.
;;;; variables from isearch.el
;; `isearch-string' is the accumulator used by minibuffer to build up the search query.
;; `isearch-opoint' is where the search first started, to allow jump back.
;; `isearch-message' is used to display the search query in the minibuffer. I am not really using it properly.
(defvar isearch-constraint-anchor ""
"This is the search string one started with. Go to that after the constraints.")
(defvar isearch-constraint-opoint 0
"Where the search was first started.")
(defvar isearch-constraint-direction nil)
(defvar isearch-constraint-string "")
(defvar isearch-constraint-message "")
(defun isearch-constraint-forward ()
(interactive)
(isearch-with-constraint 'forward))
(defun isearch-constraint-backward ()
(interactive)
(isearch-with-constraint 'backward))
(defun isearch-with-constraint (direction)
(interactive)
(unless isearch-constraint-direction
(setq isearch-constraint-anchor isearch-string)
(setq isearch-constraint-opoint isearch-opoint))
(setq isearch-constraint-string
(isearch-current-constraint-string))
(setq isearch-constraint-direction direction)
;;(pr "iwc" (isearch-current-constraint-string))
(setq isearch-string "")
(setq isearch-message "")
(isearch-search-and-update)
)
(defun isearch-current-constraint-string ()
(case isearch-constraint-direction
(forward (concat
(if (equal "" isearch-constraint-string) ""
(concat isearch-constraint-string ".*?"))
(if isearch-regexp isearch-string
(regexp-quote isearch-string))))
(backward (concat
(if isearch-regexp isearch-string
(regexp-quote isearch-string))
(if (equal "" isearch-constraint-string) ""
(concat ".*?" isearch-constraint-string))))
(t (if isearch-regexp isearch-string
(regexp-quote isearch-string)))))
;; an after advice is needed for isearch-done to clear isearch-stored-string
(defadvice isearch-done (after isearch-clear-constraint)
(when isearch-constraint-direction
(setq isearch-constraint-string "")
(setq isearch-constraint-message "")
(setq isearch-constraint-direction nil)
;; Go to the initial search we were looking for.
;;; if last isearch is forward, we'd be at the end of the match
;;; Or we'd be at the beginning of the match
(if isearch-forward
(progn (re-search-backward isearch-constraint-anchor)
(re-search-forward isearch-constraint-anchor))
(re-search-forward isearch-constraint-anchor))
(set-mark isearch-constraint-opoint)
(setq isearch-constraint-opoint 0)
))
;; two identical advices for the isearch functionalities I need.
;;; you might want others, i dunno. Dig into the source yourself.
;; These two allow search and repeated search.
(defadvice isearch-repeat (around isearch-with-constraint)
"Perform a context sensitive search using the previous input as constraint."
;;(pr "isr" (isearch-current-constraint-string) isearch-forward)
(let ((isearch-string (isearch-current-constraint-string))
(isearch-regexp t))
ad-do-it))
(defadvice isearch-search-and-update (around isearch-with-constraint)
;;(pr "isau" (isearch-current-constraint-string))
(let ((isearch-string (isearch-current-constraint-string))
(isearch-regexp t))
ad-do-it))
(ad-activate 'isearch-repeat)
(ad-activate 'isearch-search-and-update)
(ad-activate 'isearch-done)
(provide 'isearch-constraint)
;; (ad-update-all)