| [7844] | 1 | ;;; -*- Mode: Lisp; Package: hemlock -*-
|
|---|
| 2 | ;;;
|
|---|
| 3 | ;;; Copyright (C) 2007 Clozure Associates
|
|---|
| 4 |
|
|---|
| 5 | (in-package :hemlock)
|
|---|
| 6 |
|
|---|
| 7 | (defmode "I-Search" :precedence :highest
|
|---|
| 8 | ;; Make anything that's not otherwise overridden exit i-search.
|
|---|
| 9 | :default-command "I-Search Exit and Redo")
|
|---|
| 10 |
|
|---|
| 11 | (add-hook abort-hook 'end-isearch-mode)
|
|---|
| 12 |
|
|---|
| 13 | (defhvar "Self Insert Command Name"
|
|---|
| 14 | "The name of the command to handle quoted input (i.e. after c-q) in I-Search"
|
|---|
| 15 | :value "I-Search Self Insert"
|
|---|
| 16 | :mode "I-Search")
|
|---|
| 17 |
|
|---|
| 18 | (defcommand "Incremental Search" (p)
|
|---|
| 19 | "Searches for input string as characters are provided.
|
|---|
| 20 |
|
|---|
| 21 | These are the default I-Search command characters:
|
|---|
| 22 | ^Q quotes the next character typed.
|
|---|
| 23 | ^W extends the search string to include the the word after the point.
|
|---|
| 24 | Delete cancels the last key typed.
|
|---|
| 25 | ^G during a successful search aborts and returns point to where it started.
|
|---|
| 26 | During a failing search, ^G backs up to last non-failing point.
|
|---|
| 27 | ^S repeats forward, and ^R repeats backward.
|
|---|
| 28 | ^R or ^S with empty string either changes the direction or yanks the previous search string.
|
|---|
| 29 | Escape exits the search unless the string is empty.
|
|---|
| 30 | Escape with an empty search string calls the non-incremental search command.
|
|---|
| 31 |
|
|---|
| 32 | Other control characters cause exit and execution of the appropriate
|
|---|
| 33 | command.
|
|---|
| 34 | "
|
|---|
| 35 | "Set up Incremental Search mode"
|
|---|
| 36 | (declare (ignore p))
|
|---|
| 37 | (start-isearch-mode :forward))
|
|---|
| 38 |
|
|---|
| 39 | (defcommand "Reverse Incremental Search" (p)
|
|---|
| 40 | "Searches for input string as characters are provided.
|
|---|
| 41 |
|
|---|
| 42 | These are the default I-Search command characters:
|
|---|
| 43 | ^Q quotes the next character typed.
|
|---|
| 44 | ^W extends the search string to include the the word after the point.
|
|---|
| 45 | Delete cancels the last key typed.
|
|---|
| 46 | ^G during a successful search aborts and returns point to where it started.
|
|---|
| 47 | During a failing search, ^G backs up to last non-failing point.
|
|---|
| 48 | ^S repeats forward, and ^R repeats backward.
|
|---|
| 49 | ^R or ^S with empty string either changes the direction or yanks the previous search string.
|
|---|
| 50 | Escape exits the search unless the string is empty.
|
|---|
| 51 | Escape with an empty search string calls the non-incremental search command.
|
|---|
| 52 |
|
|---|
| 53 | Other control characters cause exit and execution of the appropriate
|
|---|
| 54 | command.
|
|---|
| 55 | "
|
|---|
| 56 | "Set up Incremental Search mode"
|
|---|
| 57 | (declare (ignore p))
|
|---|
| 58 | (start-isearch-mode :backward))
|
|---|
| 59 |
|
|---|
| 60 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|---|
| 61 | ;;
|
|---|
| 62 |
|
|---|
| 63 | (defstruct (isearch-state (:conc-name "ISS-"))
|
|---|
| 64 | direction
|
|---|
| [16082] | 65 | local-pattern
|
|---|
| [7844] | 66 | failure
|
|---|
| 67 | wrapped-p
|
|---|
| 68 | history
|
|---|
| 69 | start-region)
|
|---|
| 70 |
|
|---|
| [16082] | 71 | (defvar *global-search-pattern* nil "Used when *isearch-is-global*") ; can't use *last-search-pattern* because that's
|
|---|
| 72 | ; used elsewhere
|
|---|
| 73 |
|
|---|
| 74 | (defun iss-pattern (state)
|
|---|
| 75 | (if *isearch-is-global*
|
|---|
| 76 | (or *global-search-pattern*
|
|---|
| 77 | (iss-local-pattern state))
|
|---|
| 78 | (iss-local-pattern state)))
|
|---|
| 79 |
|
|---|
| 80 | (defun iss-string (state)
|
|---|
| 81 | (ignore-errors ; because iss-pattern might be nil
|
|---|
| 82 | (hi::search-pattern-pattern (iss-pattern state))))
|
|---|
| 83 |
|
|---|
| [7844] | 84 | (defun current-region-info ()
|
|---|
| 85 | (list (copy-mark (current-point) :temporary)
|
|---|
| 86 | (copy-mark (current-mark) :temporary)
|
|---|
| 87 | (region-active-p)))
|
|---|
| 88 |
|
|---|
| 89 | (defun set-current-region-info (info)
|
|---|
| 90 | (destructuring-bind (point mark active-p) info
|
|---|
| 91 | (move-mark (current-point) point)
|
|---|
| 92 | (move-mark (current-mark) mark)
|
|---|
| 93 | (if active-p
|
|---|
| 94 | (progn
|
|---|
| 95 | (activate-region)
|
|---|
| 96 | (note-current-selection-set-by-search))
|
|---|
| 97 | (deactivate-region))))
|
|---|
| 98 |
|
|---|
| 99 | (defun %i-search-save-state (iss)
|
|---|
| 100 | (push (list* (iss-string iss)
|
|---|
| 101 | (iss-direction iss)
|
|---|
| 102 | (iss-failure iss)
|
|---|
| 103 | (iss-wrapped-p iss)
|
|---|
| 104 | (current-region-info))
|
|---|
| 105 | (iss-history iss)))
|
|---|
| 106 |
|
|---|
| 107 | (defun %i-search-pop-state (iss)
|
|---|
| 108 | (destructuring-bind (string direction failure wrapped-p . region-info)
|
|---|
| 109 | (pop (iss-history iss))
|
|---|
| 110 | (setf (iss-failure iss) failure)
|
|---|
| 111 | (setf (iss-wrapped-p iss) wrapped-p)
|
|---|
| 112 | (%i-search-set-pattern iss :string string :direction direction)
|
|---|
| 113 | (set-current-region-info region-info)))
|
|---|
| 114 |
|
|---|
| 115 | (defun %i-search-message (iss)
|
|---|
| 116 | (when t ;(interactive)
|
|---|
| 117 | (message "~:[~;Failing ~]~:[~;Wrapped ~]~:[Reverse I-Search~;I-Search~]: ~A"
|
|---|
| 118 | (iss-failure iss)
|
|---|
| 119 | (iss-wrapped-p iss)
|
|---|
| 120 | (eq (iss-direction iss) :forward)
|
|---|
| 121 | (or (iss-string iss) ""))))
|
|---|
| 122 |
|
|---|
| 123 |
|
|---|
| 124 | ;; Minor errors that don't cause isearch mode to be exited, except while
|
|---|
| 125 | ;; executing keyboard macros.
|
|---|
| 126 | (defun %i-search-perhaps-error (message)
|
|---|
| 127 | message
|
|---|
| 128 | (if t ;(interactive)
|
|---|
| 129 | (beep)
|
|---|
| 130 | (abort-current-command message)))
|
|---|
| 131 |
|
|---|
| 132 | ;;;;
|
|---|
| 133 | ;;
|
|---|
| 134 |
|
|---|
| [8207] | 135 | (defun current-isearch-state ()
|
|---|
| 136 | (or (value i-search-state)
|
|---|
| 137 | (error "I-Search command invoked outside I-Search")))
|
|---|
| 138 |
|
|---|
| [7844] | 139 | (defun start-isearch-mode (direction)
|
|---|
| [8207] | 140 | (let* ((buffer (current-buffer))
|
|---|
| 141 | (iss (make-isearch-state :direction direction
|
|---|
| [7844] | 142 | :start-region (current-region-info))))
|
|---|
| [16082] | 143 | (when (iss-pattern iss)
|
|---|
| 144 | (setf (hi::search-pattern-pattern (iss-pattern iss)) nil))
|
|---|
| [8207] | 145 | (setf (buffer-minor-mode buffer "I-Search") t)
|
|---|
| 146 | (unless (hemlock-bound-p 'i-search-state :buffer buffer)
|
|---|
| 147 | (defhvar "I-Search State"
|
|---|
| 148 | "Internal variable containing current state of I-Search"
|
|---|
| 149 | :buffer buffer))
|
|---|
| [14812] | 150 | (unless (region-active-p) ; We need the selection (if there is one) to stay put!
|
|---|
| 151 | (push-new-buffer-mark (current-point)))
|
|---|
| [7844] | 152 | (setf (value i-search-state) iss)
|
|---|
| 153 | (%i-search-message iss)))
|
|---|
| 154 |
|
|---|
| 155 | (defun end-isearch-mode ()
|
|---|
| 156 | (setf (buffer-minor-mode (current-buffer) "I-Search") nil))
|
|---|
| 157 |
|
|---|
| [14812] | 158 | (defcommand "I-Search Yank Selection" (p)
|
|---|
| 159 | "Pull string from current selection into search string."
|
|---|
| 160 | (declare (ignore p))
|
|---|
| 161 | (let* ((iss (current-isearch-state)))
|
|---|
| [16082] | 162 | (i-search-extend iss (symbol-at-point (current-buffer)))))
|
|---|
| [14812] | 163 |
|
|---|
| [7844] | 164 | (defun i-search-backup (iss)
|
|---|
| 165 | (if (iss-history iss)
|
|---|
| 166 | (progn
|
|---|
| 167 | (%i-search-pop-state iss)
|
|---|
| 168 | (%i-search-message iss))
|
|---|
| 169 | (%i-search-perhaps-error "I-Search Backup failed")))
|
|---|
| 170 |
|
|---|
| 171 | (defun i-search-revert (iss)
|
|---|
| 172 | (loop while (iss-failure iss) do (%i-search-pop-state iss))
|
|---|
| 173 | (%i-search-message iss))
|
|---|
| 174 |
|
|---|
| 175 | (defun i-search-repeat (iss)
|
|---|
| 176 | (cond ((null (iss-string iss))
|
|---|
| 177 | ;; No search string, so "repeat" really means fetch last successful search string
|
|---|
| 178 | (if (zerop (length *last-search-string*))
|
|---|
| 179 | (%i-search-perhaps-error "No previous search string")
|
|---|
| 180 | (progn
|
|---|
| 181 | (%i-search-save-state iss)
|
|---|
| 182 | (%i-search-set-pattern iss :string *last-search-string*)
|
|---|
| 183 | (%i-search-do-search iss (current-mark)))))
|
|---|
| 184 | ((iss-failure iss)
|
|---|
| 185 | (%i-search-save-state iss)
|
|---|
| 186 | ;; If failed last time, "repeat" really means try again from the top.
|
|---|
| 187 | (setf (iss-wrapped-p iss) t) ;; start saying "Wrapped i-search" to remind 'em.
|
|---|
| 188 | (%i-search-do-search iss (if (eq (iss-direction iss) :forward)
|
|---|
| 189 | (buffer-start-mark (current-buffer))
|
|---|
| 190 | (buffer-end-mark (current-buffer)))))
|
|---|
| 191 | (t
|
|---|
| 192 | (%i-search-save-state iss)
|
|---|
| 193 | ;; Have a non-empty string and a successful search, just find the next one!
|
|---|
| 194 | (%i-search-do-search iss (current-point))))
|
|---|
| 195 | (%i-search-message iss))
|
|---|
| 196 |
|
|---|
| 197 | (defun i-search-reverse (iss)
|
|---|
| 198 | (%i-search-save-state iss)
|
|---|
| 199 | (%i-search-set-pattern iss :direction (ecase (iss-direction iss)
|
|---|
| 200 | (:forward :backward)
|
|---|
| 201 | (:backward :forward)))
|
|---|
| 202 | (let* ((mark (current-mark))
|
|---|
| 203 | (point (current-point)))
|
|---|
| 204 | (with-mark ((temp point))
|
|---|
| 205 | (move-mark point mark)
|
|---|
| 206 | (move-mark mark temp))
|
|---|
| 207 | (when (iss-failure iss)
|
|---|
| 208 | ;; if we were failing before, search immediately, otherwise wait til asked
|
|---|
| 209 | (%i-search-do-search iss mark)))
|
|---|
| 210 | (%i-search-message iss))
|
|---|
| 211 |
|
|---|
| 212 | (defun i-search-extend (iss extension)
|
|---|
| 213 | (%i-search-save-state iss)
|
|---|
| 214 | (let* ((new-string (concatenate 'simple-string (iss-string iss) extension)))
|
|---|
| 215 | (%i-search-set-pattern iss :string new-string))
|
|---|
| 216 | (unless (iss-failure iss) ;; Can't succeed now if failed before, so don't try
|
|---|
| 217 | (with-mark ((temp (current-mark)))
|
|---|
| 218 | (when (eq (iss-direction iss) :backward)
|
|---|
| 219 | (or (character-offset temp (length extension))
|
|---|
| 220 | (buffer-end temp)))
|
|---|
| 221 | (%i-search-do-search iss temp)))
|
|---|
| 222 | (%i-search-message iss))
|
|---|
| 223 |
|
|---|
| 224 | (defun i-search-exit (iss)
|
|---|
| 225 | (let* ((string (iss-string iss)))
|
|---|
| 226 | (when (and string (not (iss-failure iss)))
|
|---|
| 227 | (setf *last-search-string* string)))
|
|---|
| 228 | (end-isearch-mode)
|
|---|
| 229 | (message ""))
|
|---|
| 230 |
|
|---|
| 231 | (defun %i-search-set-pattern (iss &key (string nil s-p) (direction nil d-p))
|
|---|
| [16082] | 232 | (let ((thisstring (if s-p (or string "") (iss-string iss))))
|
|---|
| 233 | (when *isearch-is-global*
|
|---|
| 234 | (setf *last-search-string* thisstring))
|
|---|
| 235 | (when d-p
|
|---|
| 236 | (setf (iss-direction iss) direction))
|
|---|
| 237 | (setf *global-search-pattern*
|
|---|
| 238 | (setf (iss-local-pattern iss) (new-search-pattern (if (value string-search-ignore-case)
|
|---|
| 239 | :string-insensitive
|
|---|
| 240 | :string-sensitive)
|
|---|
| 241 | (iss-direction iss)
|
|---|
| 242 | thisstring
|
|---|
| 243 | (iss-pattern iss))))))
|
|---|
| [7844] | 244 |
|
|---|
| 245 | ;; Do a search for the current pattern starting at START going to
|
|---|
| 246 | ;; end/beginning as per ISS-DIRECTION. Sets ISS-FAILURE depending on
|
|---|
| 247 | ;; whether found or not. If successful, moves region to surround the
|
|---|
| 248 | ;; found string (with point at the end for :forward search and at the
|
|---|
| 249 | ;; beginning for :backward) and activates the region. If failed,
|
|---|
| 250 | ;; leaves region unchanged. Never modifies START.
|
|---|
| 251 | (defun %i-search-do-search (iss start)
|
|---|
| 252 | (let* ((temp (copy-mark start :temporary))
|
|---|
| 253 | (found-offset (find-pattern temp (iss-pattern iss))))
|
|---|
| 254 | (setf (iss-failure iss) (not found-offset))
|
|---|
| 255 | (if (iss-failure iss)
|
|---|
| 256 | (%i-search-perhaps-error "I-Search failed")
|
|---|
| 257 | (let* ((point (current-point))
|
|---|
| 258 | (mark (current-mark)))
|
|---|
| 259 | (move-mark point temp)
|
|---|
| 260 | (if (eq (iss-direction iss) :forward)
|
|---|
| 261 | (character-offset point found-offset)
|
|---|
| 262 | (character-offset temp found-offset))
|
|---|
| 263 | (move-mark mark temp)
|
|---|
| 264 | (activate-region)
|
|---|
| 265 | (note-current-selection-set-by-search)))))
|
|---|
| 266 |
|
|---|
| 267 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|---|
| 268 | ;;
|
|---|
| 269 |
|
|---|
| 270 | (defcommand "I-Search Repeat Forward" (p)
|
|---|
| 271 | "Repeat forward incremental search, or reverse direction if currently searching backward"
|
|---|
| 272 | (declare (ignore p))
|
|---|
| 273 | (let* ((iss (current-isearch-state)))
|
|---|
| 274 | (if (eq (iss-direction iss) :forward)
|
|---|
| 275 | (i-search-repeat iss)
|
|---|
| 276 | (i-search-reverse iss))))
|
|---|
| 277 |
|
|---|
| 278 | (defcommand "I-Search Repeat Backward" (p)
|
|---|
| 279 | "Repeat backward incremental search, or reverse direction if currently searching forward"
|
|---|
| 280 | (declare (ignore p))
|
|---|
| 281 | (let* ((iss (current-isearch-state)))
|
|---|
| 282 | (if (eq (iss-direction iss) :backward)
|
|---|
| 283 | (i-search-repeat iss)
|
|---|
| 284 | (i-search-reverse iss))))
|
|---|
| 285 |
|
|---|
| 286 | (defcommand "I-Search Backup" (p)
|
|---|
| 287 | "Undo last incremental search command"
|
|---|
| 288 | (declare (ignore p))
|
|---|
| 289 | (let* ((iss (current-isearch-state)))
|
|---|
| 290 | (i-search-backup iss)))
|
|---|
| 291 |
|
|---|
| 292 | (defcommand "I-Search Yank Word" (p)
|
|---|
| 293 | "Extend the search string to include the the word after the point."
|
|---|
| 294 | (declare (ignore p))
|
|---|
| 295 | (let* ((iss (current-isearch-state))
|
|---|
| 296 | (point (current-point)))
|
|---|
| 297 | (with-mark ((end point))
|
|---|
| 298 | (if (word-offset end 1)
|
|---|
| 299 | (i-search-extend iss (region-to-string (region point end)))
|
|---|
| 300 | (%i-search-perhaps-error "No more words")))))
|
|---|
| 301 |
|
|---|
| 302 | (defcommand "I-Search Self Insert" (p)
|
|---|
| 303 | "Add character typed to search string"
|
|---|
| 304 | (declare (ignore p))
|
|---|
| 305 | (let* ((iss (current-isearch-state))
|
|---|
| 306 | (char (last-char-typed)))
|
|---|
| 307 | (unless char (editor-error "Can't insert that character."))
|
|---|
| 308 | (i-search-extend iss (string char))))
|
|---|
| 309 |
|
|---|
| 310 | (defcommand "I-Search Abort" (p)
|
|---|
| 311 | "Abort incremental search mode if search is successful. Otherwise, revert to last
|
|---|
| 312 | successful search and continue searching."
|
|---|
| 313 | (declare (ignore p))
|
|---|
| 314 | (let* ((iss (current-isearch-state)))
|
|---|
| 315 | (if (iss-failure iss)
|
|---|
| 316 | (i-search-revert iss)
|
|---|
| 317 | ;; Else move back to starting point and stop searching
|
|---|
| 318 | (progn
|
|---|
| 319 | (set-current-region-info (iss-start-region iss))
|
|---|
| 320 | (abort-current-command "Search aborted")))))
|
|---|
| 321 |
|
|---|
| 322 | ;; The transparent-p flag takes care of executing the key normally when we're done,
|
|---|
| 323 | ;; as long as we don't take a non-local exit.
|
|---|
| 324 | (defcommand ("I-Search Exit and Redo" :transparent-p t) (p)
|
|---|
| 325 | "Exit Incremental Search and then execute the key normally"
|
|---|
| 326 | (declare (ignore p))
|
|---|
| 327 | (let* ((iss (current-isearch-state)))
|
|---|
| 328 | (i-search-exit iss)))
|
|---|
| 329 |
|
|---|
| 330 | (defcommand "I-Search Exit or Search" (p)
|
|---|
| 331 | "Exit incremental search. If the search string is empty, switch to non-incremental search,
|
|---|
| 332 | otherwise just quit"
|
|---|
| 333 | (declare (ignore p))
|
|---|
| 334 | (let* ((iss (current-isearch-state))
|
|---|
| 335 | (string (iss-string iss))
|
|---|
| 336 | (direction (iss-direction iss)))
|
|---|
| 337 | (i-search-exit iss)
|
|---|
| 338 | (when (null string)
|
|---|
| 339 | (if (eq direction :forward)
|
|---|
| 340 | (forward-search-command nil)
|
|---|
| 341 | (reverse-search-command nil)))))
|
|---|
| 342 |
|
|---|
| 343 |
|
|---|
| 344 |
|
|---|