Use external tool `w3m' for dumping html to plain texts.
[xwl-elisp.git] / pabbrev.el
blobff5ea5c372663c1c1b9a0a77c10cf9f7db1bf3d6
1 ;;; pabbrev.el --- Predictive abbreviation expansion
3 ;; $Revision: 728 $
4 ;; $Date: 2007-11-19 16:33:45 +0000 (Mon, 19 Nov 2007) $
6 ;; This file is not part of Emacs
8 ;; Author: Phillip Lord <p.lord@russet.org.uk>
9 ;; Maintainer: Phillip Lord <p.lord@russet.org.uk>
10 ;; Maintainer (XEmacs): Martin Kuehl (martin.kuehl@gmail.com)
11 ;; Website: http://www.russet.org.uk
13 ;; COPYRIGHT NOTICE
15 ;; This program is free software; you can redistribute it and/or modify
16 ;; it under the terms of the GNU General Public License as published by
17 ;; the Free Software Foundation; either version 2, or (at your option)
18 ;; any later version.
20 ;; This program is distributed in the hope that it will be useful,
21 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
22 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 ;; GNU General Public License for more details.
25 ;; You should have received a copy of the GNU General Public License
26 ;; along with this program; see the file COPYING. If not, write to the
27 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
28 ;; Boston, MA 02111-1307, USA.
30 ;;; Commentary:
32 ;; The code provides a abbreviation expansion for Emacs. Its fairly
33 ;; similar to "dabbrev" expansion, which works based on the contents
34 ;; of the current buffer (or other buffers).
36 ;; Predictive abbreviation expansion works based on the previously
37 ;; written text. Unlike dynamic abbreviation, the text is analysed
38 ;; during idle time, while Emacs is doing nothing else. `pabbrev-mode'
39 ;; tells you when this is happening. If this irritates you unset
40 ;; `pabbrev-idle-timer-verbose'. The advantage of this is that its
41 ;; very quick to look up potential abbreviations, which means that the
42 ;; can be constantly displayed, without interfering with the user as
43 ;; they type. Certainly it works for me, on an old laptop, typing as
44 ;; fast as I can (which is fast, since I learnt to type with four
45 ;; fingers).
47 ;; pabbrev's main entry point is through the minor mode
48 ;; `pabbrev-mode'. There is also a global minor mode, called
49 ;; `global-pabbrev-mode', which does the same in all appropriate
50 ;; buffers.
52 ;; The current user interface looks like so...
54 ;; p[oint]
55 ;; pr[ogn]
56 ;; pre[-command-hook]
57 ;; pred[ictive]
59 ;; As the user types the system narrows down the possibilities. The
60 ;; narrowing is based on how many times the words have been used
61 ;; previously. By hitting [tab] at any point the user can complete the
62 ;; word. The [tab] key is normally bound to `indent-line'.
63 ;; `pabbrev-mode' preserves access to this command (or whatever else
64 ;; [tab] was bound to), if there is not current expansion.
66 ;; Sometimes you do not want to select the most commonly occurring
67 ;; word, but a less frequently occurring word. You can access this
68 ;; functionality by hitting [tab] for a second time. This takes you
69 ;; into a special suggestions buffer, from where you can select
70 ;; secondary selections. See `pabbrev-select-mode' for more
71 ;; details. There is also an option `pabbrev-minimal-expansion-p'
72 ;; which results in the shortest substring option being offered as the
73 ;; first replacement.
75 ;; But is this actually of any use? Well having use the system for a
76 ;; while now, I can say that it is sometimes. I originally thought
77 ;; that it would be good for text, but in general its not so
78 ;; useful. By the time you have realised that you have an expansion
79 ;; that you can use, hit tab, and checked that its done the right
80 ;; thing, you could have just typed the word directly in. It's much
81 ;; nicer in code containing buffers, where there tend to be lots of
82 ;; long words, which is obviously where an abbreviation expansion
83 ;; mechanism is most useful.
85 ;; Currently pabbrev builds up a dictionary on a per major-mode basis.
86 ;; While pabbrev builds up this dictionary automatically, you can also
87 ;; explicitly add a buffer, or a region to the dictionary with
88 ;; `pabbrev-scavenge-buffer', or `pabbrev-scavenge-region'. There is
89 ;; also a command `pabbrev-scavenge-some' which adds some words from
90 ;; around point. pabbrev remembers the word that it has seen already,
91 ;; so run these commands as many times as you wish.
93 ;; Although the main data structures are efficient during typing, the
94 ;; pay off cost is that they can take a reasonable amount of time, and
95 ;; processor power to gather up the words from the buffer. There are
96 ;; two main settings of interest to reduce this, which are
97 ;; `pabbrev-scavenge-some-chunk-size' and
98 ;; `pabbrev-scavenge-on-large-move'. `pabbrev-mode' gathers text from
99 ;; around point when point has moved a long way. This means symbols
100 ;; within the current context should be in the dictionary, but it can
101 ;; make Emacs choppy, in handling. Either reduce
102 ;; `pabbrev-scavenge-some-chunk-size' to a smaller value, or
103 ;; `pabbrev-scavenge-on-large-move' to nil to reduce the effects of
104 ;; this.
106 ;; NOTE: There are a set of standard conventions for Emacs minor
107 ;; modes, particularly with respect to standard key bindings, which
108 ;; pabbrev somewhat abuses. The justification for this is that the
109 ;; whole point of pabbrev mode is to speed up typing. Access to its
110 ;; main function has to be on a very easy to use keybinding. The tab
111 ;; seems to be a good choice for this. By preserving access to the
112 ;; original tab binding when there is no expansion, pabbrev mostly
113 ;; "does what I mean", at least in my hands.
115 ;;; Installation:
117 ;; To install this file place in your `load-path', and add
119 ;; (require 'pabbrev)
121 ;; to your .emacs
123 ;;; Status:
125 ;; At the moment this seems to be working mostly, although
126 ;; occasionally it seems to leave an expansion in the buffer.
127 ;; I wrote this on an Emacs 21.0 prerelease, that I haven't upgraded
128 ;; yet, running under RedHat 7.x. I've also tried this out on NT
129 ;; Emacs (21.1), where it seems to byte compile, and work. But it has not
130 ;; been tried out extensively. It will NOT work on Emacs' older than
131 ;; 21.
133 ;; This package now has an XEmacs maintainer (Martin Kuehl). He
134 ;; appears to have isolated the last few problems with pabbrev on
135 ;; XEmacs, and it is running stably there now. It has been tested on
136 ;; XEmacs 21.4, running on Debian and Ubuntu Linux.
138 ;;; Package Support:
140 ;; Some packages need extra support for pabbrev to work with. There are two
141 ;; plists properties which package developers can use.
143 ;; (put 'command-name 'pabbrev-expand-after-command t)
145 ;; means that the following the named command (in this case command-name),
146 ;; expansion will be offered. `self-insert-command' and a few others is
147 ;; normally fine, but not always.
149 ;; (put mode-name 'pabbrev-global-mode-excluded-modes t)
151 ;; will mean that any buffer with this major mode will not have
152 ;; global-pabbrev-mode activated.
155 ;;; Bugs;
157 ;; This package had an occasional bug which has historically been hard
158 ;; to track down and reproduce. Basically I end up with a load of
159 ;; offering expansions in the buffer. It looks something like this....
160 ;; pabbrev[-mode][v][ev][rev][brev][bbrev][abbrev] which is amusing
161 ;; the first time, but more or less totally useless.
163 ;; Thanks to the efforts of Martin Kuehl, I think we have tracked the
164 ;; cause of the problem now (the old version depended on
165 ;; pre-command-hook and post-command-hook being called
166 ;; consecutively. But sometimes they get called twice). Please let us
167 ;; know if you see this problem.
169 ;;; Limitations:
171 ;; pabbrev mode has a number of common limitations.
173 ;; 1) I think it would be nice to save the dictionaries, or offer
174 ;; facilities for doing so, before Emacs is killed. This would clearly
175 ;; depend on point 3 also. I'm not sure whether this is possible in a
176 ;; reasonable length of time. `pabbrev-debug-print-hashes' is
177 ;; certainly pretty slow.
179 ;; 2) I think that the scavenge functions are more computationally
180 ;; intensive than they need to be. They generally run in the idle
181 ;; cycle so its not a disaster. However more efficiency would mean the
182 ;; buffer could be gathered more quickly. This has the disadvantage
183 ;; that I would have to start to think about...
185 ;; 3) There are current no facilities at all, for removing words from
186 ;; the dictionaries. The original data structures, and in particular
187 ;; the usage hash, were partly designed to support this. One way I
188 ;; would do this is, for example, by just decreasing the number of
189 ;; usages by a given amount, and then deleting (probably after the
190 ;; sort during the scavenge), any cons cells with less than one
191 ;; usage. I'm not sure this is a problem though. The number of words
192 ;; in the dictionaries only increases slowly, then don't seem to grow
193 ;; that quickly, and they don't take up that much memory.
196 ;;; Bug Reporting
198 ;; Bug reports are more than welcome. However one particular problem
199 ;; with this mode is that it makes heavy use of
200 ;; `post-command-hook'. This is a very useful hook, but makes the
201 ;; package difficult to debug. If you want to send in a bug report it
202 ;; will help a lot if you can get a repeatable set of keypresses, that
203 ;; always causes the problem.
205 ;;; Implementation notes:
207 ;; The core data structures are two hashes. The first of which looks
208 ;; like this...
209 ;; "the" -> ("the" . 5)
210 ;; "there" -> ("there" . 3)
212 ;; I call this the usage hash, as it stores the total number of times
213 ;; each word has been seen.
215 ;; The second hash which is called the prefix hash. It stores
216 ;; prefixes, and usages...
218 ;; "t"->
219 ;; (("the" . 64)
220 ;; ("to" . 28)
221 ;; ("t" . 22)
222 ;; ("this" . 17))
224 ;; "th"->
225 ;; (("the" . 64)
226 ;; ("this" . 17)
227 ;; ("that" . 7))
229 ;; "the"->
230 ;; (("the" . 64)
231 ;; ("there" . 6)
232 ;; ("then" . 3)
233 ;; ("these" . 1))
235 ;; The alist cons cells in the first hash are conserved in the second,
236 ;; but the alists are not. The alist in the second hash is always
237 ;; sorted, on the basis of word usage.
239 ;; The point with this data structure is that I can find word usage
240 ;; in constant time, from the first hash, and completions for a given
241 ;; prefix, also in constant time. As access to completions happens as
242 ;; the user types speed is more important here, than during
243 ;; update, which is why the prefix hash maintains sorted alists. This
244 ;; is probably at the cost of slower updating of words.
246 ;;; Acknowledgements;
248 ;; Many thanks to Martin Kuehl for tracking down the last bug which
249 ;; stood between this being an "official" full release.
251 ;; Once again I need to thank Stefan Monnier, for his comments on my
252 ;; code base. Once day I will write a minor mode which Stefan Monnier
253 ;; does not offer me advice on, but it would appear that this day has not
254 ;; yet arrived!
256 ;; I should also thank Kim F. Storm (and in turn Stephen Eglen), as
257 ;; the user interface for this mode has been heavily influenced by
258 ;; ido.el, a wonderful package which I use every day.
260 ;; Carsten Dominik suggested I add the package suppport rather than the
261 ;; existing defcustom which was not as good I think.
264 ;;; Code:
265 (eval-when-compile (require 'cl))
266 (require 'thingatpt)
269 (eval-and-compile
270 (if (featurep 'xemacs)
271 (progn
272 (require 'overlay)
273 (unless (fboundp 'line-beginning-position)
274 (defalias 'line-beginning-position 'point-at-bol))
275 (unless (fboundp 'line-end-position)
276 (defalias 'line-end-position 'point-at-eol))
277 (unless (fboundp 'cancel-timer)
278 (defalias 'cancel-timer 'delete-itimer))
281 (defconst pabbrev-xemacs-p (string-match "XEmacs" (emacs-version))
282 "Non-nil if we are running in the XEmacs environment.")
284 (defgroup pabbrev nil
285 "Predicative abbreviation expansion."
286 :tag "Predictive Abbreviations."
287 :group 'abbrev
288 :group 'convenience)
290 (defcustom pabbrev-global-mode-not-buffer-names '("*Messages*")
291 "*Will not activate function `global-pabbrev-mode' if buffers have this name."
292 :type '(repeat (string :tag "Buffer name"))
293 :group 'pabbrev)
295 (defcustom pabbrev-global-mode-buffer-size-limit nil
296 "*Will not activate function `global-pabbrev-mode' if buffers are over this size (in bytes) (when non-nil)."
297 :type 'integer
298 :group 'pabbrev)
300 (defcustom pabbrev-marker-distance-before-scavenge 2000
301 "Minimal distance moved before we wish to scavenge."
302 :type 'integer
303 :group 'pabbrev)
306 ;;(setq pabbrev-scavenge-on-large-move nil)
307 (defcustom pabbrev-scavenge-on-large-move t
308 "*If non NIL, scavenge when a large buffer move has occured.
309 This can make Emacs' handling a little bumpy. See also
310 `pabbrev-scavenge-some-chunk-size', as reducing this, or increasing
311 `pabbrev-marker-distance-before-scavenge' is an alternative
312 to setting this to nil"
313 :type 'boolean
314 :group 'pabbrev)
316 (defcustom pabbrev-thing-at-point-constituent 'symbol
317 "Symbol defining THING which function `pabbrev-mode' works on.
318 This symbol should be understandable by
319 `bounds-of-thing-at-point'. This symbol defines what function `pabbrev-mode'
320 considers to be the basic unit of expansion. If if it set to `symbol',
321 for example, \"pabbrev-mode\" would be offered as an expansion, while
322 if it is set to `word' \"pabbrev\" and \"mode\" would be offered.
323 You could also set it to `whitespace' which would be really daft,
324 or `page' which would be silly in a different way."
325 :group 'pabbrev
326 :type 'symbol
327 :options '(symbol word))
329 (defcustom pabbrev-scavenge-some-chunk-size 40
330 "Number of words that `pabbrev-scavenge-words' gathers.
331 This also affects the speed with which pabbrev will scan through
332 the buffer during idle, so decrease this if too much processor
333 is being used, increase it if you want more. It's set quite
334 conservatively. If you get choppy performance when moving
335 around the buffer you should also consider
336 `pabbrev-scavenge-on-large-move' to nil."
337 :type 'integer
338 :group 'pabbrev)
340 (defcustom pabbrev-idle-timer-verbose t
341 "If non NIL, print messages while scavenging on idle timer.
343 At the moment this is set to t by default. The idle timer function,
344 `pabbrev-idle-timer-function' uses quite a bit of processor power, and
345 I want the users to known what is eating their CPU. I may change
346 this at a later date."
347 :type 'boolean
348 :group 'pabbrev)
350 (defcustom pabbrev-read-only-error t
351 "If non NIL, signal an error when in a read only buffer.
353 `pabbrev-mode' works by alterating the local buffer, so it's pointless
354 within a read only buffer. So, normally, it signals an error when an
355 attempt is made to use it in this way. But this is a pain if you toggle
356 buffers read only a lot. Set this to NIL, and pabbrev-mode will disable
357 it's functionality in read only buffers silently."
358 :type 'boolean
359 :group 'pabbrev)
362 ;; variable in progress
363 (defcustom pabbrev-minimal-expansion-p nil
364 "If t offer minimal expansion.
366 pabbrev can select the optimal expansion in two different ways. The
367 normal way is to offer the expansion which occurs most frequently in
368 the words which pabbrev has scavenged (in any buffer in the same
369 mode). The other method is to take the minimal occuring substring
370 present in any potential expansion; this is a lot more like standard
371 completion seen on a command line.
373 I'm not telling you which version, I prefer."
374 :type 'boolean
375 :group 'pabbrev
377 ;;(setq pabbrev-minimal-expansion-p t)
379 ;; stolen from font-lock!
380 (if pabbrev-xemacs-p
381 (defface pabbrev-suggestions-face
382 '((((class color) (background dark)) (:foreground "tan"))
383 (((class color) (background light)) (:foreground "green4"))
384 (((class grayscale) (background light)) (:foreground "DimGray" :italic t))
385 (((class grayscale) (background dark)) (:foreground "LightGray" :italic t))
386 (t (:bold t)))
387 "Face for displaying suggestions."
388 :group 'pabbrev)
389 (defface pabbrev-suggestions-face
390 '((((type tty) (class color)) (:foreground "green"))
391 (((class grayscale) (background light)) (:foreground "Gray90" :bold t))
392 (((class grayscale) (background dark)) (:foreground "DimGray" :bold t))
393 (((class color) (background light)) (:foreground "ForestGreen"))
394 (((class color) (background dark)) (:foreground "PaleGreen"))
395 (t (:bold t :underline t)))
396 "Face for displaying suggestions."
397 :group 'pabbrev))
399 (if pabbrev-xemacs-p
400 (defface pabbrev-suggestions-label-face
401 nil "Font lock mode face used to highlight suggestions"
402 :group 'pabbrev)
403 (defface pabbrev-suggestions-label-face
404 '((t
405 :inverse-video t))
406 "Font Lock mode face used to highlight suggestions"
407 :group 'pabbrev))
409 ;;;; End user Customizable variables.
412 ;;;; Begin Package Support.
415 ;; mark commands after which expansion should be offered
416 (mapc
417 (lambda(x)
418 (put x 'pabbrev-expand-after-command t))
419 '(self-insert-command mouse-set-point delete-char
420 backward-delete-char-untabify pabbrev-expand-maybe))
422 ;; mark modes in which to not activate pabbrev with global mode.
423 (mapc
424 (lambda(x)
425 (put x 'pabbrev-global-mode-excluded-modes t))
426 '(custom-mode
427 shell-mode
428 term-mode
429 telnet-mode
430 dired-mode
431 ;; gnus article mode is read-only so should be missed anyway,
432 ;; but it does something wierd so that it's not
433 gnus-article-mode
437 ;;;; End Package Support
439 ;;; Start data structures
440 (defvar pabbrev-usage-hash-modes nil
441 "List of modes with associated usage dictionaries.")
443 (defvar pabbrev-prefix-hash-modes nil
444 "List of modes with associated prefix dictionaries.")
446 (defmacro pabbrev-save-buffer-modified-p (&rest body)
447 "Eval BODY without affected buffer modification status"
448 `(let ((buffer-modified (buffer-modified-p))
449 (buffer-undo-list t))
450 ,@body
451 (set-buffer-modified-p buffer-modified)))
453 (defun pabbrev-get-usage-hash()
454 "Returns the usage hash for this buffer."
455 (let((hash (get major-mode 'pabbrev-usage-hash)))
456 (unless hash
457 (put major-mode 'pabbrev-usage-hash
458 (setq hash
459 (make-hash-table :test 'equal)))
460 (push major-mode pabbrev-usage-hash-modes))
461 hash))
463 (defun pabbrev-get-usage-dictionary-size()
464 "Returns the size of the usage hash."
465 (hash-table-count (pabbrev-get-usage-hash)))
467 (defun pabbrev-get-total-usages-dictionary()
468 "Returns the total number of usages from the usage hash"
469 (interactive)
470 (let ((size 0))
471 (maphash
472 (lambda(key value)
473 (setq size (+ size (cdr value))))
474 (pabbrev-get-usage-hash))
475 size))
477 (defun pabbrev-get-prefix-hash()
478 "Returns the prefix hash for the current buffer."
479 (let((hash (get major-mode 'pabbrev-prefix-hash)))
480 (unless hash
481 (put major-mode 'pabbrev-prefix-hash
482 (setq hash
483 (make-hash-table :test 'equal)))
484 (push major-mode pabbrev-prefix-hash-modes))
485 hash))
487 (defun pabbrev-add-word-usage (word)
488 "Add a WORD to the usage hash.
489 This is a function internal to the data structures. The
490 `pabbrev-add-word' is the main entry point to this functionality."
491 (let ((value
492 (gethash
493 ;; look for word usage cons we need a cons, but the last
494 ;; value is irrelevant.
495 word
496 (pabbrev-get-usage-hash))))
497 ;; so now we have cons, or nil
498 (if value
499 ;; increment occurences
500 (setcdr
501 value (+ 1 (cdr value)))
502 ;; we have no so make is
503 (setq value
504 (cons word 1)))
505 ;; so now we the cons cell for sure
506 ;; possible we should do this above, as I think it only needs
507 ;; doing for a new cons.
508 (puthash word value (pabbrev-get-usage-hash))
509 value))
512 (defun pabbrev-add-word-cons-with-prefix (prefix conscell)
513 "Add a word usage, and a PREFIX.
514 This function is internal to the data structures, and should normally
515 only be called, by `pabbrev-add-word'. CONSCELL should be cons
516 returned from `pabbrev-add-word-usage', while PREFIX should be a
517 prefix of the from the cons cell."
518 (let
519 ;; this should be an alist or nil
520 ((value (gethash prefix
521 (pabbrev-get-prefix-hash))))
522 (if value
523 ;; so we have an alist. Has our word been added to this alist
524 ;; before? If not, do so. If it has been added, then it will
525 ;; have been updated with the addition of the word
526 (if (not
527 (member conscell value))
528 (setq value (cons conscell value)))
529 ;; nothing in there, so create an alist with
530 ;; a single element
531 (setq value (list conscell)))
532 ;; so we now have the value alist...sort it and store it back in
533 ;; the hash
534 (puthash prefix
535 (pabbrev-sort-alist value conscell)
536 (pabbrev-get-prefix-hash))))
539 (defun pabbrev-sort-alist(alist cons)
540 ;; this sort is bit poor. It should be possible to do this in less
541 ;; than linear time, rather than n(log-n) as now. I think most of
542 ;; the time is spent entering the lambda function. The irony is that
543 ;; the sort is more or less sorted from the start, so a bubble sort
544 ;; would work in linear time. I've tried replacing with a linear
545 ;; sort, that is just placing the cons in the correct place, but in
546 ;; my hands, it's three or four times slower, on this buffer which
547 ;; has a lot of common prefixes, and so should take a while,
548 ;; probably because too much has to be done in lisp rather than with
549 ;; builtin's.
551 ;; Possibly the sort could be done on removing the value from the
552 ;; hash, which would reduce the amount of sorting that needs to be
553 ;; done. But it would then be in the command cycle rather than the
554 ;; idle loop, which seems like a really bad idea to me.
556 ;; When I wrote the data structures this was a bit of a worry as
557 ;; emacs spent most of its time in this loop, but now I've bolted
558 ;; on a user interface, its not so much of a problem, as plenty of
559 ;; time is spent in placing on the "been here" overlays....
560 (sort alist
561 ;;'pabbrev-comparitor-function))
562 (lambda(a b)
563 (> (cdr a) (cdr b)))))
565 (defun pabbrev-comparitor-function(a b)
566 (> (cdr a) (cdr b)))
569 (defun pabbrev-add-word (word)
570 "Add the usage of a WORD to the current dictionary."
571 (let ((conscell
572 (pabbrev-add-word-usage word)))
573 (dotimes (i (- (length word) 1))
574 (pabbrev-add-word-cons-with-prefix
575 (substring word 0 (1+ i))
576 conscell))))
578 (defun pabbrev-fetch-all-suggestions-for-prefix(prefix)
579 "Returns the suggestions for a given PREFIX.
580 Results are an alist, with cons with car of the word, and cdr of the
581 number of usages seen so far. This alist should NOT be altered, its
582 it's ordering is part of the core data structures"
583 (gethash prefix (pabbrev-get-prefix-hash)))
584 ;; Which completes the core data structures.
588 ;; This code provides the minor mode which displays, and accepts
589 ;; abbreviations.
590 (defvar pabbrev-mode-map (make-keymap)
591 "Keymap for pabbrev-minor-mode.")
593 ;; I don't understand this. I thought that this were equivalent. But
594 ;; modes which define [tab] get used in preference to \t. So I define
595 ;; both. Don't change these without also changing the definition of
596 ;; pabbrev-expand-maybe.
597 (define-key pabbrev-mode-map "\t" 'pabbrev-expand-maybe)
598 (define-key pabbrev-mode-map [tab] 'pabbrev-expand-maybe)
601 ;; xemacs has synced to newest easy-mmode now
602 ;;(if (not pabbrev-xemacs-p)
603 (define-minor-mode pabbrev-mode
604 "Toggle pabbrev mode.
605 With arg, turn on Predicative Abbreviation mode if and only if arg is
606 positive.
608 This mode is another abbreviation expansion mode somewhat like
609 `dabbrev-expand', in that it looks through the current buffer for
610 symbols that can complete the current symbol. Unlike `dabbrev-expand',
611 it does this by discovering the words during the Emacs idle time, and
612 places the results into data structures which enable very rapid
613 extraction of expansions. The upshot of this is that it can offer
614 suggestions as you type, without causing an unacceptable slow down.
616 There is an associated `global-pabbrev-mode' which turns on the mode
617 on in all buffers.
620 " Pabbrev"
621 pabbrev-mode-map
622 (when (and pabbrev-mode-map
623 buffer-read-only)
624 (if pabbrev-read-only-error
625 (error "Can not use pabbrev-mode in read only buffer"))))
627 ;; (easy-mmode-define-minor-mode pabbrev-mode
628 ;; "Toggle pabbrev mode.
629 ;; This mode is an abbreviation expansion mode. It looks through the
630 ;; current buffer, and offers expansions based on the words already
631 ;; there.
633 ;; I have only just recently ported this to XEmacs, and I don't
634 ;; personally use XEmacs, so it has received little or no testing."
635 ;; nil
636 ;; " Pabbrev"
637 ;; pabbrev-mode-map))
639 (if (fboundp 'easy-mmode-define-global-mode)
640 (easy-mmode-define-global-mode global-pabbrev-mode
641 pabbrev-mode pabbrev-global-mode))
643 (defun pabbrev-global-mode()
644 "Switch on `pabbrev-mode' in current buffer if appropriate.
645 Currently appropriate means, if the buffer is not read only, and is
646 not a minibuffer."
647 (unless (or buffer-read-only
648 pabbrev-mode
649 (get major-mode 'pabbrev-global-mode-excluded-modes)
650 ;; don't turn on in non listable buffers
651 (equal (substring (buffer-name) 0 1) " ")
652 (when pabbrev-global-mode-buffer-size-limit
653 (> (buffer-size) pabbrev-global-mode-buffer-size-limit))
654 (member (buffer-name) pabbrev-global-mode-not-buffer-names)
655 (window-minibuffer-p (selected-window)))
656 (let
657 ;; set the chunk size low, or the global mode takes for ever
658 ;; to switch on
659 ((pabbrev-scavenge-some-chunk-size 0))
660 (pabbrev-mode))))
662 ;; hooks for switching on and off.
663 (add-hook 'pabbrev-mode-on-hook
664 'pabbrev-mode-on)
665 (add-hook 'pabbrev-mode-off-hook
666 'pabbrev-mode-off)
668 (defvar pabbrev-marker nil
669 "Location of current insertion, or nil.
670 This variable is not actually a marker, but a cons of
671 start and end positions")
672 (make-variable-buffer-local 'pabbrev-marker)
674 (defvar pabbrev-expansion nil
675 "Currently displayed expansion, or nil.")
676 (make-variable-buffer-local 'pabbrev-expansion)
678 (defvar pabbrev-expansion-suggestions nil
679 "Current expansion suggestions, or nil.")
680 (make-variable-buffer-local 'pabbrev-expansion-suggestions)
682 (defvar pabbrev-marker-last-expansion nil
683 "Marks where the last possible expansion was.")
684 (make-variable-buffer-local 'pabbrev-marker-last-expansion)
686 (defun pabbrev-mode-on()
687 "Turn `pabbrev-mode' on."
688 (make-local-hook 'pre-command-hook)
689 (add-hook 'pre-command-hook 'pabbrev-pre-command-hook nil t)
690 (make-local-hook 'post-command-hook)
691 (add-hook 'post-command-hook 'pabbrev-post-command-hook nil t))
693 (defun pabbrev-mode-off()
694 "Turn `pabbrev-mode' off."
695 ;; we have to remove the binding for tab. Other wise next time we
696 ;; switch the mode on, this binding will be found, and set for
697 ;; pabbrev-tab-previously-defined
698 (remove-hook 'pre-command-hook 'pabbrev-pre-command-hook t)
699 (remove-hook 'post-command-hook 'pabbrev-post-command-hook t))
701 ;;(defun test()(interactive)(let ((last-command 'self-insert-command))(pabbrev-post-command-hook)))
705 ;;(defun test()
706 ;; (interactive)
707 ;; (pabbrev-insert-suggestion
708 ;; (pabbrev-thing-at-point)
709 ;; (cdr (pabbrev-bounds-of-thing-at-point))
710 ;; (pabbrev-fetch-all-suggestions-for-prefix
711 ;; (pabbrev-thing-at-point))))
714 (defun pabbrev-post-command-hook()
715 "Offer expansion if appropriate.
716 This function is normally run off the `post-command-hook'."
717 (condition-case err
718 ;; pabbrev will not switch on in a read only buffer. But the
719 ;; buffer may have become read only between the time that it was
720 ;; switched on, and now. So we need to check this anyway.
721 (unless (or buffer-read-only
722 ;; This seems to be an issue in xemacs, so check for
723 ;; this as well.
724 (window-minibuffer-p (selected-window)))
725 (save-excursion
726 ;; ensure that any suggestion is deleted.
727 (when pabbrev-marker
728 (pabbrev-delete-last-suggestion))
729 (let ((word (pabbrev-thing-at-point))
730 (bounds (pabbrev-bounds-of-thing-at-point))
731 (suggestions))
732 (if (and
733 ;; last command was a symbol
734 (symbolp last-command)
735 ;; we have just had an appropriate command
736 (get last-command 'pabbrev-expand-after-command)
737 ;; is word at point
738 word
739 ;; we are at the end of it.
740 (= (point) (cdr bounds))
741 ;; and we have some suggestions.
742 (setq suggestions (pabbrev-fetch-all-suggestions-for-prefix word)))
743 (progn
744 (pabbrev-insert-suggestion word (cdr bounds) suggestions)
745 (pabbrev-post-command-check-movement))))))
746 (error
747 (pabbrev-command-hook-fail err "post" ))))
750 (defun pabbrev-delete-last-suggestion()
751 "Remove previously inserted suggestions."
752 (pabbrev-save-buffer-modified-p
753 ;; I don't think we need to check for buffer-read-only
754 ;; here, because pabbrev-marker will always be nil in a
755 ;; read only buffer. I could be wrong about this of
756 ;; course.
757 (pabbrev-delete-overlay)
758 (delete-region (car pabbrev-marker) (cdr pabbrev-marker))
759 (setq pabbrev-marker nil)))
762 (defun pabbrev-pre-command-hook()
763 "Remove offering expansion from the buffer, if present.
764 This function is normally run off the `pre-command-hook'"
765 (condition-case err
766 (progn
767 (unless (memq this-command
768 pabbrev-expand-commands)
769 (setq pabbrev-expansion nil
770 pabbrev-expansion-suggestions nil))
771 (when pabbrev-marker
772 (pabbrev-delete-last-suggestion)))
773 ;;catch the error
774 (error
775 (pabbrev-command-hook-fail err "pre"))))
777 (defun pabbrev-command-hook-fail(err hook)
778 "Advise user of a failure command-hooks.
779 This function should only run as the result of a bug.
780 A message is sent, as we can do little else safely,
781 on the `post-command-hook', or `pre-command-hook'."
782 (message "pabbrev mode has failed on %s hook: %s "
783 hook (error-message-string err))
784 (remove-hook 'pre-command-hook 'pabbrev-pre-command-hook t)
785 (remove-hook 'post-command-hook 'pabbrev-post-command-hook t)
786 (with-output-to-temp-buffer "*pabbrev-fail*"
787 (princ "There has been an error in pabbrev-mode. This mode normally
788 makes use of \"post-command-hook\", which runs after every command. If this
789 error continued Emacs could be made unusable, so pabbrev-mode has attempted
790 to disable itself. So although it will appear to still be on, it won't do
791 anything. Toggling it off, and then on again will usually restore functionality.\n")
792 (princ "The following is debugging information\n\n")
793 (princ (error-message-string err))
794 (princ "\n\nBacktrace is: \n" )
795 (let ((standard-output (get-buffer "*pabbrev-fail*" )))
796 (backtrace)))
797 (select-window (get-buffer-window "*pabbrev-fail*"))
798 (error "Error in pabbrev-mode"))
800 (defun pabbrev-marker-last-expansion()
801 "Fetch marker for last offered expansion."
802 (unless
803 pabbrev-marker-last-expansion
804 (setq pabbrev-marker-last-expansion
805 (set-marker (make-marker)
806 (point) (current-buffer))))
807 pabbrev-marker-last-expansion)
809 (defun pabbrev-update-marker()
810 (set-marker (pabbrev-marker-last-expansion)
811 (point) (current-buffer)))
813 (defun pabbrev-post-command-check-movement()
814 (let ((distance
815 (abs (- (point) (marker-position
816 (pabbrev-marker-last-expansion))))))
817 (if (> distance pabbrev-marker-distance-before-scavenge)
818 ;; we have moved a lot in the buffer
819 (progn
820 (pabbrev-debug-message "Scavenge due to buffer marker")
821 (pabbrev-scavenge-some)
822 (pabbrev-update-marker)))))
824 (defvar pabbrev-overlay nil
825 "Overlay for offered completion.")
826 (make-variable-buffer-local 'pabbrev-overlay)
828 (defun pabbrev-set-overlay(start end)
829 "Move overlay to START END location."
830 (unless pabbrev-overlay
831 (setq pabbrev-overlay
832 ;; set an overlay at 1 1. Originally this used to be a 0 0 but
833 ;; it crashes xemacs...well I never....
834 (make-overlay 1 1)))
835 (overlay-put pabbrev-overlay
836 'face 'pabbrev-suggestions-face)
837 (move-overlay pabbrev-overlay start end (current-buffer)))
839 (defun pabbrev-delete-overlay()
840 "Make overlay invisible."
841 (if pabbrev-overlay
842 (delete-overlay pabbrev-overlay)))
847 (defun pabbrev-insert-suggestion(prefix end suggestions)
848 "Insert a suggestion into the buffer.
849 The suggestion should start with PREFIX, and be entered
850 at buffer position END."
851 (interactive)
852 (let* ((suggestion
853 (if (not pabbrev-minimal-expansion-p)
854 (car (car suggestions))
855 (try-completion "" suggestions))))
856 (let ((expansion
857 (if suggestion
858 (substring suggestion
859 (length prefix))
860 "")))
861 (save-excursion
862 (if (< 0 (length expansion))
863 ;; add the abbreviation to the buffer
864 (pabbrev-save-buffer-modified-p
865 (insert
866 "[" expansion "]" )
867 ;; store everything. Most importantly the pabbrev-marker!
868 (setq
869 pabbrev-expansion expansion
870 pabbrev-expansion-suggestions suggestions
871 pabbrev-marker
872 (cons end (point)))
873 (let ((point-1 (- (point) 1)))
874 (pabbrev-set-overlay
875 (- point-1 (length expansion)) point-1))))))))
880 (defun pabbrev-expand-maybe()
881 "Expand abbreviation, or run previous command.
882 If there is no expansion the command returned by
883 `pabbrev-get-previous-binding' will be run instead."
884 (interactive)
885 ;; call expand if we can
886 (if (and (eq last-command 'pabbrev-expand-maybe)
887 (> (length pabbrev-expansion-suggestions) 1))
888 (pabbrev-suggestions-goto-buffer)
889 (if pabbrev-expansion
890 (pabbrev-expand)
891 ;; hopefully this code will actually work as intended now. It's
892 ;; been around the house a few times already!
893 (let ((prev-binding
894 (pabbrev-get-previous-binding)))
895 (if (and (fboundp prev-binding)
896 (not (eq prev-binding 'pabbrev-expand-maybe)))
897 (funcall prev-binding))))))
900 (defun pabbrev-show-previous-binding ()
901 (interactive)
902 (message "Previous binding is: %s"
903 (pabbrev-get-previous-binding)))
905 (defun pabbrev-get-previous-binding ()
906 "Show the binding of tab if pabbrev were not active.
907 The command `pabbrev-show-previous-binding' prints this out."
908 (let ((pabbrev-mode nil))
909 ;; This is the original and satisfying solution
910 ;;(key-binding (char-to-string last-command-event)))))
912 ;; This is the new and unsatisfying one. The
913 ;; keybindings are hard coded here, because I defined
914 ;; [tab] and \t earlier. Both are tab, but the former
915 ;; gets used in preference to the later.
916 (or (key-binding [tab])
917 (key-binding "\t"))))
919 ;; ;; I think that I have this worked out now.
920 ;; (if (eq prev-binding 'pabbrev-expand-maybe)
921 ;; (message "pabbrev known bug! Avoiding recursive tab")
922 ;; (funcall prev-binding))))))
924 ;; (define-key pabbrev-mode-map "\t" nil)
925 ;; (let ((tunneled-keybinding (key-binding "\t")))
926 ;; (if (and (fboundp tunneled-keybinding)
927 ;; (not (eq tunneled-keybinding 'pabbrev-expand-maybe)))
928 ;; (funcall tunneled-keybinding)))
929 ;; (define-key pabbrev-mode-map "\t" 'pabbrev-expand-maybe)))
931 (defvar pabbrev-expand-previous-word nil)
932 (defun pabbrev-expand()
933 "Expand abbreviation"
934 (setq pabbrev-expand-previous-word (pabbrev-thing-at-point))
935 (if pabbrev-expansion
936 (insert pabbrev-expansion)
937 (message "No expansion"))
938 (setq pabbrev-expansion nil))
941 (defvar pabbrev-expand-commands
942 '(pabbrev-expand-maybe pabbrev-expand)
943 "List of commands which will be used expand.
944 We need to know this, or the possible expansions are deleted
945 before the command gets run.")
947 ;; suggestions buffer
948 ;; (defvar pabbrev-suggestions-buffer-enable nil)
949 ;; (defun pabbrev-suggestions-toggle()
950 ;; "NOT FULLY FUNCTIONAL. Enable interactive suggestions window.
951 ;; This is just a test function at the moment. The idea is that you will
952 ;; be able to see alternate suggestions as you type. This will be most
953 ;; useful in a programming buffer. At the moment there is no way of
954 ;; actually selecting these abbreviations. But it appears that the core
955 ;; data structures are quick enough to work."
956 ;; (interactive)
957 ;; (if pabbrev-suggestions-buffer-enable
958 ;; (progn
959 ;; (setq pabbrev-suggestions-buffer-enable nil)
960 ;; (remove-hook 'post-command-hook
961 ;; 'pabbrev-suggestions-delete-window)
962 ;; (delete-window (get-buffer-window " *pabbrev suggestions*"))
963 ;; (message "pabbrev suggestions off"))
964 ;; (setq pabbrev-suggestions-buffer-enable t)
965 ;; (add-hook 'post-command-hook
966 ;; 'pabbrev-suggestions-delete-window)
967 ;; (message "pabbrev suggestions on")))
969 (defun pabbrev-suggestions-delete-window()
970 "Delete the suggestions window."
971 (interactive)
972 (unless
973 (or pabbrev-mode
974 (eq (buffer-name) " *pabbrev suggestions*"))
975 (delete-window (get-buffer-window " *pabbrev suggestions*"))
976 (set-window-configuration pabbrev-window-configuration)))
978 ;; (defun pabbrev-post-command-delete-suggestions()
979 ;; (interactive)
980 ;; (if pabbrev-suggestions-buffer-enable
981 ;; (progn
982 ;; ;; this isn't perfect. The window pops up in a fairly random place.
983 ;; (with-output-to-temp-buffer " *pabbrev suggestions*")
984 ;; (shrink-window-if-larger-than-buffer (get-buffer-window " *pabbrev suggestions*")))))
986 ;; (defun pabbrev-post-command-show-suggestions(suggestions prefix)
987 ;; (if pabbrev-suggestions-buffer-enable
988 ;; (pabbrev-suggestions-buffer suggestions prefix)))
991 (defvar pabbrev-window-configuration nil
992 "Stores the window configuration before presence of a window buffer")
995 (defun pabbrev-suggestions-goto-buffer()
996 "Jump into the suggestions buffer."
997 ;; (if pabbrev-suggestions-buffer-enable
998 ;; (pabbrev-suggestions-delete-window))
999 (setq pabbrev-window-configuration (current-window-configuration))
1000 (pabbrev-suggestions-buffer pabbrev-expansion-suggestions "")
1001 (shrink-window-if-larger-than-buffer
1002 (select-window (get-buffer-window " *pabbrev suggestions*"))))
1004 (defvar pabbrev-suggestions-from-buffer nil)
1005 (defvar pabbrev-suggestions-done-suggestions nil)
1006 (defvar pabbrev-suggestions-best-suggestion nil)
1008 (defun pabbrev-suggestions-buffer(suggestions prefix)
1009 "Form the suggestions buffer."
1010 (with-output-to-temp-buffer " *pabbrev suggestions*"
1011 (setq pabbrev-suggestions-from-buffer (current-buffer))
1012 (setq pabbrev-suggestions-best-suggestion
1013 (car suggestions))
1014 (setq pabbrev-suggestions-done-suggestions
1015 (pabbrev-suggestions-limit-alpha-sort suggestions))
1016 (setq suggestions pabbrev-suggestions-done-suggestions)
1017 (let
1018 ((window-width (- (window-width) 1)))
1019 (save-excursion
1020 (set-buffer (get-buffer " *pabbrev suggestions*"))
1021 (pabbrev-suggestions-setup)`
1022 (princ
1023 (concat;;"Current Word: " prefix " "
1024 "Max Substring: " (try-completion "" suggestions)
1025 "\n"))
1026 (princ
1027 (concat
1028 "Best Match: " (car pabbrev-suggestions-best-suggestion)
1029 "\n"))
1030 (if suggestions
1031 (loop for i from 0 to 9 do
1032 ;; are we less than the suggestions
1033 (if (< i (length suggestions))
1034 (progn
1035 (goto-char (point-max))
1036 ;; insert all the suggestions
1037 (let ((next-suggestion
1038 (concat
1039 (number-to-string i)
1040 ") "
1041 (car (nth i suggestions)) " " ))
1042 (line-length
1043 (- (line-end-position) (line-beginning-position))))
1044 ;; if well. are not on the first suggestion,
1045 (if (and (> i 0)
1046 ;; and the line will be too long
1047 (< window-width
1048 (+ line-length (length next-suggestion))))
1049 ;; add a new line.
1050 (princ "\n"))
1051 (princ next-suggestion)
1052 (let ((start (- (point) (length next-suggestion))))
1053 (overlay-put
1054 (make-overlay start (+ 2 start))
1055 'face 'pabbrev-suggestions-label-face))))))))))
1056 (shrink-window-if-larger-than-buffer (get-buffer-window " *pabbrev suggestions*")))
1058 (defun pabbrev-suggestions-limit-alpha-sort(suggestions)
1059 "Limit suggestions and sort."
1060 (delq nil
1061 (sort (pabbrev-suggestions-subseq suggestions 0 10)
1062 (lambda(a b)
1063 (string< (car a) (car b))))))
1065 (defun pabbrev-suggestions-subseq(sequence from to)
1066 "Return subsequence from seq.
1067 FROM starting here
1068 TO finishing here.
1069 Amazing though it seems the implementation of this differs between Emacs,
1070 and XEmacs. Irritating or what!
1071 The Emacs version copes with numbers past the end, and backs with nil
1072 values. XEmacs uses its own builtin rather than the one in the CL package.
1073 It crashes under the same circumstances. Yeech."
1074 (if pabbrev-xemacs-p
1075 (subseq sequence from
1076 (min to
1077 (length sequence)))
1078 (subseq sequence from to)))
1080 (defun pabbrev-suggestions-setup()
1081 "Set up suggestions major mode."
1082 (unless (fboundp 'pabbrev-select-mode)
1083 ;; define pabbrev select mode
1084 (define-derived-mode pabbrev-select-mode fundamental-mode
1085 "Pabbrev Select"
1086 "Major mode for selecting `pabbrev-mode' expansions.
1087 The number keys selects the various possible expansions. \\[pabbrev-suggestions-delete]
1088 removes the previously added expansion, \\[pabbrev-suggestions-minimum] selects the minimum
1089 matching substring, while \\[pabbrev-suggestions-delete-window] just deletes the window
1090 \\{pabbrev-select-mode-map}")
1091 (setq pabbrev-select-mode-map (make-sparse-keymap))
1092 (loop for i from 33 to 126 do
1093 (define-key pabbrev-select-mode-map (char-to-string i) 'pabbrev-noop))
1094 (define-key pabbrev-select-mode-map "\t" 'pabbrev-suggestions-select-default)
1095 (define-key pabbrev-select-mode-map [delete] 'pabbrev-suggestions-delete)
1096 (define-key pabbrev-select-mode-map [backspace] 'pabbrev-suggestions-delete)
1097 (define-key pabbrev-select-mode-map "\C-m" 'pabbrev-suggestions-minimum)
1098 (define-key pabbrev-select-mode-map " " 'pabbrev-suggestions-delete-window)
1099 ;; define all the standard insert commands
1100 (loop for i from 0 to 9 do
1101 (define-key pabbrev-select-mode-map
1102 (number-to-string i) 'pabbrev-suggestions-select)))
1103 (pabbrev-select-mode))
1105 (defun pabbrev-noop()
1106 "Do absolutely nothing.
1107 This command is used to nobble the suggestions buffer
1108 self inserting commands."
1109 (interactive))
1111 (defun pabbrev-suggestions-select-default()
1112 "Select the most commonly occuring string."
1113 (interactive)
1114 (if pabbrev-suggestions-best-suggestion
1115 (pabbrev-suggestions-insert
1116 (car pabbrev-suggestions-best-suggestion))))
1118 (defun pabbrev-suggestions-delete()
1119 "Delete the last suggestion."
1120 (interactive)
1121 (pabbrev-suggestions-insert
1122 pabbrev-expand-previous-word))
1124 (defun pabbrev-suggestions-minimum()
1125 "Select the maximally occuring substring."
1126 (interactive)
1127 (pabbrev-suggestions-insert
1128 ;;(try-completion "" pabbrev-suggestions-done-suggestions)))
1129 (try-completion "" (pabbrev-suggestions-subseq pabbrev-suggestions-done-suggestions 0 10))))
1131 (defun pabbrev-suggestions-insert(insertion)
1132 "Actually insert the suggestion."
1133 (let ((point))
1134 (save-excursion
1135 (set-buffer pabbrev-suggestions-from-buffer)
1136 (let ((bounds (pabbrev-bounds-of-thing-at-point)))
1137 (progn
1138 (delete-region (car bounds) (cdr bounds))
1139 (insert insertion)
1140 (setq point (point)))))
1141 (pabbrev-suggestions-delete-window)
1142 (if point
1143 (goto-char point))))
1145 (defun pabbrev-suggestions-select(&optional index)
1146 "Select one of the numbered suggestions."
1147 (interactive)
1148 (let ((insert-index
1149 (or index
1150 (string-to-number
1151 (char-to-string last-command-event)))))
1152 (if (< insert-index
1153 (length pabbrev-suggestions-done-suggestions))
1154 (pabbrev-suggestions-insert
1155 (car
1156 (nth insert-index pabbrev-suggestions-done-suggestions))))))
1159 ;; These functions define movement around the buffer, which
1160 ;; determines what pabbrev considers to be a "word"
1161 (defun pabbrev-forward-thing(&optional number)
1162 "Move forward a pabbrev word. Or backwards if number -1"
1163 (interactive)
1164 (forward-thing pabbrev-thing-at-point-constituent number))
1166 (defun pabbrev-thing-at-point()
1167 "Get thing at point."
1168 (let ((bounds (pabbrev-bounds-of-thing-at-point)))
1169 (if bounds
1170 (buffer-substring-no-properties
1171 (car bounds) (cdr bounds)))))
1173 (defun pabbrev-bounds-of-thing-at-point()
1174 "Get the bounds of the thing at point"
1175 (bounds-of-thing-at-point
1176 pabbrev-thing-at-point-constituent))
1179 ;; These functions deal with scavenging word usage from the buffer,
1180 ;; which are then added to the dictionary.
1181 (defun pabbrev-bounds-marked-p (start end)
1182 "Return t if anywhere between START and END is marked."
1183 (save-excursion
1184 (let ((retn))
1185 (do ((i start (1+ i)))
1186 ((> i end))
1188 (setq retn
1189 (get-text-property i 'pabbrev-added))
1190 (setq i end)))
1191 retn)))
1193 (defun pabbrev-mark-add-word (bounds)
1194 "Add word in BOUNDS as abbreviation, and mark the buffer."
1195 (if bounds
1196 (let ((start (car bounds))
1197 (end (cdr bounds)))
1198 (unless
1199 ;; is this word or part of it already added?
1200 (pabbrev-bounds-marked-p start end)
1201 ;; mark the word visibly as well.
1202 (pabbrev-debug-display start end)
1203 ;; set a property so that we know what we have done.
1204 (pabbrev-save-buffer-modified-p
1205 (add-text-properties start end
1206 '(pabbrev-added t)))
1207 ;; and add the word to the system.
1208 (pabbrev-add-word
1209 (buffer-substring-no-properties start end))))))
1211 (defun pabbrev-scavenge-some()
1212 "Gather some words up from around point"
1213 (interactive)
1214 (save-excursion
1215 ;; move somewhat away from point, as this is likely to not contain
1216 ;; complete words.
1217 (pabbrev-forward-thing -2)
1218 (pabbrev-scavenge-words -1
1219 (* 2 pabbrev-scavenge-some-chunk-size))
1220 (save-excursion
1221 (pabbrev-forward-thing 2)
1222 (pabbrev-scavenge-words 1 pabbrev-scavenge-some-chunk-size))))
1224 (defun pabbrev-scavenge-region()
1225 (interactive)
1226 (narrow-to-region (region-beginning) (region-end))
1227 (pabbrev-scavenge-buffer))
1230 (defun pabbrev-scavenge-buffer-fast()
1231 (interactive)
1232 (message "pabbrev fast scavenging buffer...")
1233 (save-excursion
1234 (goto-char (point-min))
1235 (while (pabbrev-forward-thing)
1237 (let* ((bounds (pabbrev-bounds-of-thing-at-point))
1238 (start (car bounds))
1239 (stop (cdr bounds)))
1240 (unless
1241 (pabbrev-bounds-marked-p start stop)
1242 (pabbrev-add-word
1243 (buffer-substring-no-properties start stop)))))
1245 (pabbrev-debug-message "Dictionary size %s total usage %s"
1246 (pabbrev-get-usage-dictionary-size))
1247 (pabbrev-save-buffer-modified-p
1248 (add-text-properties (point-min) (point-max)
1249 '(pabbrev-added t)))
1250 (message "pabbrev fast scavenging buffer...done.")))
1253 (defun pabbrev-scavenge-buffer()
1254 (interactive)
1255 (save-excursion
1256 (goto-char (point-min))
1258 (working-status-forms "pabbrev scavenging buffer" "done"
1259 (while (pabbrev-forward-thing)
1260 (working-status (/ (* 100 (point)) (point-max)))
1261 ;;(message "pabbrev scavenging (buffer %s words %s line %s done %s %%)..."
1262 ;; (current-buffer)
1263 ;; (pabbrev-get-usage-dictionary-size)
1264 ;; current-line
1265 ;; (/ (* 100 current-line) total-line))
1266 ;;(message "pabbrev scavenging buffer...On line %s"
1267 ;; (count-lines (point-min) (point)))
1268 (pabbrev-mark-add-word
1269 (pabbrev-bounds-of-thing-at-point)))
1270 (working-status t))
1272 (pabbrev-debug-message "Dictionary size %s total usage %s"
1273 (pabbrev-get-usage-dictionary-size))
1274 (message "pabbrev scavenging buffer...done.")))
1277 (defun pabbrev-scavenge-words(&optional direction number)
1278 "Scavenge words from current buffer, starting from point.
1279 DIRECTION is in which direction we should work,
1280 NUMBER is how many words we should try to scavenge"
1281 (if (not direction)
1282 (setq direction 1))
1283 (if (not number)
1284 (setq number 20))
1285 (save-excursion
1286 (dotimes (i number)
1287 (pabbrev-forward-thing direction)
1288 (pabbrev-mark-add-word
1289 (pabbrev-bounds-of-thing-at-point)))
1290 (point)))
1292 ;; switch on the idle timer if required when the mode is switched on.
1293 (add-hook 'pabbrev-mode-on-hook
1294 'pabbrev-ensure-idle-timer)
1295 ;; also run the idle timer function, to put some works in the
1296 ;; dictionary.
1297 (add-hook 'pabbrev-mode-on-hook
1298 'pabbrev-scavenge-some)
1300 (defvar pabbrev-long-idle-timer nil
1301 "Timer which adds whole buffer.
1302 There are two idle timers which run for function `pabbrev-mode'. This
1303 one doesn't start for a while, but once it has will work its way
1304 through the whole buffer. In prints out a message to say what its
1305 doing, and stops on user input. The variable
1306 `pabbrev-short-idle-timer' is the other.
1307 The idea here is that the short timer will pick up most of the recent
1308 changes, and will not bother the user. The long timer will slowly
1309 gather up the whole buffer, telling the user what it is doing, in case
1310 it takes up too much processor. If this happened after a second it
1311 would be irritating in the extreme.")
1313 (defvar pabbrev-short-idle-timer nil
1314 "Timer which adds a few words.
1315 See `pabbrev-long-idle-timer'.")
1317 (defun pabbrev-ensure-idle-timer()
1318 (unless nil
1319 (if (not (and pabbrev-short-idle-timer
1320 pabbrev-long-idle-timer))
1321 (pabbrev-start-idle-timer))))
1323 (defun pabbrev-start-idle-timer()
1324 (setq pabbrev-long-idle-timer
1325 (run-with-idle-timer 5 t 'pabbrev-idle-timer-function))
1326 (setq pabbrev-short-idle-timer
1327 (run-with-idle-timer 1 t 'pabbrev-short-idle-timer)))
1329 ;;(setq pabbrev-disable-timers t)
1330 (defvar pabbrev-disable-timers nil)
1331 ;; I don't understand why this is necessary but it seems to help the
1332 ;; slow idle timer work in the correct buffer. I suspect someother
1333 ;; timer is screwing up with the current buffer...
1334 (defvar pabbrev-timer-buffer nil)
1336 (defun pabbrev-short-idle-timer(&optional buffer)
1337 "Add a few words to the dictionary."
1338 (save-excursion
1339 (set-buffer (or buffer (current-buffer)))
1340 ;; remember which buffer we have just looked at.
1341 (setq pabbrev-timer-buffer (current-buffer))
1342 (if (and pabbrev-mode (not pabbrev-disable-timers))
1343 (progn
1344 (pabbrev-debug-message "running short idle timer")
1345 ;;(message "Running short timer in %s" (current-buffer))
1346 (pabbrev-scavenge-some)
1347 (pabbrev-debug-message "Dictionary size %s total usage %s"
1348 (pabbrev-get-usage-dictionary-size)
1349 (pabbrev-get-total-usages-dictionary))))))
1351 (defun pabbrev-idle-timer-function(&optional buffer)
1352 ;; so this only works on the current buffer. Might want to scavenge
1353 ;; over other buffers
1354 (save-excursion
1355 (set-buffer (or buffer pabbrev-timer-buffer (current-buffer)))
1356 (if (and pabbrev-mode (not pabbrev-disable-timers))
1357 (pabbrev-idle-timer-function-0)
1358 (pabbrev-debug-message "idle running in non pabbrev-mode"))))
1360 ;; for some reason that I do not understand yet, this sometimes
1361 ;; appears to work in the wrong buffer. I really have not got any idea
1362 ;; why this is the case.
1363 (defun pabbrev-idle-timer-function-0()
1364 "Add all words to the buffer.
1365 `pabbrev-scavenge-buffer' does this more efficiently interactively.
1366 If this takes up too much processor power, see `pabbrev-scavenge-some-chunk-size'."
1367 (let ((forward-marker (point))
1368 (backward-marker (point))
1369 (forward-complete nil)
1370 (backward-complete nil)
1371 (repeat t))
1372 (if pabbrev-idle-timer-verbose
1373 (message "pabbrev scavenging..."))
1374 (pabbrev-debug-message "running idle timer at %s" (point))
1375 (while
1376 (and repeat
1377 (not (and forward-complete backward-complete)))
1378 (save-excursion
1379 (unless backward-complete
1380 (goto-char backward-marker)
1381 (setq backward-marker
1382 (pabbrev-scavenge-words -1
1383 (* 2 pabbrev-scavenge-some-chunk-size)))
1384 (setq backward-complete
1385 (eq (point-min) backward-marker))
1386 (pabbrev-debug-message "searching backward to %s complete %s"
1387 backward-marker backward-complete))
1388 (unless forward-complete
1389 (goto-char forward-marker)
1390 (setq forward-marker
1391 (pabbrev-scavenge-words 1 pabbrev-scavenge-some-chunk-size))
1392 (setq forward-complete
1393 (eq (point-max) forward-marker))
1394 (pabbrev-debug-message "searching forward to %s complete %s"
1395 forward-marker forward-complete)))
1396 (pabbrev-debug-message "Dictionary size %s total usage %s"
1397 (pabbrev-get-usage-dictionary-size)
1398 (pabbrev-get-total-usages-dictionary))
1400 (if pabbrev-idle-timer-verbose
1401 (message "pabbrev scavenging (%s words %s buffer)..." (pabbrev-get-usage-dictionary-size)
1402 (buffer-name (current-buffer))))
1403 (setq repeat (sit-for 0.1)))
1404 (if pabbrev-idle-timer-verbose
1405 (progn
1406 (message "pabbrev scavenging...done")
1407 (sit-for 2)
1408 (message nil)))))
1410 (defun pabbrev-shut-up()
1411 "Switch off verbose messages..."
1412 (interactive)
1413 (message "Swiching off pabbrev messages" )
1414 (setq pabbrev-idle-timer-verbose nil))
1416 ;;; The following are debug functions.
1417 (defvar pabbrev-debug-buffer nil)
1419 ;;(setq pabbrev-debug-enabled t)
1420 (defvar pabbrev-debug-enabled nil)
1422 (defun pabbrev-debug-get-buffer()
1423 (get-buffer-create "*pabbrev-debug"))
1425 (defmacro pabbrev-debug-message(&rest body)
1426 `(if pabbrev-debug-enabled
1427 (let ((insert
1428 (concat (format ,@body) "\n")))
1429 (save-excursion
1430 (set-buffer
1431 (pabbrev-debug-get-buffer))
1432 (goto-char (point-max))
1433 (insert insert)
1434 (pabbrev-debug-frame-scroll)))))
1436 (defun pabbrev-debug()
1437 (interactive)
1438 (pabbrev-debug-frame)
1439 (setq pabbrev-debug-enabled t))
1441 (defvar pabbrev-debug-frame nil)
1442 (defun pabbrev-debug-frame()
1443 (interactive)
1444 (if (not pabbrev-debug-frame)
1445 (progn
1446 (setq pabbrev-debug-frame
1447 (make-frame '((width . 30)
1448 (height . 30))))
1449 (select-frame pabbrev-debug-frame)
1450 (switch-to-buffer (pabbrev-debug-get-buffer)))))
1452 (defun pabbrev-debug-frame-scroll()
1453 (save-excursion
1454 (if pabbrev-debug-frame
1455 (progn
1456 (select-frame pabbrev-debug-frame)
1457 (switch-to-buffer (pabbrev-debug-get-buffer))
1458 (goto-char (point-max))))))
1460 ;;(setq pabbrev-debug-display t)
1461 (defvar pabbrev-debug-display nil
1462 "If t visible mark the progress of function `pabbrev-mode' through the buffer.
1463 This looks very ugly. Note that this only shows newly added words. Use
1464 `pabbrev-debug-remove-properties' to clear this invisible markers. Use
1465 `pabbrev-debug-show-all-properties' to show existing markers.")
1467 (defun pabbrev-debug-display(start end)
1468 (if pabbrev-debug-display
1469 (overlay-put
1470 (make-overlay start end)
1471 'face 'pabbrev-debug-display-label-face)))
1473 (defface pabbrev-debug-display-label-face
1474 '((t
1475 (:underline "navy")))
1476 "Font Lock mode face used to highlight suggestions"
1477 :group 'pabbrev)
1480 (defun pabbrev-debug-erase-all-overlays()
1481 "Kill all visible overlays from the current buffer. "
1482 (interactive)
1483 (pabbrev-debug-remove-properties)
1484 (mapcar
1485 (lambda(overlay)
1487 (eq 'pabbrev-debug-display-label-face
1488 (overlay-get overlay 'face))
1489 (delete-overlay overlay)))
1490 (overlays-in
1491 (point-min) (point-max))))
1493 (defun pabbrev-debug-show-all-properties()
1494 "Show all existing markers.
1495 This can be rather slow."
1496 (interactive)
1497 (goto-char (point-min))
1498 (let ((on-mark-state nil)
1499 (on-mark))
1500 (while t
1501 (progn
1502 (setq on-mark (get-text-property (point) 'pabbrev-added))
1503 (message "On line %s"
1504 (count-lines (point-min) (point)))
1505 (cond
1506 ;; just moved onto marked area
1507 ((and on-mark (not on-mark-state))
1508 (setq on-mark-state (point)))
1509 ;; just moved off a marked area
1510 ((and on-mark-state (not on-mark))
1511 (progn
1512 (overlay-put
1513 (make-overlay on-mark-state (point))
1514 'face 'underline)
1515 (setq on-mark-state nil)))))
1516 (forward-char))))
1518 (defun pabbrev-debug-restart-idle-timer()
1519 "Kill and restart the idle timers."
1520 (interactive)
1521 (pabbrev-debug-kill-idle-timer)
1522 (pabbrev-ensure-idle-timer))
1524 (defun pabbrev-debug-kill-idle-timer()
1525 "Kill the idle timers.
1526 Toggling `pabbrev-mode' will tend to turn them on again, as
1527 will `pabbrev-debug-restart-idle-timer'."
1528 (interactive)
1529 (if pabbrev-short-idle-timer
1530 (progn
1531 (cancel-timer pabbrev-short-idle-timer)
1532 (setq pabbrev-short-idle-timer nil)))
1533 (if pabbrev-long-idle-timer
1534 (progn
1535 (cancel-timer pabbrev-long-idle-timer)
1536 (setq pabbrev-long-idle-timer nil))))
1538 (defun pabbrev-debug-clear()
1539 (pabbrev-debug-clear-all-hashes)
1540 (pabbrev-debug-remove-properties))
1542 (defun pabbrev-debug-remove-properties()
1543 "Remove all the `pabbrev-added' properties from the buffer.
1544 This means all the words in the buffer will be open for addition
1545 to the dictionary."
1546 (interactive)
1547 (remove-text-properties
1548 (point-min)
1549 (point-max)
1550 '(pabbrev-added)))
1552 (defun pabbrev-debug-clear-hashes(&optional mode)
1553 "Clear the dictionary for major mode MODE, or the current mode."
1554 (interactive)
1555 (if (not mode)
1556 (setq mode major-mode))
1557 (setq pabbrev-prefix-hash-modes
1558 (delq mode pabbrev-prefix-hash-modes))
1559 (setq pabbrev-usage-hash-modes
1560 (delq mode pabbrev-usage-hash-modes))
1561 ;; help the GC a bit..
1562 (if (pabbrev-get-usage-hash)
1563 (progn
1564 (clrhash (pabbrev-get-usage-hash))
1565 (put mode 'pabbrev-usage-hash nil)))
1566 (if (pabbrev-get-prefix-hash)
1567 (progn
1568 (clrhash (pabbrev-get-prefix-hash))
1569 (put mode 'pabbrev-get-prefix-hash nil))))
1571 (defun pabbrev-debug-clear-all-hashes()
1572 "Clear all hashes for all modes."
1573 (interactive)
1574 (mapcar 'pabbrev-debug-clear-hashes pabbrev-prefix-hash-modes))
1576 (defun pabbrev-debug-print-hashes()
1577 "Print the hashes for the current mode."
1578 (interactive)
1579 (let ((usage (pabbrev-get-usage-hash))
1580 (prefix (pabbrev-get-prefix-hash)))
1581 (switch-to-buffer
1582 (get-buffer-create "*pabbrev hash*"))
1583 (erase-buffer)
1584 (if (not usage)
1585 (insert "Usage hash nil"))
1586 (insert "Usage hash size "
1587 (number-to-string
1588 (hash-table-count usage)) "\n")
1589 (if (not prefix)
1590 (insert "Prefix hash nil")
1591 (insert "Prefix hash size "
1592 (number-to-string
1593 (hash-table-count prefix)) "\n"))
1594 (insert "Usage hash:\n")
1595 (pabbrev-debug-print-hash usage)
1596 (insert "Prefix hash:\n")
1597 (pabbrev-debug-print-hash prefix)))
1599 (defun pabbrev-debug-print-hash(hash)
1600 "Pretty print a hash."
1601 (if hash
1602 (progn
1603 (pp hash (current-buffer))
1604 (insert "\n")
1605 (maphash
1606 (lambda(key value)
1607 (insert (concat "KEY: " key "\n"))
1608 (pp value (current-buffer)))
1609 hash))))
1612 ;; nobble pabbrev -- useful for profiling.
1614 ;; nobble core data structures...
1615 ;;(defun pabbrev-add-word(word))
1617 ;; nobble text properties...
1618 ;; (defun pabbrev-mark-add-word (bounds))
1622 ;; Working.el hack. Use working.el if it's around, or don't if it's
1623 ;; not.
1624 (eval-and-compile
1625 (condition-case nil
1626 (require 'working)
1627 (error
1628 (progn
1629 (defmacro working-status-forms (message donestr &rest forms)
1630 "Contain a block of code during which a working status is shown."
1631 (list 'let (list (list 'msg message) (list 'dstr donestr)
1632 '(ref1 0))
1633 (cons 'progn forms)))
1635 (defun working-status (&optional percent &rest args)
1636 "Called within the macro `working-status-forms', show the status."
1637 (message "%s%s" (apply 'format msg args)
1638 (if (eq percent t) (concat "... " dstr)
1639 (format "... %3d%%"
1640 (or percent
1641 (floor (* 100.0 (/ (float (point))
1642 (point-max)))))))))
1644 (defun working-dynamic-status (&optional number &rest args)
1645 "Called within the macro `working-status-forms', show the status."
1646 (message "%s%s" (apply 'format msg args)
1647 (format "... %c" (aref [ ?- ?/ ?| ?\\ ] (% ref1 4))))
1648 (setq ref1 (1+ ref1)))
1650 (put 'working-status-forms 'lisp-indent-function 2)))))
1655 (provide 'pabbrev)
1656 ;;; pabbrev.el ends here