]> code.delx.au - gnu-emacs/blob - lisp/emulation/viper-mous.el
new version
[gnu-emacs] / lisp / emulation / viper-mous.el
1 ;;; viper-mous.el --- mouse support for Viper
2
3 ;; Copyright (C) 1994, 1995, 1996, 1997 Free Software Foundation, Inc.
4
5 ;; This file is part of GNU Emacs.
6
7 ;; GNU Emacs is free software; you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation; either version 2, or (at your option)
10 ;; any later version.
11
12 ;; GNU Emacs is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;; GNU General Public License for more details.
16
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with GNU Emacs; see the file COPYING. If not, write to the
19 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 ;; Boston, MA 02111-1307, USA.
21
22 ;; Code
23
24 (provide 'viper-mous)
25
26 ;; compiler pacifier
27 (defvar double-click-time)
28 (defvar mouse-track-multi-click-time)
29 (defvar vip-search-start-marker)
30 (defvar vip-local-search-start-marker)
31 (defvar vip-search-history)
32 (defvar vip-s-string)
33 (defvar vip-re-search)
34
35 (eval-when-compile
36 (let ((load-path (cons (expand-file-name ".") load-path)))
37 (or (featurep 'viper-util)
38 (load "viper-util.el" nil nil 'nosuffix))
39 (or (featurep 'viper)
40 (load "viper.el" nil nil 'nosuffix))
41 ))
42 ;; end pacifier
43
44 (require 'viper-util)
45
46
47 \f
48 ;;; Variables
49
50 ;; Variable used for catching the switch-frame event.
51 ;; If non-nil, indicates that previous-frame should be the selected
52 ;; one. Used by vip-mouse-click-get-word. Not a user option.
53 (defvar vip-frame-of-focus nil)
54
55 ;; Frame that was selected before the switch-frame event.
56 (defconst vip-current-frame-saved (selected-frame))
57
58 (defvar vip-surrounding-word-function 'vip-surrounding-word
59 "*Function that determines what constitutes a word for clicking events.
60 Takes two parameters: a COUNT, indicating how many words to return,
61 and CLICK-COUNT, telling whether this is the first click, a double-click,
62 or a tripple-click.")
63
64 ;; time interval in millisecond within which successive clicks are
65 ;; considered related
66 (defconst vip-multiclick-timeout (if vip-xemacs-p
67 mouse-track-multi-click-time
68 double-click-time)
69 "*Time interval in millisecond within which successive clicks are
70 considered related.")
71
72 ;; current event click count; XEmacs only
73 (defvar vip-current-click-count 0)
74 ;; time stamp of the last click event; XEmacs only
75 (defvar vip-last-click-event-timestamp 0)
76
77 ;; Local variable used to toggle wraparound search on click.
78 (vip-deflocalvar vip-mouse-click-search-noerror t)
79
80 ;; Local variable used to delimit search after wraparound.
81 (vip-deflocalvar vip-mouse-click-search-limit nil)
82
83 ;; remembers prefix argument to pass along to commands invoked by second
84 ;; click.
85 ;; This is needed because in Emacs (not XEmacs), assigning to preix-arg
86 ;; causes Emacs to count the second click as if it was a single click
87 (defvar vip-global-prefix-argument nil)
88
89
90 \f
91 ;;; Code
92
93 (defsubst vip-multiclick-p ()
94 (not (vip-sit-for-short vip-multiclick-timeout t)))
95
96 ;; Returns window where click occurs
97 (defsubst vip-mouse-click-window (click)
98 (if vip-xemacs-p
99 (event-window click)
100 (posn-window (event-start click))))
101
102 ;; Returns window where click occurs
103 (defsubst vip-mouse-click-frame (click)
104 (window-frame (vip-mouse-click-window click)))
105
106 ;; Returns the buffer of the window where click occurs
107 (defsubst vip-mouse-click-window-buffer (click)
108 (window-buffer (vip-mouse-click-window click)))
109
110 ;; Returns the name of the buffer in the window where click occurs
111 (defsubst vip-mouse-click-window-buffer-name (click)
112 (buffer-name (vip-mouse-click-window-buffer click)))
113
114 ;; Returns position of a click
115 (defsubst vip-mouse-click-posn (click)
116 (if vip-xemacs-p
117 (event-point click)
118 (posn-point (event-start click))))
119
120
121 (defun vip-surrounding-word (count click-count)
122 "Returns word surrounding point according to a heuristic.
123 COUNT indicates how many regions to return.
124 If CLICK-COUNT is 1, `word' is a word in Vi sense.
125 If CLICK-COUNT is 2,then `word' is a Word in Vi sense.
126 If the character clicked on is a non-separator and is non-alphanumeric but
127 is adjacent to an alphanumeric symbol, then it is considered alphanumeric
128 for the purpose of this command. If this character has a matching
129 character, such as `\(' is a match for `\)', then the matching character is
130 also considered alphanumeric.
131 For convenience, in Lisp modes, `-' is considered alphanumeric.
132
133 If CLICK-COUNT is 3 or more, returns the line clicked on with leading and
134 trailing space and tabs removed. In that case, the first argument, COUNT,
135 is ignored."
136 (let ((modifiers "")
137 beg skip-flag result
138 word-beg)
139 (if (> click-count 2)
140 (save-excursion
141 (beginning-of-line)
142 (vip-skip-all-separators-forward 'within-line)
143 (setq beg (point))
144 (end-of-line)
145 (setq result (buffer-substring beg (point))))
146
147 (if (and (not (vip-looking-at-alphasep))
148 (or (save-excursion (vip-backward-char-carefully)
149 (vip-looking-at-alpha))
150 (save-excursion (vip-forward-char-carefully)
151 (vip-looking-at-alpha))))
152 (setq modifiers
153 (cond ((looking-at "\\\\") "\\\\")
154 ((looking-at "-") "C-C-")
155 ((looking-at "[][]") "][")
156 ((looking-at "[()]") ")(")
157 ((looking-at "[{}]") "{}")
158 ((looking-at "[<>]") "<>")
159 ((looking-at "[`']") "`'")
160 ((looking-at "\\^") "\\^")
161 ((vip-looking-at-separator) "")
162 (t (char-to-string (following-char))))
163 ))
164
165 ;; Add `-' to alphanum, if it wasn't added and if we are in Lisp
166 (or (looking-at "-")
167 (not (string-match "lisp" (symbol-name major-mode)))
168 (setq modifiers (concat modifiers "C-C-")))
169
170
171 (save-excursion
172 (cond ((> click-count 1) (vip-skip-nonseparators 'backward))
173 ((vip-looking-at-alpha modifiers)
174 (vip-skip-alpha-backward modifiers))
175 ((not (vip-looking-at-alphasep modifiers))
176 (vip-skip-nonalphasep-backward))
177 (t (if (> click-count 1)
178 (vip-skip-nonseparators 'backward)
179 (vip-skip-alpha-backward modifiers))))
180
181 (setq word-beg (point))
182
183 (setq skip-flag nil) ; don't move 1 char forw the first time
184 (while (> count 0)
185 (if skip-flag (vip-forward-char-carefully 1))
186 (setq skip-flag t) ; now always move 1 char forward
187 (if (> click-count 1)
188 (vip-skip-nonseparators 'forward)
189 (vip-skip-alpha-forward modifiers))
190 (setq count (1- count)))
191
192 (setq result (buffer-substring word-beg (point))))
193 ) ; if
194 ;; XEmacs doesn't have set-text-properties, but there buffer-substring
195 ;; doesn't return properties together with the string, so it's not needed.
196 (if vip-emacs-p
197 (set-text-properties 0 (length result) nil result))
198 result
199 ))
200
201
202 (defun vip-mouse-click-get-word (click count click-count)
203 "Returns word surrounding the position of a mouse click.
204 Click may be in another window. Current window and buffer isn't changed.
205 On single or double click, returns the word as determined by
206 `vip-surrounding-word-function'."
207
208 (let ((click-word "")
209 (click-pos (vip-mouse-click-posn click))
210 (click-buf (vip-mouse-click-window-buffer click)))
211 (or (natnump count) (setq count 1))
212 (or (natnump click-count) (setq click-count 1))
213
214 (save-excursion
215 (save-window-excursion
216 (if click-pos
217 (progn
218 (set-buffer click-buf)
219
220 (goto-char click-pos)
221 (setq click-word
222 (funcall vip-surrounding-word-function count click-count)))
223 (error "Click must be over a window."))
224 click-word))))
225
226
227 (defun vip-mouse-click-insert-word (click arg)
228 "Insert word clicked or double-clicked on.
229 With prefix argument, N, insert that many words.
230 This command must be bound to a mouse click.
231 The double-click action of the same mouse button must not be bound
232 \(or it must be bound to the same function\).
233 See `vip-surrounding-word' for the definition of a word in this case."
234 (interactive "e\nP")
235 (if vip-frame-of-focus ;; to handle clicks in another frame
236 (select-frame vip-frame-of-focus))
237
238 ;; turn arg into a number
239 (cond ((integerp arg) nil)
240 ;; prefix arg is a list when one hits C-u then command
241 ((and (listp arg) (integerp (car arg)))
242 (setq arg (car arg)))
243 (t (setq arg 1)))
244
245 (let (click-count interrupting-event)
246 (if (and
247 (vip-multiclick-p)
248 ;; This trick checks if there is a pending mouse event
249 ;; if so, we use this latter event and discard the current mouse click
250 ;; If the next pending event is not a mouse event, we execute
251 ;; the current mouse event
252 (progn
253 (setq interrupting-event (vip-read-event))
254 (vip-mouse-event-p last-input-event)))
255 (progn ;; interrupted wait
256 (setq vip-global-prefix-argument arg)
257 ;; count this click for XEmacs
258 (vip-event-click-count click))
259 ;; uninterrupted wait or the interrupting event wasn't a mouse event
260 (setq click-count (vip-event-click-count click))
261 (if (> click-count 1)
262 (setq arg vip-global-prefix-argument
263 vip-global-prefix-argument nil))
264 (insert (vip-mouse-click-get-word click arg click-count))
265 (if (and interrupting-event
266 (eventp interrupting-event)
267 (not (vip-mouse-event-p interrupting-event)))
268 (vip-set-unread-command-events interrupting-event))
269 )))
270
271 ;; arg is an event. accepts symbols and numbers, too
272 (defun vip-mouse-event-p (event)
273 (if (eventp event)
274 (string-match "\\(mouse-\\|frame\\|screen\\|track\\)"
275 (prin1-to-string (vip-event-key event)))))
276
277 ;; XEmacs has no double-click events. So, we must simulate.
278 ;; So, we have to simulate event-click-count.
279 (defun vip-event-click-count (click)
280 (if vip-xemacs-p
281 (progn
282 ;; if more than 1 second
283 (if (> (- (event-timestamp click) vip-last-click-event-timestamp)
284 vip-multiclick-timeout)
285 (setq vip-current-click-count 0))
286 (setq vip-last-click-event-timestamp (event-timestamp click)
287 vip-current-click-count (1+ vip-current-click-count)))
288 (event-click-count click)))
289
290
291
292 (defun vip-mouse-click-search-word (click arg)
293 "Find the word clicked or double-clicked on. Word may be in another window.
294 With prefix argument, N, search for N-th occurrence.
295 This command must be bound to a mouse click. The double-click action of the
296 same button must not be bound \(or it must be bound to the same function\).
297 See `vip-surrounding-word' for the details on what constitutes a word for
298 this command."
299 (interactive "e\nP")
300 (if vip-frame-of-focus ;; to handle clicks in another frame
301 (select-frame vip-frame-of-focus))
302 (let (click-word click-count
303 (previous-search-string vip-s-string))
304
305 (if (and
306 (vip-multiclick-p)
307 ;; This trick checks if there is a pending mouse event
308 ;; if so, we use this latter event and discard the current mouse click
309 ;; If the next pending event is not a mouse event, we execute
310 ;; the current mouse event
311 (progn
312 (vip-read-event)
313 (vip-mouse-event-p last-input-event)))
314 (progn ;; interrupted wait
315 (setq vip-global-prefix-argument
316 (or vip-global-prefix-argument arg))
317 ;; remember command that was before the multiclick
318 (setq this-command last-command)
319 ;; make sure we counted this event---needed for XEmacs only
320 (vip-event-click-count click))
321 ;; uninterrupted wait
322 (setq click-count (vip-event-click-count click))
323 (setq click-word (vip-mouse-click-get-word click nil click-count))
324
325 (if (> click-count 1)
326 (setq arg vip-global-prefix-argument
327 vip-global-prefix-argument nil))
328 (setq arg (or arg 1))
329
330 (vip-deactivate-mark)
331 (if (or (not (string= click-word vip-s-string))
332 (not (markerp vip-search-start-marker))
333 (not (equal (marker-buffer vip-search-start-marker)
334 (current-buffer)))
335 (not (eq last-command 'vip-mouse-click-search-word)))
336 (progn
337 (setq vip-search-start-marker (point-marker)
338 vip-local-search-start-marker vip-search-start-marker
339 vip-mouse-click-search-noerror t
340 vip-mouse-click-search-limit nil)
341
342 ;; make search string known to Viper
343 (setq vip-s-string (if vip-re-search
344 (regexp-quote click-word)
345 click-word))
346 (if (not (string= vip-s-string (car vip-search-history)))
347 (setq vip-search-history
348 (cons vip-s-string vip-search-history)))
349 ))
350
351 (push-mark nil t)
352 (while (> arg 0)
353 (vip-forward-word 1)
354 (condition-case nil
355 (progn
356 (if (not (search-forward click-word vip-mouse-click-search-limit
357 vip-mouse-click-search-noerror))
358 (progn
359 (setq vip-mouse-click-search-noerror nil)
360 (setq vip-mouse-click-search-limit
361 (save-excursion
362 (if (and
363 (markerp vip-local-search-start-marker)
364 (marker-buffer vip-local-search-start-marker))
365 (goto-char vip-local-search-start-marker))
366 (vip-line-pos 'end)))
367
368 (goto-char (point-min))
369 (search-forward click-word
370 vip-mouse-click-search-limit nil)))
371 (goto-char (match-beginning 0))
372 (message "Searching for: %s" vip-s-string)
373 (if (<= arg 1) ; found the right occurrence of the pattern
374 (progn
375 (vip-adjust-window)
376 (vip-flash-search-pattern)))
377 )
378 (error (beep 1)
379 (if (or (not (string= click-word previous-search-string))
380 (not (eq last-command 'vip-mouse-click-search-word)))
381 (message "`%s': String not found in %s"
382 vip-s-string (buffer-name (current-buffer)))
383 (message
384 "`%s': Last occurrence in %s. Back to beginning of search"
385 click-word (buffer-name (current-buffer)))
386 (setq arg 1) ;; to terminate the loop
387 (sit-for 2))
388 (setq vip-mouse-click-search-noerror t)
389 (setq vip-mouse-click-search-limit nil)
390 (if (and (markerp vip-local-search-start-marker)
391 (marker-buffer vip-local-search-start-marker))
392 (goto-char vip-local-search-start-marker))))
393 (setq arg (1- arg)))
394 )))
395
396 (defun vip-mouse-catch-frame-switch (event arg)
397 "Catch the event of switching frame.
398 Usually is bound to a 'down-mouse' event to work properly. See sample
399 bindings in viper.el and in the Viper manual."
400 (interactive "e\nP")
401 (setq vip-frame-of-focus nil)
402 ;; pass prefix arg along to vip-mouse-click-search/insert-word
403 (setq prefix-arg arg)
404 (if (eq last-command 'handle-switch-frame)
405 (setq vip-frame-of-focus vip-current-frame-saved))
406 ;; make Emacs forget that it executed vip-mouse-catch-frame-switch
407 (setq this-command last-command))
408
409 ;; Called just before switching frames. Saves the old selected frame.
410 ;; Sets last-command to handle-switch-frame (this is done automatically in
411 ;; Emacs.
412 ;; The semantics of switching frames is different in Emacs and XEmacs.
413 ;; In Emacs, if you select-frame A while mouse is over frame B and then
414 ;; start typing, input goes to frame B, which becomes selected.
415 ;; In XEmacs, input will go to frame A. This may be a bug in one of the
416 ;; Emacsen, but also may be a design decision.
417 ;; Also, in Emacs sending input to frame B generates handle-switch-frame
418 ;; event, while in XEmacs it doesn't.
419 ;; All this accounts for the difference in the behavior of
420 ;; vip-mouse-click-* commands when you click in a frame other than the one
421 ;; that was the last to receive input. In Emacs, focus will be in frame A
422 ;; until you do something other than vip-mouse-click-* command.
423 ;; In XEmacs, you have to manually select frame B (with the mouse click) in
424 ;; order to shift focus to frame B.
425 (defsubst vip-remember-current-frame (frame)
426 (setq last-command 'handle-switch-frame
427 vip-current-frame-saved (selected-frame)))
428
429
430 (cond ((vip-window-display-p)
431 (let* ((search-key (if vip-xemacs-p
432 [(meta shift button1up)] [M-S-mouse-1]))
433 (search-key-catch (if vip-xemacs-p
434 [(meta shift button1)] [M-S-down-mouse-1]))
435 (insert-key (if vip-xemacs-p
436 [(meta shift button2up)] [M-S-mouse-2]))
437 (insert-key-catch (if vip-xemacs-p
438 [(meta shift button2)] [M-S-down-mouse-2]))
439 (search-key-unbound (and (not (key-binding search-key))
440 (not (key-binding search-key-catch))))
441 (insert-key-unbound (and (not (key-binding insert-key))
442 (not (key-binding insert-key-catch))))
443 )
444
445 (if search-key-unbound
446 (global-set-key search-key 'vip-mouse-click-search-word))
447 (if insert-key-unbound
448 (global-set-key insert-key 'vip-mouse-click-insert-word))
449
450 ;; The following would be needed if you want to use the above two
451 ;; while clicking in another frame. If you only want to use them
452 ;; by clicking in another window, not frame, the bindings below
453 ;; aren't necessary.
454
455 ;; These must be bound to mouse-down event for the same mouse
456 ;; buttons as 'vip-mouse-click-search-word and
457 ;; 'vip-mouse-click-insert-word
458 (if search-key-unbound
459 (global-set-key search-key-catch 'vip-mouse-catch-frame-switch))
460 (if insert-key-unbound
461 (global-set-key insert-key-catch 'vip-mouse-catch-frame-switch))
462
463 (if vip-xemacs-p
464 (add-hook 'mouse-leave-frame-hook
465 'vip-remember-current-frame)
466 (defadvice handle-switch-frame (before vip-frame-advice activate)
467 "Remember the selected frame before the switch-frame event."
468 (vip-remember-current-frame (selected-frame))))
469 )))
470
471
472 ;;; viper-mous.el ends here