1 ;;; tinysearch.el --- Grab and search word under cursor
3 ;; This file is not part of Emacs
7 ;; Copyright (C) 1994-2007 Jari Aalto
10 ;; Maintainer: Jari Aalto
12 ;; To get information on this program, call M-x tinysearch-version.
13 ;; Look at the code with folding.el.
17 ;; This program is free software; you can redistribute it and/or modify it
18 ;; under the terms of the GNU General Public License as published by the Free
19 ;; Software Foundation; either version 2 of the License, or (at your option)
22 ;; This program is distributed in the hope that it will be useful, but
23 ;; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
24 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
27 ;; You should have received a copy of the GNU General Public License
28 ;; along with program; see the file COPYING. If not, write to the
29 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
30 ;; Boston, MA 02110-1301, USA.
32 ;; Visit <http://www.gnu.org/copyleft/gpl.html> for more information
38 ;; ....................................................... &t-install ...
39 ;; Put this file on your Emacs-Lisp load path, add following into
40 ;; ~/.emacs startup file.
42 ;; (require 'tinysearch)
44 ;; or use autoload, and your ~/.emacs loads quicker
46 ;; (autoload 'tinysearch-search-word-forward "tinysearch" "" t)
47 ;; (autoload 'tinysearch-search-word-backward "tinysearch" "" t)
49 ;; ;; Install default keybindings: M-s (forward search), C-M-s
50 ;; ;; (bbackward), M-Mouse-1 (forward), C-M-Mouse-1 (backward)
51 ;; (add-hook 'tinysearch-:load-hook 'tinysearch-install)
56 ;; ..................................................... &t-commentary ...
61 ;; In 7 Nov 1994 <aep@world.std.com> (Andrew E Page) posted
62 ;; interesting code by article name 'Script Example: Search for next
63 ;; word', which was interesting. The idea of the code was good, but it
64 ;; didn't work as expected at all. Gradually the idea was crystallized
67 ;; "Why we need search word package, when in emacs I can do `C-s' to
68 ;; enter search mode: C-w C-w C-w to grap words immediately after
69 ;; point and finally C-s to start searching...?"
71 ;; Well, people tend to forget, that life was out there when 19.xx
72 ;; wan't in hands of developers. This package was originally made for
73 ;; 18. The advantage of this package is the variable
75 ;; tinysearch-:word-boundary-set
77 ;; which you can easily change whenever you need (e.g. thru
78 ;; functions). To do the same in emacs, you have to go and modify the
79 ;; syntax entries involved...then come back again when you're done. I
80 ;; never do that, I seldom touch the syntax entries. Besides all
81 ;; mode-xxx go crazy if I would do so. Now you see the advantage?
83 ;; And of course I feel more comfortable to do just one keypress,
84 ;; like like `M-s' to search forward instead of cubersome C-s C-w C-w
85 ;; C-w [n times] and finally C-s
89 ;; Grab word under oint and searches fwd/back. The word is inserted
90 ;; into Emacs's search ring, so that you can later continue with `C-s'
91 ;; or with `C-r' call.
93 ;; Why doesn't it find my C++ function class::InitClass() ??
95 ;; User pressed the search function over the call:
97 ;; InitClass(); << Here
100 ;; Why isn't the function found? Remember that this searches
101 ;; 'true' words, not parts of them. A word is surrounded by at
102 ;; least one whitespace, since it's not a word if it is concatenated
103 ;; together with something else.
105 ;; The problem is, that if is you define ':' to belong to a
106 ;; character set in C++, [because you propably want to grab
107 ;; variables easily. including the scope operator
108 ;; 'clss::variable' or '::global'], this package expects to
109 ;; find word a boundary:
111 ;; nonWordWORDnonWord
114 ;; And as you can see, the if ':' belongs to word, it can't
115 ;; simultaneously belong to NonWord ! Summa summarum: Revert to
116 ;; emacs C-s for a moment, since the word is automatically added
117 ;; to the search buffer.
119 ;; Word accept function note:
121 ;; There is variable `tinysearch-:accept-word-function', which has
124 ;; tinysearch-accept-word
126 ;; The function's purpose is to check if the searched word is
127 ;; accepted and that search should be terminated. Currently there it
128 ;; contains some programming logic for C/C++ languages, so that
129 ;; certain hits are ignored. Consider following case:
136 ;; foo = x; - 5 * start of 'foo' and 'x' search backward
138 ;; C/C++ mode, searching for 'foo' finds 4,2,1 -- Not 3
139 ;; C/C++ mode, searching for 'x' finds 5,4,3 -- Not 2
140 ;; But in text-mode, you would find all occurrances.
142 ;; The added logic to C++ ignores the struct's MEMBER matches so that
143 ;; you really can find the "main" variables. If you don't like
144 ;; this added feature, you can alwasy go to
148 ;; For a while, or if want to permanently switch this feature off,
149 ;; you set the variable `tinysearch-:accept-word-function' to nil, which
150 ;; causes all hits to be accepted.
152 ;; Needless to say, that you can use put your own checking
153 ;; function in that variable to control the accurrances better.
165 (eval-when-compile (ti::package-use-dynamic-compilation))
167 (ti::package-defgroup-tiny TinySearch tinysearch-: extensions
168 "search word under cursor: backward, forward.")
173 ;;; ......................................................... &v-hooks ...
175 (defcustom tinysearch-:before-hook nil
176 "*Hook that is run at the BEG of search function.
177 You can set this to point to function that alters the value of
178 `tinysearch-:word-boundary-set' e.g. by looking at the file type."
182 (defcustom tinysearch-:final-hook nil
183 "*Hook that is _always_ run at the END of search function.
184 It doesn't care about word grabbings or search failures."
188 (defcustom tinysearch-:load-hook nil
189 "*Run when package has been loaded.
190 A good candidate could be `tinysearch-install-default-keybindings'."
197 ;;; ....................................................... &v-private ...
199 (defvar tinysearch-:direction nil
200 "Tell direction of search. nil = forward.")
202 (defvar tinysearch-:search-status nil
203 "Status of word search. t = successful.")
205 (defvar tinysearch-:overlay nil
206 "Overlay used for highlighting.
207 Created and killed during program execution.")
209 ;;; ........................................................ &v-public ...
210 ;;; User configurable
212 (defcustom tinysearch-:word-boundary-set "-A-Za-z0-9_"
213 "*Character set to conform a single word.
214 You might want to set this to something else before doing search."
218 (defcustom tinysearch-:wrap-flag nil
219 "*Non-nil means wrap buffer if there is no more match."
223 (defcustom tinysearch-:accept-word-function 'tinysearch-accept-word
224 "*Function run after the search for word has been successful.
225 If this variable contains non-existing function (like nil), the
226 content of the variable is ignored.
230 'tinysearch-accept-word'
232 Passed args to function:
234 string word being searched
236 Return values of function:
239 nil do not accept search, continue searching next word."
243 ;;; ....................................................... &v-version ...
246 (ti::macrof-version-bug-report
249 tinysearch-:version-id
250 "$Id: tinysearch.el,v 2.49 2007/05/07 10:50:14 jaalto Exp $"
251 '(tinysearch-:version-id
252 tinysearch-:before-hook
253 tinysearch-:final-hook
254 tinysearch-:load-hook
255 tinysearch-:direction
256 tinysearch-:search-status
258 tinysearch-:word-boundary-set
259 tinysearch-:wrap-flag)))
263 ;;; ########################################################### &Funcs ###
265 ;;{{{ 19.xx isearch add
267 ;;; ----------------------------------------------------------------------
269 (defun tinysearch-add-to-isearch-search-ring (isearch-string)
270 "Add search pattern to ISEARCH-STRING in Emacs.
271 This code is directly taken from function `isearch-done' By Daniel LaLiberte."
272 (if (> (length isearch-string) 0)
273 ;; Update the ring data.
275 (if (or (null regexp-search-ring)
276 (not (string= isearch-string (car regexp-search-ring))))
278 (setq regexp-search-ring
279 (cons isearch-string regexp-search-ring))
280 (if (> (length regexp-search-ring) regexp-search-ring-max)
281 (setcdr (nthcdr (1- search-ring-max) regexp-search-ring)
283 (if (or (null search-ring)
284 (not (string= isearch-string (car search-ring))))
286 (setq search-ring (cons isearch-string search-ring))
287 (if (> (length search-ring) search-ring-max)
288 (setcdr (nthcdr (1- search-ring-max) search-ring) nil)))))))
293 ;;; ----------------------------------------------------------------------
295 (defun tinysearch-accept-word (word)
296 "Determine if we accept searched WORD."
297 (let* ((type (symbol-name major-mode))
298 (ret t) ;default, accept search
301 ((string-match "^c-\\|^cc-\\|c[+]+" type)
302 ;; Check C/C++ dependent variables, where rg. 'a' is
304 ;; a = 1 , begin search with 'a'
305 ;; a = a + 1 , accepted hit
306 ;; struct->a , not accepted hit, continue search
309 (or (ti::buffer-read-space-word)
311 (if (string-match (concat "\\(->\\|[.]\\)" (regexp-quote word))
317 ;;; ----------------------------------------------------------------------
319 (defun tinysearch-grab-word (&optional charset beg end )
320 "Gets word under cursor limited by CHARSET string.
321 Optional BEG and END gives maximum search limits.
322 Default boundary is line limit."
323 (let* (re-word-boundary
325 ;; We accept ':' and '-' , beasuse they are used in c++ and lisp
326 (charset (or charset "-:A-Za-z0-9_"))
327 (beg (or beg (line-beginning-position)))
328 (end (or end (line-end-position)))
334 (setq re-word-boundary (concat "[^" charset "]"))
335 (setq re-word (concat "[" charset "]")) ;considered single word
336 ;; Note: the first search goes backwards to find the start of the
337 ;; word, which is one character in front of the character
338 ;; found by the search. Then we go forward to the end of
339 ;; word which is one character behind the character found by the
341 (save-excursion ;conceive original (point)
342 (if (re-search-backward re-word-boundary beg t)
343 (setq pb (1+ (point))))
344 (if pb nil ;already found
347 (if (eq p (point)) ;we were at the BEG
349 (setq re (concat re-word "+"))) ;skip chars
350 (if (re-search-forward re (1+ p) t) ; word at the BEG
352 ;; Then search end point
354 (if (re-search-forward re-word-boundary end t)
355 (setq pe (1- (point))))
356 (if pe nil ;already found
357 (if (looking-at (concat re-word "+$")) ; handle word at the END of ln
360 (setq ret (buffer-substring pb pe)))
361 ;; easier to debug this way
364 ;;; ----------------------------------------------------------------------
365 ;;; - There is lot of re-search-backward/fwd commands and it is intentional,
366 ;;; so that the code is totally emacs version independent. Newer emacs
367 ;;; has nice functions that shrink this code to 10 lines :->
368 ;;; - Never grab word function is already coded in tinylib.el
370 (defun tinysearch-search-word-main (&optional backward set)
371 "Gets word under cursor and search next occurrence.
372 If BACKWARD is non-nil, the search will be headed backward, the SET
373 corresponds to `tinysearch-:word-boundary-set'.
375 Before searching is done the tinysearch-hooks is thrown. This is useful
376 is you want someone to dynamically change the search-word's idea of
377 the chars belonging to word. By setting `tinysearch-:word-boundary-set' you
378 can set different sets for text and Lisp. [In Lisp the '-' is part of
379 word while in text it normally isn't].
383 You cannot search 1 char words with this due to internal
384 behaviour of search method and cursor positioning."
386 (let ((wrap tinysearch-:wrap-flag)
392 re-word-boundary re-word
397 (or tinysearch-:overlay
398 (setq tinysearch-:overlay (ti::compat-overlay-some)))
399 ;; ................................................... set charset ...
400 (setq tinysearch-:direction backward ;inform possible hook func
401 charset (or set tinysearch-:word-boundary-set)
402 re-word-boundary (concat "[^" charset "]")
403 re-word (concat "[" charset "]") ;considered single word
405 ;; Let the user set the word criteria
406 (if tinysearch-:before-hook
407 (run-hooks 'tinysearch-:before-hook))
408 ;; ...................................................... set word ...
409 (setq word (tinysearch-grab-word charset))
411 (message "TinySearch: Word not grabbed.")
412 ;; enable C-s and C-r to use the word, look isearch.el
413 ;; NOTE: this doesn't put the WORD regexp there...
414 (tinysearch-add-to-isearch-search-ring word)
415 ;; post a message saying what we're looking for
416 (message "searching for \`%s\`" word)
417 (setq no-msg (concat "TinySearch: No more words [" word "]" ))
421 re-word-boundary word "\\)" re-word-boundary))
422 ;; ................................................... do search ...
424 ;; Record the point only if the word is accepted.
426 (setq prev-point (point)))
427 (if backward ;choose backward
429 (setq found (re-search-backward re-word nil t))
432 (save-match-data ;highlight needs orig region
433 (unless (looking-at re-charset)
434 (re-search-forward re-charset) ;Goto first char
435 (backward-char 1)))))
436 ;; - This a little hard to explain: the search
437 ;; does not succeed, if the variable 'a' is at
438 ;; the beginning of line due to backward-char 2 correction
439 (if (eq (current-column) 0)
440 (ignore-errors (forward-char 1)))
441 (setq found (re-search-forward re-word nil t))
445 ;; - So that NEXT word will be grabbed, that's why 1 char words
447 (setq mb (match-beginning 0) me (match-end 0) )
449 ;; ........................................................ done ...
450 (setq tinysearch-:search-status found) ;save status
451 ;; Should we continue searching ?
456 (setq loop nil) ;No hits at all
457 (if backward ;start a new round
462 ;; Word accept function caused loop to run again, but
463 ;; there were no more hits. Back to prev position
464 (goto-char prev-point)
467 (not (fboundp tinysearch-:accept-word-function)))
470 ;; Is this found word accepted in the context
471 ;; surrounding the text ?
472 (setq accept (funcall tinysearch-:accept-word-function word)))
473 ;; Restore previous search point
475 ;; .................................................... do hilit ...
476 (if (and tinysearch-:overlay found (null loop))
477 (ti::compat-overlay-move tinysearch-:overlay mb me nil 'highlight))
478 (when tinysearch-:overlay ;Hide overlay
480 (ti::compat-overlay-move tinysearch-:overlay 1 1))
483 ;; ---------------------- grabbed
484 (if tinysearch-:final-hook
485 (run-hooks 'tinysearch-:final-hook))))
487 ;;; ----------------------------------------------------------------------
489 (defun tinysearch-charset-control ()
490 "Dynamic character set change according to mode. This is example function."
491 (let* ((type (symbol-name major-mode))
494 ((string-match "^c-\\|^cc-\\|c[+]+" type)
495 (setq set "A-Za-z0-9_"))
496 ((string-match "lisp" type)
497 ;; Add ':' , which I use in variable names.
498 (setq set "-:A-Za-z0-9_"))
499 ((string-match "text\\|shell\\|perl" type)
500 (setq set "A-Za-z0-9_")))
503 ;;; ----------------------------------------------------------------------
506 (defun tinysearch-search-word-forward ()
507 "Search word at point forward."
509 (tinysearch-search-word-main nil (tinysearch-charset-control)))
511 ;;; ----------------------------------------------------------------------
514 (defun tinysearch-search-word-backward ()
515 "Search word at point backward."
517 (tinysearch-search-word-main 'back (tinysearch-charset-control)))
519 ;;; ----------------------------------------------------------------------
522 (defun tinysearch-install-default-keybindings (&optional uninstall)
523 "Install default keybindings; M-s C-M-s, M-Mouse-1, C-M-Mouse-1."
525 (global-set-key [(meta ?s)] 'tinysearch-search-word-forward)
526 (global-set-key [(control meta ?s)] 'tinysearch-search-word-backward)
527 ;; For mouse (under windowed system)
528 (global-set-key [(meta control mouse-1)]
529 'tinysearch-search-word-forward)
530 (global-set-key [(meta control shift mouse-1)]
531 'tinysearch-search-word-backward))
533 ;;; ----------------------------------------------------------------------
536 (defun tinysearch-install (&optional arg)
537 "Call `tinysearch-install-default-keybindings' with ARG."
539 (tinysearch-install-default-keybindings arg))
543 (provide 'tinysearch)
544 (run-hooks 'tinysearch-:load-hook)
546 ;;; tinysearch.el ends here