;; pabbrev.el --- Predictive abbreviation expansion
;; Version: 3.0
;; This file is not part of Emacs
;; Author: Phillip Lord
;; Maintainer: Phillip Lord
;; Maintainer (XEmacs): Martin Kuehl (martin.kuehl@gmail.com)
;; Website: http://www.russet.org.uk
;; COPYRIGHT NOTICE
;;
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;; Commentary:
;;
;; The code provides a abbreviation expansion for Emacs. Its fairly
;; similar to "dabbrev" expansion, which works based on the contents
;; of the current buffer (or other buffers).
;;
;; Predictive abbreviation expansion works based on the previously
;; written text. Unlike dynamic abbreviation, the text is analysed
;; during idle time, while Emacs is doing nothing else. `pabbrev-mode'
;; tells you when this is happening. If this irritates you unset
;; `pabbrev-idle-timer-verbose'. The advantage of this is that its
;; very quick to look up potential abbreviations, which means that the
;; can be constantly displayed, without interfering with the user as
;; they type. Certainly it works for me, on an old laptop, typing as
;; fast as I can (which is fast, since I learnt to type with four
;; fingers).
;;
;; pabbrev's main entry point is through the minor mode
;; `pabbrev-mode'. There is also a global minor mode, called
;; `global-pabbrev-mode', which does the same in all appropriate
;; buffers.
;;
;; The current user interface looks like so...
;;
;; p[oint]
;; pr[ogn]
;; pre[-command-hook]
;; pred[ictive]
;;
;; As the user types the system narrows down the possibilities. The
;; narrowing is based on how many times the words have been used
;; previously. By hitting [tab] at any point the user can complete the
;; word. The [tab] key is normally bound to `indent-line'.
;; `pabbrev-mode' preserves access to this command (or whatever else
;; [tab] was bound to), if there is no current expansion.
;;
;; Sometimes you do not want to select the most commonly occurring
;; word, but a less frequently occurring word. You can access this
;; functionality by hitting [tab] for a second time. This takes you
;; into a special suggestions buffer, from where you can select
;; secondary selections. See `pabbrev-select-mode' for more
;; details. There is also an option `pabbrev-minimal-expansion-p'
;; which results in the shortest substring option being offered as the
;; first replacement.
;;
;; But is this actually of any use? Well having use the system for a
;; while now, I can say that it is sometimes. I originally thought
;; that it would be good for text, but in general its not so
;; useful. By the time you have realised that you have an expansion
;; that you can use, hit tab, and checked that its done the right
;; thing, you could have just typed the word directly in. It's much
;; nicer in code containing buffers, where there tend to be lots of
;; long words, which is obviously where an abbreviation expansion
;; mechanism is most useful.
;;
;; Currently pabbrev builds up a dictionary on a per major-mode basis.
;; While pabbrev builds up this dictionary automatically, you can also
;; explicitly add a buffer, or a region to the dictionary with
;; `pabbrev-scavenge-buffer', or `pabbrev-scavenge-region'. There is
;; also a command `pabbrev-scavenge-some' which adds some words from
;; around point. pabbrev remembers the word that it has seen already,
;; so run these commands as many times as you wish.
;;
;; Although the main data structures are efficient during typing, the
;; pay off cost is that they can take a reasonable amount of time, and
;; processor power to gather up the words from the buffer. There are
;; two main settings of interest to reduce this, which are
;; `pabbrev-scavenge-some-chunk-size' and
;; `pabbrev-scavenge-on-large-move'. `pabbrev-mode' gathers text from
;; around point when point has moved a long way. This means symbols
;; within the current context should be in the dictionary, but it can
;; make Emacs choppy, in handling. Either reduce
;; `pabbrev-scavenge-some-chunk-size' to a smaller value, or
;; `pabbrev-scavenge-on-large-move' to nil to reduce the effects of
;; this.
;;
;; NOTE: There are a set of standard conventions for Emacs minor
;; modes, particularly with respect to standard key bindings, which
;; pabbrev somewhat abuses. The justification for this is that the
;; whole point of pabbrev mode is to speed up typing. Access to its
;; main function has to be on a very easy to use keybinding. The tab
;; seems to be a good choice for this. By preserving access to the
;; original tab binding when there is no expansion, pabbrev mostly
;; "does what I mean", at least in my hands.
;;; Installation:
;;
;; To install this file place in your `load-path', and add
;;
;; (require 'pabbrev)
;;
;; to your .emacs
;;; Status:
;;
;; At the moment this seems to be working mostly, although
;; occasionally it seems to leave an expansion in the buffer.
;; I wrote this on an Emacs 21.0 prerelease, that I haven't upgraded
;; yet, running under RedHat 7.x. I've also tried this out on NT
;; Emacs (21.1), where it seems to byte compile, and work. But it has not
;; been tried out extensively. It will NOT work on Emacs' older than
;; 21.
;;
;; This package now has an XEmacs maintainer (Martin Kuehl). He
;; appears to have isolated the last few problems with pabbrev on
;; XEmacs, and it is running stably there now. It has been tested on
;; XEmacs 21.4, running on Debian and Ubuntu Linux.
;;; Package Support:
;;
;; Some packages need extra support for pabbrev to work with. There are two
;; plists properties which package developers can use.
;;
;; (put 'command-name 'pabbrev-expand-after-command t)
;;
;; means that the following the named command (in this case command-name),
;; expansion will be offered. `self-insert-command' and a few others is
;; normally fine, but not always.
;;
;; (put mode-name 'pabbrev-global-mode-excluded-modes t)
;;
;; will mean that any buffer with this major mode will not have
;; global-pabbrev-mode activated.
;;
;;; Bugs;
;;
;; This package had an occasional bug which has historically been hard
;; to track down and reproduce. Basically I end up with a load of
;; offering expansions in the buffer. It looks something like this....
;; pabbrev[-mode][v][ev][rev][brev][bbrev][abbrev] which is amusing
;; the first time, but more or less totally useless.
;;
;; Thanks to the efforts of Martin Kuehl, I think we have tracked the
;; cause of the problem now (the old version depended on
;; pre-command-hook and post-command-hook being called
;; consecutively. But sometimes they get called twice). Please let us
;; know if you see this problem.
;;; Limitations:
;;
;; pabbrev mode has a number of common limitations.
;;
;; 1) I think it would be nice to save the dictionaries, or offer
;; facilities for doing so, before Emacs is killed. This would clearly
;; depend on point 3 also. I'm not sure whether this is possible in a
;; reasonable length of time. `pabbrev-debug-print-hashes' is
;; certainly pretty slow.
;;
;; 2) I think that the scavenge functions are more computationally
;; intensive than they need to be. They generally run in the idle
;; cycle so its not a disaster. However more efficiency would mean the
;; buffer could be gathered more quickly. This has the disadvantage
;; that I would have to start to think about...
;;
;; 3) There are current no facilities at all, for removing words from
;; the dictionaries. The original data structures, and in particular
;; the usage hash, were partly designed to support this. One way I
;; would do this is, for example, by just decreasing the number of
;; usages by a given amount, and then deleting (probably after the
;; sort during the scavenge), any cons cells with less than one
;; usage. I'm not sure this is a problem though. The number of words
;; in the dictionaries only increases slowly, then don't seem to grow
;; that quickly, and they don't take up that much memory.
;;; Bug Reporting
;;
;; Bug reports are more than welcome. However one particular problem
;; with this mode is that it makes heavy use of
;; `post-command-hook'. This is a very useful hook, but makes the
;; package difficult to debug. If you want to send in a bug report it
;; will help a lot if you can get a repeatable set of keypresses, that
;; always causes the problem.
;;; Implementation notes:
;;
;; The core data structures are two hashes. The first of which looks
;; like this...
;; "the" -> ("the" . 5)
;; "there" -> ("there" . 3)
;;
;; I call this the usage hash, as it stores the total number of times
;; each word has been seen.
;;
;; The second hash which is called the prefix hash. It stores
;; prefixes, and usages...
;;
;; "t"->
;; (("the" . 64)
;; ("to" . 28)
;; ("t" . 22)
;; ("this" . 17))
;;
;; "th"->
;; (("the" . 64)
;; ("this" . 17)
;; ("that" . 7))
;;
;; "the"->
;; (("the" . 64)
;; ("there" . 6)
;; ("then" . 3)
;; ("these" . 1))
;;
;; The alist cons cells in the first hash are conserved in the second,
;; but the alists are not. The alist in the second hash is always
;; sorted, on the basis of word usage.
;;
;; The point with this data structure is that I can find word usage
;; in constant time, from the first hash, and completions for a given
;; prefix, also in constant time. As access to completions happens as
;; the user types speed is more important here, than during
;; update, which is why the prefix hash maintains sorted alists. This
;; is probably at the cost of slower updating of words.
;;; Acknowledgements;
;;
;; Many thanks to Martin Kuehl for tracking down the last bug which
;; stood between this being an "official" full release.
;;
;; Once again I need to thank Stefan Monnier, for his comments on my
;; code base. Once day I will write a minor mode which Stefan Monnier
;; does not offer me advice on, but it would appear that this day has not
;; yet arrived!
;;
;; I should also thank Kim F. Storm (and in turn Stephen Eglen), as
;; the user interface for this mode has been heavily influenced by
;; ido.el, a wonderful package which I use every day.
;;
;; Carsten Dominik suggested I add the package suppport rather than the
;; existing defcustom which was not as good I think.
;;
;; Scott Vokes added a nice patch, adding the single/multiple expansion, the
;; universal argument support and some bug fixes.
;;; Code:
(eval-when-compile (require 'cl))
(require 'thingatpt)
(eval-and-compile
(if (featurep 'xemacs)
(progn
(require 'overlay)
(unless (fboundp 'line-beginning-position)
(defalias 'line-beginning-position 'point-at-bol))
(unless (fboundp 'line-end-position)
(defalias 'line-end-position 'point-at-eol))
(unless (fboundp 'cancel-timer)
(defalias 'cancel-timer 'delete-itimer))
)))
(defconst pabbrev-xemacs-p (string-match "XEmacs" (emacs-version))
"Non-nil if we are running in the XEmacs environment.")
(defgroup pabbrev nil
"Predicative abbreviation expansion."
:tag "Predictive Abbreviations."
:group 'abbrev
:group 'convenience)
(defcustom pabbrev-global-mode-not-buffer-names '("*Messages*")
"*Will not activate function `global-pabbrev-mode' if buffers have this name."
:type '(repeat (string :tag "Buffer name"))
:group 'pabbrev)
(defcustom pabbrev-global-mode-buffer-size-limit nil
"*Will not activate function `global-pabbrev-mode' if buffers are over this size (in bytes) (when non-nil)."
:type 'integer
:group 'pabbrev)
(defcustom pabbrev-marker-distance-before-scavenge 2000
"Minimal distance moved before we wish to scavenge."
:type 'integer
:group 'pabbrev)
;;(setq pabbrev-scavenge-on-large-move nil)
(defcustom pabbrev-scavenge-on-large-move t
"*If non NIL, scavenge when a large buffer move has occured.
This can make Emacs' handling a little bumpy. See also
`pabbrev-scavenge-some-chunk-size', as reducing this, or increasing
`pabbrev-marker-distance-before-scavenge' is an alternative
to setting this to nil"
:type 'boolean
:group 'pabbrev)
(defcustom pabbrev-thing-at-point-constituent 'symbol
"Symbol defining THING which function `pabbrev-mode' works on.
This symbol should be understandable by
`bounds-of-thing-at-point'. This symbol defines what function `pabbrev-mode'
considers to be the basic unit of expansion. If if it set to `symbol',
for example, \"pabbrev-mode\" would be offered as an expansion, while
if it is set to `word' \"pabbrev\" and \"mode\" would be offered.
You could also set it to `whitespace' which would be really daft,
or `page' which would be silly in a different way."
:group 'pabbrev
:type 'symbol
:options '(symbol word))
(defcustom pabbrev-scavenge-some-chunk-size 40
"Number of words that `pabbrev-scavenge-words' gathers.
This also affects the speed with which pabbrev will scan through
the buffer during idle, so decrease this if too much processor
is being used, increase it if you want more. It's set quite
conservatively. If you get choppy performance when moving
around the buffer you should also consider
`pabbrev-scavenge-on-large-move' to nil."
:type 'integer
:group 'pabbrev)
(defcustom pabbrev-idle-timer-verbose t
"If non NIL, print messages while scavenging on idle timer.
At the moment this is set to t by default. The idle timer function,
`pabbrev-idle-timer-function' uses quite a bit of processor power, and
I want the users to known what is eating their CPU. I may change
this at a later date."
:type 'boolean
:group 'pabbrev)
(defcustom pabbrev-read-only-error t
"If non NIL, signal an error when in a read only buffer.
`pabbrev-mode' works by alterating the local buffer, so it's pointless
within a read only buffer. So, normally, it signals an error when an
attempt is made to use it in this way. But this is a pain if you toggle
buffers read only a lot. Set this to NIL, and pabbrev-mode will disable
it's functionality in read only buffers silently."
:type 'boolean
:group 'pabbrev)
;; variable in progress
(defcustom pabbrev-minimal-expansion-p nil
"If t offer minimal expansion.
pabbrev can select the optimal expansion in two different ways. The
normal way is to offer the expansion which occurs most frequently in
the words which pabbrev has scavenged (in any buffer in the same
mode). The other method is to take the minimal occuring substring
present in any potential expansion; this is a lot more like standard
completion seen on a command line.
I'm not telling you which version, I prefer."
:type 'boolean
:group 'pabbrev
)
;;(setq pabbrev-minimal-expansion-p t)
;; stolen from font-lock!
(if pabbrev-xemacs-p
(progn
(defface pabbrev-suggestions-face
'((((class color) (background dark)) (:foreground "tan"))
(((class color) (background light)) (:foreground "green4"))
(((class grayscale) (background light)) (:foreground "DimGray" :italic t))
(((class grayscale) (background dark)) (:foreground "LightGray" :italic t))
(t (:bold t)))
"Face for displaying suggestions."
:group 'pabbrev)
(defface pabbrev-single-suggestion-face
'((((class color) (background dark)) (:foreground "tan"))
(((class color) (background light)) (:foreground "green4"))
(((class grayscale) (background light)) (:foreground "DimGray" :italic t))
(((class grayscale) (background dark)) (:foreground "LightGray" :italic t))
(t (:bold t)))
"Face for displaying one suggestion."
:group 'pabbrev)
(defface pabbrev-suggestions-label-face
nil "Font lock mode face used to highlight suggestions"
:group 'pabbrev))
(progn ; GNU Emacs
(defface pabbrev-suggestions-face
'((((type tty) (class color)) (:foreground "green"))
(((class grayscale) (background light)) (:foreground "Gray90" :bold t))
(((class grayscale) (background dark)) (:foreground "DimGray" :bold t))
(((class color) (background light)) (:foreground "ForestGreen"))
(((class color) (background dark)) (:foreground "Red"))
(t (:bold t :underline t)))
"Face for displaying suggestions."
:group 'pabbrev)
(defface pabbrev-single-suggestion-face
'((((type tty) (class color)) (:foreground "green"))
(((class grayscale) (background light)) (:foreground "Gray70" :bold t))
(((class grayscale) (background dark)) (:foreground "DarkSlateGray" :bold t))
(((class color) (background light)) (:foreground "OliveDrab"))
(((class color) (background dark)) (:foreground "PaleGreen"))
(t (:bold t :underline t)))
"Face for displaying one suggestion."
:group 'pabbrev)
(defface pabbrev-suggestions-label-face
'((t
:inverse-video t))
"Font Lock mode face used to highlight suggestions"
:group 'pabbrev)))
;;;; End user Customizable variables.
;;;; Begin Package Support.
;; mark commands after which expansion should be offered
(mapc
(lambda(x)
(put x 'pabbrev-expand-after-command t))
'(self-insert-command mouse-set-point delete-char
backward-delete-char-untabify pabbrev-expand-maybe
pabbrev-expand-maybe-minimal pabbrev-expand-maybe-full
universal-argument universal-argument-other-key))
;; mark modes in which to not activate pabbrev with global mode.
(mapc
(lambda(x)
(put x 'pabbrev-global-mode-excluded-modes t))
'(shell-mode
Custom-mode
custom-mode
telnet-mode
term-mode
dired-mode
eshell-mode
;; gnus article mode is read-only so should be missed anyway,
;; but it does something wierd so that it's not
gnus-article-mode
))
;;;; End Package Support
;;; Start data structures
(defvar pabbrev-usage-hash-modes nil
"List of modes with associated usage dictionaries.")
(defvar pabbrev-prefix-hash-modes nil
"List of modes with associated prefix dictionaries.")
(defmacro pabbrev-save-buffer-modified-p (&rest body)
"Eval BODY without affected buffer modification status"
`(let ((buffer-modified (buffer-modified-p))
(buffer-undo-list t))
,@body
(set-buffer-modified-p buffer-modified)))
(defun pabbrev-get-usage-hash()
"Returns the usage hash for this buffer."
(let((hash (get major-mode 'pabbrev-usage-hash)))
(unless hash
(put major-mode 'pabbrev-usage-hash
(setq hash
(make-hash-table :test 'equal)))
(push major-mode pabbrev-usage-hash-modes))
hash))
(defun pabbrev-get-usage-dictionary-size()
"Returns the size of the usage hash."
(hash-table-count (pabbrev-get-usage-hash)))
(defun pabbrev-get-total-usages-dictionary()
"Returns the total number of usages from the usage hash"
(interactive)
(let ((size 0))
(maphash
(lambda(key value)
(setq size (+ size (cdr value))))
(pabbrev-get-usage-hash))
size))
(defun pabbrev-get-prefix-hash()
"Returns the prefix hash for the current buffer."
(let((hash (get major-mode 'pabbrev-prefix-hash)))
(unless hash
(put major-mode 'pabbrev-prefix-hash
(setq hash
(make-hash-table :test 'equal)))
(push major-mode pabbrev-prefix-hash-modes))
hash))
(defun pabbrev-add-word-usage (word)
"Add a WORD to the usage hash.
This is a function internal to the data structures. The
`pabbrev-add-word' is the main entry point to this functionality."
(let ((value
(gethash
;; look for word usage cons we need a cons, but the last
;; value is irrelevant.
word
(pabbrev-get-usage-hash))))
;; so now we have cons, or nil
(if value
;; increment occurences
(setcdr
value (+ 1 (cdr value)))
;; we have no so make is
(setq value
(cons word 1)))
;; so now we the cons cell for sure
;; possible we should do this above, as I think it only needs
;; doing for a new cons.
(puthash word value (pabbrev-get-usage-hash))
value))
(defun pabbrev-add-word-cons-with-prefix (prefix conscell)
"Add a word usage, and a PREFIX.
This function is internal to the data structures, and should normally
only be called, by `pabbrev-add-word'. CONSCELL should be cons
returned from `pabbrev-add-word-usage', while PREFIX should be a
prefix of the from the cons cell."
(let
;; this should be an alist or nil
((value (gethash prefix
(pabbrev-get-prefix-hash))))
(if value
;; so we have an alist. Has our word been added to this alist
;; before? If not, do so. If it has been added, then it will
;; have been updated with the addition of the word
(if (not
(member conscell value))
(setq value (cons conscell value)))
;; nothing in there, so create an alist with
;; a single element
(setq value (list conscell)))
;; so we now have the value alist...sort it and store it back in
;; the hash
(puthash prefix
(pabbrev-sort-alist value conscell)
(pabbrev-get-prefix-hash))))
(defun pabbrev-sort-alist(alist cons)
;; this sort is bit poor. It should be possible to do this in less
;; than linear time, rather than n(log-n) as now. I think most of
;; the time is spent entering the lambda function. The irony is that
;; the sort is more or less sorted from the start, so a bubble sort
;; would work in linear time. I've tried replacing with a linear
;; sort, that is just placing the cons in the correct place, but in
;; my hands, it's three or four times slower, on this buffer which
;; has a lot of common prefixes, and so should take a while,
;; probably because too much has to be done in lisp rather than with
;; builtin's.
;;
;; Possibly the sort could be done on removing the value from the
;; hash, which would reduce the amount of sorting that needs to be
;; done. But it would then be in the command cycle rather than the
;; idle loop, which seems like a really bad idea to me.
;;
;; When I wrote the data structures this was a bit of a worry as
;; emacs spent most of its time in this loop, but now I've bolted
;; on a user interface, its not so much of a problem, as plenty of
;; time is spent in placing on the "been here" overlays....
(sort alist
;;'pabbrev-comparitor-function))
(lambda(a b)
(> (cdr a) (cdr b)))))
(defun pabbrev-comparitor-function(a b)
(> (cdr a) (cdr b)))
(defun pabbrev-add-word (word)
"Add the usage of a WORD to the current dictionary."
(let ((conscell
(pabbrev-add-word-usage word)))
(dotimes (i (- (length word) 1))
(pabbrev-add-word-cons-with-prefix
(substring word 0 (1+ i))
conscell))))
(defun pabbrev-fetch-all-suggestions-for-prefix(prefix)
"Returns the suggestions for a given PREFIX.
Results are an alist, with cons with car of the word, and cdr of the
number of usages seen so far. This alist should NOT be altered, its
it's ordering is part of the core data structures"
(gethash prefix (pabbrev-get-prefix-hash)))
;; Which completes the core data structures.
;; This code provides the minor mode which displays, and accepts
;; abbreviations.
(defvar pabbrev-mode-map (make-keymap)
"Keymap for pabbrev-minor-mode.")
;; I don't understand this. I thought that this were equivalent. But
;; modes which define [tab] get used in preference to \t. So I define
;; both. Don't change these without also changing the definition of
;; pabbrev-expand-maybe.
(define-key pabbrev-mode-map "\t" 'pabbrev-expand-maybe)
(define-key pabbrev-mode-map [tab] 'pabbrev-expand-maybe)
;; xemacs has synced to newest easy-mmode now
;;(if (not pabbrev-xemacs-p)
(define-minor-mode pabbrev-mode
"Toggle pabbrev mode.
With arg, turn on Predicative Abbreviation mode if and only if arg is
positive.
This mode is another abbreviation expansion mode somewhat like
`dabbrev-expand', in that it looks through the current buffer for
symbols that can complete the current symbol. Unlike `dabbrev-expand',
it does this by discovering the words during the Emacs idle time, and
places the results into data structures which enable very rapid
extraction of expansions. The upshot of this is that it can offer
suggestions as you type, without causing an unacceptable slow down.
There is an associated `global-pabbrev-mode' which turns on the mode
on in all buffers.
"
nil
" Pabbrev"
pabbrev-mode-map
(when (and pabbrev-mode-map
buffer-read-only)
(if pabbrev-read-only-error
(error "Can not use pabbrev-mode in read only buffer"))))
;; (easy-mmode-define-minor-mode pabbrev-mode
;; "Toggle pabbrev mode.
;; This mode is an abbreviation expansion mode. It looks through the
;; current buffer, and offers expansions based on the words already
;; there.
;; I have only just recently ported this to XEmacs, and I don't
;; personally use XEmacs, so it has received little or no testing."
;; nil
;; " Pabbrev"
;; pabbrev-mode-map))
(if (fboundp 'easy-mmode-define-global-mode)
(easy-mmode-define-global-mode global-pabbrev-mode
pabbrev-mode pabbrev-global-mode))
(defun pabbrev-global-mode()
"Switch on `pabbrev-mode' in current buffer if appropriate.
Currently appropriate means, if the buffer is not read only, and is
not a minibuffer."
(unless (or buffer-read-only
pabbrev-mode
(get major-mode 'pabbrev-global-mode-excluded-modes)
;; don't turn on in non listable buffers
(equal (substring (buffer-name) 0 1) " ")
(when pabbrev-global-mode-buffer-size-limit
(> (buffer-size) pabbrev-global-mode-buffer-size-limit))
(member (buffer-name) pabbrev-global-mode-not-buffer-names)
(window-minibuffer-p (selected-window)))
(let
;; set the chunk size low, or the global mode takes for ever
;; to switch on
((pabbrev-scavenge-some-chunk-size 0))
(pabbrev-mode))))
;; hooks for switching on and off.
(add-hook 'pabbrev-mode-on-hook
'pabbrev-mode-on)
(add-hook 'pabbrev-mode-off-hook
'pabbrev-mode-off)
(defvar pabbrev-marker nil
"Location of current insertion, or nil.
This variable is not actually a marker, but a cons of
start and end positions")
(make-variable-buffer-local 'pabbrev-marker)
(defvar pabbrev-expansion nil
"Currently displayed expansion, or nil.")
(make-variable-buffer-local 'pabbrev-expansion)
(defvar pabbrev-expansion-suggestions nil
"Current expansion suggestions, or nil.")
(make-variable-buffer-local 'pabbrev-expansion-suggestions)
(defvar pabbrev-marker-last-expansion nil
"Marks where the last possible expansion was.")
(make-variable-buffer-local 'pabbrev-marker-last-expansion)
(defun pabbrev-mode-on()
"Turn `pabbrev-mode' on."
(add-hook 'pre-command-hook 'pabbrev-pre-command-hook nil t)
(add-hook 'post-command-hook 'pabbrev-post-command-hook nil t))
(defun pabbrev-mode-off()
"Turn `pabbrev-mode' off."
;; we have to remove the binding for tab. Other wise next time we
;; switch the mode on, this binding will be found, and set for
;; pabbrev-tab-previously-defined
(remove-hook 'pre-command-hook 'pabbrev-pre-command-hook t)
(remove-hook 'post-command-hook 'pabbrev-post-command-hook t))
;;(defun test()(interactive)(let ((last-command 'self-insert-command))(pabbrev-post-command-hook)))
;;(defun test()
;; (interactive)
;; (pabbrev-insert-suggestion
;; (pabbrev-thing-at-point)
;; (cdr (pabbrev-bounds-of-thing-at-point))
;; (pabbrev-fetch-all-suggestions-for-prefix
;; (pabbrev-thing-at-point))))
(defun pabbrev-post-command-hook()
"Offer expansion if appropriate.
This function is normally run off the `post-command-hook'."
(condition-case err
;; pabbrev will not switch on in a read only buffer. But the
;; buffer may have become read only between the time that it was
;; switched on, and now. So we need to check this anyway.
(unless (or buffer-read-only
;; This seems to be an issue in xemacs, so check for
;; this as well.
(window-minibuffer-p (selected-window)))
(save-excursion
;; ensure that any suggestion is deleted.
(when pabbrev-marker
(pabbrev-delete-last-suggestion))
(let ((word (pabbrev-thing-at-point))
(bounds (pabbrev-bounds-of-thing-at-point))
(suggestions))
(if (and
;; last command was a symbol
;; PWL last-command to this-command
(symbolp this-command)
;; we have just had an appropriate command
;; PWL commented out and testing change suggestd by Ye Wenbin
;;(get last-command 'pabbrev-expand-after-command)
(get this-command 'pabbrev-expand-after-command)
;; is word at point
word
;; we are at the end of it.
(= (point) (cdr bounds))
;; and we have some suggestions.
(setq suggestions (pabbrev-fetch-all-suggestions-for-prefix word)))
(progn
(pabbrev-insert-suggestion word (cdr bounds) suggestions)
(pabbrev-post-command-check-movement))))))
(error
(pabbrev-command-hook-fail err "post" ))))
(defun pabbrev-delete-last-suggestion()
"Remove previously inserted suggestions."
(pabbrev-save-buffer-modified-p
;; I don't think we need to check for buffer-read-only
;; here, because pabbrev-marker will always be nil in a
;; read only buffer. I could be wrong about this of
;; course.
(pabbrev-delete-overlay)
(delete-region (car pabbrev-marker) (cdr pabbrev-marker))
(setq pabbrev-marker nil)))
(defun pabbrev-pre-command-hook()
"Remove offering expansion from the buffer, if present.
This function is normally run off the `pre-command-hook'"
(condition-case err
(progn
(unless (memq this-command
pabbrev-expand-commands)
(setq pabbrev-expansion nil
pabbrev-expansion-suggestions nil))
(when pabbrev-marker
(pabbrev-delete-last-suggestion)))
;;catch the error
(error
(pabbrev-command-hook-fail err "pre"))))
(defun pabbrev-command-hook-fail(err hook)
"Advise user of a failure command-hooks.
This function should only run as the result of a bug.
A message is sent, as we can do little else safely,
on the `post-command-hook', or `pre-command-hook'."
(message "pabbrev mode has failed on %s hook: %s "
hook (error-message-string err))
(remove-hook 'pre-command-hook 'pabbrev-pre-command-hook t)
(remove-hook 'post-command-hook 'pabbrev-post-command-hook t)
(with-output-to-temp-buffer "*pabbrev-fail*"
(princ "There has been an error in pabbrev-mode. This mode normally
makes use of \"post-command-hook\", which runs after every command. If this
error continued Emacs could be made unusable, so pabbrev-mode has attempted
to disable itself. So although it will appear to still be on, it won't do
anything. Toggling it off, and then on again will usually restore functionality.\n")
(princ "The following is debugging information\n\n")
(princ (error-message-string err))
(princ "\n\nBacktrace is: \n" )
(let ((standard-output (get-buffer "*pabbrev-fail*" )))
(backtrace)))
(select-window (get-buffer-window "*pabbrev-fail*"))
(error "Error in pabbrev-mode"))
(defun pabbrev-marker-last-expansion()
"Fetch marker for last offered expansion."
(unless
pabbrev-marker-last-expansion
(setq pabbrev-marker-last-expansion
(set-marker (make-marker)
(point) (current-buffer))))
pabbrev-marker-last-expansion)
(defun pabbrev-update-marker()
(set-marker (pabbrev-marker-last-expansion)
(point) (current-buffer)))
(defun pabbrev-post-command-check-movement()
(let ((distance
(abs (- (point) (marker-position
(pabbrev-marker-last-expansion))))))
(if (> distance pabbrev-marker-distance-before-scavenge)
;; we have moved a lot in the buffer
(progn
(pabbrev-debug-message "Scavenge due to buffer marker")
(pabbrev-scavenge-some)
(pabbrev-update-marker)))))
(defvar pabbrev-overlay nil
"Overlay for offered completion.")
(make-variable-buffer-local 'pabbrev-overlay)
(defun pabbrev-set-overlay(start end count)
"Move overlay to START END location."
(unless pabbrev-overlay
(setq pabbrev-overlay
;; set an overlay at 1 1. Originally this used to be a 0 0 but
;; it crashes xemacs...well I never....
(make-overlay 1 1)))
;; for when we are not in font-lock-mode
(overlay-put pabbrev-overlay 'face
(if (> count 1) 'pabbrev-suggestions-face
'pabbrev-single-suggestion-face))
;; for when we are. If we just set face, font-lock tends to reset the face
;; immediately. This isn't working for me. font-lock still just blithely
;; resets the properties we have so carefully just placed
(overlay-put pabbrev-overlay 'font-lock-face
(if (> count 1) 'pabbrev-suggestions-face
'pabbrev-single-suggestion-face))
(move-overlay pabbrev-overlay start end (current-buffer)))
(defun pabbrev-set-overlay(start end count)
"Move overlay to START END location."
(unless pabbrev-overlay
(setq pabbrev-overlay
;; set an overlay at 1 1. Originally this used to be a 0 0 but
;; it crashes xemacs...well I never....
(make-overlay 1 1)))
(overlay-put pabbrev-overlay 'face
(if (> count 1) 'pabbrev-suggestions-face
'pabbrev-single-suggestion-face))
(move-overlay pabbrev-overlay start end (current-buffer)))
(defun pabbrev-delete-overlay()
"Make overlay invisible."
(if pabbrev-overlay
(delete-overlay pabbrev-overlay)))
(defun pabbrev-insert-suggestion(prefix end suggestions)
"Insert a suggestion into the buffer.
The suggestion should start with PREFIX, and be entered
at buffer position END."
(interactive)
(let* ((suggestion
(if (not pabbrev-minimal-expansion-p)
(car (car suggestions))
(try-completion "" suggestions))))
(let ((expansion
(if suggestion
(substring suggestion
(length prefix))
"")))
(save-excursion
(if (< 0 (length expansion))
;; add the abbreviation to the buffer
(pabbrev-save-buffer-modified-p
(insert
"[" expansion "]" )
;; store everything. Most importantly the pabbrev-marker!
(setq
pabbrev-expansion expansion
pabbrev-expansion-suggestions suggestions
pabbrev-marker
(cons end (point)))
(let ((point-1 (- (point) 1)))
(pabbrev-set-overlay
(- point-1 (length expansion)) point-1
(length suggestions)))))))))
(defvar pabbrev-last-expansion-suggestions nil
"Cached alternative suggestions from the last expansion.")
;; patch from Trey Jackson to fix problem with python (which uses tab to cycle
;; through indentation levels
(defun pabbrev-call-previous-tab-binding ()
"Call the function normally associated with [tab]."
(let ((prev-binding (pabbrev-get-previous-binding)))
(if (and (fboundp prev-binding)
(not (eq prev-binding 'pabbrev-expand-maybe)))
(let ((last-command (if (eq last-command this-command)
prev-binding
last-command))
(this-command prev-binding))
(funcall prev-binding)))))
;; (defun pabbrev-call-previous-tab-binding ()
;; "Call the function normally associated with [tab]."
;; (let ((prev-binding (pabbrev-get-previous-binding)))
;; (if (and (fboundp prev-binding)
;; (not (eq prev-binding 'pabbrev-expand-maybe)))
;; (funcall prev-binding))))
(defun pabbrev-expand-maybe(uarg)
"Call appropriate expansion command based on whether
minimal or full expansion is desired. If there is no expansion the command returned by
`pabbrev-get-previous-binding' will be run instead."
(interactive "p")
(if pabbrev-minimal-expansion-p
(pabbrev-expand-maybe-minimal uarg)
(pabbrev-expand-maybe-full uarg)))
(defun pabbrev-expand-maybe-minimal (uarg)
"Expand the minimal common prefix at point.
With prefix argument, bring up the menu of all full expansions."
(if (= uarg 4)
(if (> (length pabbrev-expansion-suggestions) 1)
(pabbrev-suggestions-goto-buffer pabbrev-expansion-suggestions)
(pabbrev-call-previous-tab-binding))
(if pabbrev-expansion
(pabbrev-expand)
(pabbrev-call-previous-tab-binding))))
(defun pabbrev-expand-maybe-full (uarg)
"Expand fully to the most common abbreviation at point.
With prefix argument, bring up a menu of all full expansions."
(cond
((= uarg 4)
(if (> (length pabbrev-expansion-suggestions) 1)
(pabbrev-suggestions-goto-buffer pabbrev-expansion-suggestions)
(pabbrev-call-previous-tab-binding)))
((eq last-command 'pabbrev-expand-maybe)
(if (and (> (length pabbrev-expansion-suggestions) 1)
(> (length pabbrev-last-expansion-suggestions) 1))
(pabbrev-suggestions-goto-buffer pabbrev-last-expansion-suggestions)
(pabbrev-call-previous-tab-binding)))
(pabbrev-expansion
(progn
(setq pabbrev-last-expansion-suggestions pabbrev-expansion-suggestions)
(pabbrev-expand)))
(t (pabbrev-call-previous-tab-binding))))
;; (setq pabbrev-minimal-expansion-p nil)
(defun pabbrev-show-previous-binding ()
(interactive)
(message "Previous binding is: %s"
(pabbrev-get-previous-binding)))
(defun pabbrev-get-previous-binding ()
"Show the binding of tab if pabbrev were not active.
The command `pabbrev-show-previous-binding' prints this out."
(let ((pabbrev-mode nil))
;; This is the original and satisfying solution
;;(key-binding (char-to-string last-command-event)))))
;; This is the new and unsatisfying one. The
;; keybindings are hard coded here, because I defined
;; [tab] and \t earlier. Both are tab, but the former
;; gets used in preference to the later.
(or (key-binding [tab])
(key-binding "\t"))))
;; ;; I think that I have this worked out now.
;; (if (eq prev-binding 'pabbrev-expand-maybe)
;; (message "pabbrev known bug! Avoiding recursive tab")
;; (funcall prev-binding))))))
;; (define-key pabbrev-mode-map "\t" nil)
;; (let ((tunneled-keybinding (key-binding "\t")))
;; (if (and (fboundp tunneled-keybinding)
;; (not (eq tunneled-keybinding 'pabbrev-expand-maybe)))
;; (funcall tunneled-keybinding)))
;; (define-key pabbrev-mode-map "\t" 'pabbrev-expand-maybe)))
(defvar pabbrev-expand-previous-word nil)
(defun pabbrev-expand()
"Expand abbreviation"
(setq pabbrev-expand-previous-word (pabbrev-thing-at-point))
(if pabbrev-expansion
(insert pabbrev-expansion)
(message "No expansion"))
(setq pabbrev-expansion nil))
(defvar pabbrev-expand-commands
'(pabbrev-expand-maybe pabbrev-expand
pabbrev-expand-maybe-minimal pabbrev-expand-maybe-full)
"List of commands which will be used expand.
We need to know this, or the possible expansions are deleted
before the command gets run.")
;; suggestions buffer
;; (defvar pabbrev-suggestions-buffer-enable nil)
;; (defun pabbrev-suggestions-toggle()
;; "NOT FULLY FUNCTIONAL. Enable interactive suggestions window.
;; This is just a test function at the moment. The idea is that you will
;; be able to see alternate suggestions as you type. This will be most
;; useful in a programming buffer. At the moment there is no way of
;; actually selecting these abbreviations. But it appears that the core
;; data structures are quick enough to work."
;; (interactive)
;; (if pabbrev-suggestions-buffer-enable
;; (progn
;; (setq pabbrev-suggestions-buffer-enable nil)
;; (remove-hook 'post-command-hook
;; 'pabbrev-suggestions-delete-window)
;; (delete-window (get-buffer-window " *pabbrev suggestions*"))
;; (message "pabbrev suggestions off"))
;; (setq pabbrev-suggestions-buffer-enable t)
;; (add-hook 'post-command-hook
;; 'pabbrev-suggestions-delete-window)
;; (message "pabbrev suggestions on")))
(defun pabbrev-suggestions-delete-window()
"Delete the suggestions window."
(interactive)
(unless
(or pabbrev-mode
(eq (buffer-name) " *pabbrev suggestions*"))
(delete-window (get-buffer-window " *pabbrev suggestions*"))
(set-window-configuration pabbrev-window-configuration)))
;; (defun pabbrev-post-command-delete-suggestions()
;; (interactive)
;; (if pabbrev-suggestions-buffer-enable
;; (progn
;; ;; this isn't perfect. The window pops up in a fairly random place.
;; (with-output-to-temp-buffer " *pabbrev suggestions*")
;; (shrink-window-if-larger-than-buffer (get-buffer-window " *pabbrev suggestions*")))))
;; (defun pabbrev-post-command-show-suggestions(suggestions prefix)
;; (if pabbrev-suggestions-buffer-enable
;; (pabbrev-suggestions-buffer suggestions prefix)))
(defvar pabbrev-window-configuration nil
"Stores the window configuration before presence of a window buffer")
(defun pabbrev-suggestions-goto-buffer(suggestion-list)
"Jump into the suggestions buffer."
;; (if pabbrev-suggestions-buffer-enable
;; (pabbrev-suggestions-delete-window))
(setq pabbrev-window-configuration (current-window-configuration))
(pabbrev-suggestions-buffer suggestion-list "")
(shrink-window-if-larger-than-buffer
(select-window (get-buffer-window " *pabbrev suggestions*"))))
(defvar pabbrev-suggestions-from-buffer nil)
(defvar pabbrev-suggestions-done-suggestions nil)
(defvar pabbrev-suggestions-best-suggestion nil)
(defun pabbrev-suggestions-buffer(suggestions prefix)
"Form the suggestions buffer."
(with-output-to-temp-buffer " *pabbrev suggestions*"
(setq pabbrev-suggestions-from-buffer (current-buffer))
(setq pabbrev-suggestions-best-suggestion
(car suggestions))
(setq pabbrev-suggestions-done-suggestions
(pabbrev-suggestions-limit-alpha-sort suggestions))
(setq suggestions pabbrev-suggestions-done-suggestions)
(let
((window-width (- (window-width) 1)))
(save-excursion
(set-buffer (get-buffer " *pabbrev suggestions*"))
(pabbrev-suggestions-setup)`
(princ
(concat;;"Current Word: " prefix " "
"Max Substring: " (try-completion "" suggestions)
"\n"))
(princ
(concat
"Best Match: " (car pabbrev-suggestions-best-suggestion)
"\n"))
(if suggestions
(loop for i from 0 to 9 do
;; are we less than the suggestions
(if (< i (length suggestions))
(progn
(goto-char (point-max))
;; insert all the suggestions
(let ((next-suggestion
(concat
(number-to-string i)
") "
(car (nth i suggestions)) " " ))
(line-length
(- (line-end-position) (line-beginning-position))))
;; if well. are not on the first suggestion,
(if (and (> i 0)
;; and the line will be too long
(< window-width
(+ line-length (length next-suggestion))))
;; add a new line.
(princ "\n"))
(princ next-suggestion)
(let ((start (- (point) (length next-suggestion))))
(overlay-put
(make-overlay start (+ 2 start))
'face 'pabbrev-suggestions-label-face))))))))))
(shrink-window-if-larger-than-buffer (get-buffer-window " *pabbrev suggestions*")))
(defun pabbrev-suggestions-limit-alpha-sort(suggestions)
"Limit suggestions and sort."
(delq nil
(sort (pabbrev-suggestions-subseq suggestions 0 10)
(lambda(a b)
(string< (car a) (car b))))))
(defun pabbrev-suggestions-subseq(sequence from to)
"Return subsequence from seq.
FROM starting here
TO finishing here.
Amazing though it seems the implementation of this differs between Emacs,
and XEmacs. Irritating or what!
The Emacs version copes with numbers past the end, and backs with nil
values. XEmacs uses its own builtin rather than the one in the CL package.
It crashes under the same circumstances. Yeech."
(if pabbrev-xemacs-p
(subseq sequence from
(min to
(length sequence)))
(subseq sequence from to)))
(defun pabbrev-suggestions-setup()
"Set up suggestions major mode."
(unless (fboundp 'pabbrev-select-mode)
;; define pabbrev select mode
(define-derived-mode pabbrev-select-mode fundamental-mode
"Pabbrev Select"
"Major mode for selecting `pabbrev-mode' expansions.
The number keys selects the various possible expansions. \\[pabbrev-suggestions-delete]
removes the previously added expansion, \\[pabbrev-suggestions-minimum] selects the minimum
matching substring, while \\[pabbrev-suggestions-delete-window] just deletes the window
\\{pabbrev-select-mode-map}")
(setq pabbrev-select-mode-map (make-sparse-keymap))
(loop for i from 33 to 126 do
(define-key pabbrev-select-mode-map (char-to-string i) 'pabbrev-noop))
(define-key pabbrev-select-mode-map "\t" 'pabbrev-suggestions-select-default)
(define-key pabbrev-select-mode-map [delete] 'pabbrev-suggestions-delete)
(define-key pabbrev-select-mode-map [backspace] 'pabbrev-suggestions-delete)
(define-key pabbrev-select-mode-map "\C-m" 'pabbrev-suggestions-minimum)
(define-key pabbrev-select-mode-map " " 'pabbrev-suggestions-delete-window)
(define-key pabbrev-select-mode-map "q" 'pabbrev-suggestions-delete-window)
;; define all the standard insert commands
(loop for i from 0 to 9 do
(define-key pabbrev-select-mode-map
(number-to-string i) 'pabbrev-suggestions-select)))
(pabbrev-select-mode))
(defun pabbrev-noop()
"Do absolutely nothing.
This command is used to nobble the suggestions buffer
self inserting commands."
(interactive))
(defun pabbrev-suggestions-select-default()
"Select the most commonly occuring string."
(interactive)
(if pabbrev-suggestions-best-suggestion
(pabbrev-suggestions-insert
(car pabbrev-suggestions-best-suggestion))))
(defun pabbrev-suggestions-delete()
"Delete the last suggestion."
(interactive)
(pabbrev-suggestions-insert
pabbrev-expand-previous-word))
(defun pabbrev-suggestions-minimum()
"Select the maximally occuring substring."
(interactive)
(pabbrev-suggestions-insert
;;(try-completion "" pabbrev-suggestions-done-suggestions)))
(try-completion "" (pabbrev-suggestions-subseq pabbrev-suggestions-done-suggestions 0 10))))
(defun pabbrev-suggestions-insert(insertion)
"Actually insert the suggestion."
(let ((point))
(save-excursion
(set-buffer pabbrev-suggestions-from-buffer)
(let ((bounds (pabbrev-bounds-of-thing-at-point)))
(progn
(delete-region (car bounds) (cdr bounds))
(insert insertion)
(setq point (point)))))
(pabbrev-suggestions-delete-window)
(if point
(goto-char point))))
(defun pabbrev-suggestions-select(&optional index)
"Select one of the numbered suggestions."
(interactive)
(let ((insert-index
(or index
(string-to-number
(char-to-string last-command-event)))))
(if (< insert-index
(length pabbrev-suggestions-done-suggestions))
(pabbrev-suggestions-insert
(car
(nth insert-index pabbrev-suggestions-done-suggestions))))))
;; These functions define movement around the buffer, which
;; determines what pabbrev considers to be a "word"
(defun pabbrev-forward-thing(&optional number)
"Move forward a pabbrev word. Or backwards if number -1"
(interactive)
(forward-thing pabbrev-thing-at-point-constituent number))
(defun pabbrev-thing-at-point()
"Get thing at point."
(let ((bounds (pabbrev-bounds-of-thing-at-point)))
(if bounds
(buffer-substring-no-properties
(car bounds) (cdr bounds)))))
(defun pabbrev-bounds-of-thing-at-point()
"Get the bounds of the thing at point"
(bounds-of-thing-at-point
pabbrev-thing-at-point-constituent))
;; These functions deal with scavenging word usage from the buffer,
;; which are then added to the dictionary.
(defun pabbrev-bounds-marked-p (start end)
"Return t if anywhere between START and END is marked."
(save-excursion
(let ((retn))
(do ((i start (1+ i)))
((> i end))
(if
(setq retn
(get-text-property i 'pabbrev-added))
(setq i end)))
retn)))
(defun pabbrev-mark-add-word (bounds)
"Add word in BOUNDS as abbreviation, and mark the buffer."
(if bounds
(let ((start (car bounds))
(end (cdr bounds)))
(unless
;; is this word or part of it already added?
(pabbrev-bounds-marked-p start end)
;; mark the word visibly as well.
(pabbrev-debug-display start end)
;; set a property so that we know what we have done.
(pabbrev-save-buffer-modified-p
(add-text-properties start end
'(pabbrev-added t)))
;; and add the word to the system.
(pabbrev-add-word
(buffer-substring-no-properties start end))))))
(defun pabbrev-scavenge-some()
"Gather some words up from around point"
(interactive)
(save-excursion
;; move somewhat away from point, as this is likely to not contain
;; complete words.
(pabbrev-forward-thing -2)
(pabbrev-scavenge-words -1
(* 2 pabbrev-scavenge-some-chunk-size))
(save-excursion
(pabbrev-forward-thing 2)
(pabbrev-scavenge-words 1 pabbrev-scavenge-some-chunk-size))))
(defun pabbrev-scavenge-region()
(interactive)
(narrow-to-region (region-beginning) (region-end))
(pabbrev-scavenge-buffer))
(defun pabbrev-scavenge-buffer-fast()
(interactive)
(message "pabbrev fast scavenging buffer...")
(save-excursion
(goto-char (point-min))
(while (pabbrev-forward-thing)
(let* ((bounds (pabbrev-bounds-of-thing-at-point))
(start (car bounds))
(stop (cdr bounds)))
(unless
(pabbrev-bounds-marked-p start stop)
(pabbrev-add-word
(buffer-substring-no-properties start stop)))))
(pabbrev-debug-message "Dictionary size %s total usage %s"
(pabbrev-get-usage-dictionary-size))
(pabbrev-save-buffer-modified-p
(add-text-properties (point-min) (point-max)
'(pabbrev-added t)))
(message "pabbrev fast scavenging buffer...done.")))
(defun pabbrev-scavenge-buffer()
(interactive)
(save-excursion
(goto-char (point-min))
(working-status-forms "pabbrev scavenging buffer" "done"
(while (pabbrev-forward-thing)
(working-status (/ (* 100 (point)) (point-max)))
;;(message "pabbrev scavenging (buffer %s words %s line %s done %s %%)..."
;; (current-buffer)
;; (pabbrev-get-usage-dictionary-size)
;; current-line
;; (/ (* 100 current-line) total-line))
;;(message "pabbrev scavenging buffer...On line %s"
;; (count-lines (point-min) (point)))
(pabbrev-mark-add-word
(pabbrev-bounds-of-thing-at-point)))
(working-status t))
(pabbrev-debug-message "Dictionary size %s total usage %s"
(pabbrev-get-usage-dictionary-size))
(message "pabbrev scavenging buffer...done.")))
(defun pabbrev-scavenge-words(&optional direction number)
"Scavenge words from current buffer, starting from point.
DIRECTION is in which direction we should work,
NUMBER is how many words we should try to scavenge"
(if (not direction)
(setq direction 1))
(if (not number)
(setq number 20))
(save-excursion
(dotimes (i number)
(pabbrev-forward-thing direction)
(pabbrev-mark-add-word
(pabbrev-bounds-of-thing-at-point)))
(point)))
;; switch on the idle timer if required when the mode is switched on.
(add-hook 'pabbrev-mode-on-hook
'pabbrev-ensure-idle-timer)
;; also run the idle timer function, to put some works in the
;; dictionary.
(add-hook 'pabbrev-mode-on-hook
'pabbrev-scavenge-some)
(defvar pabbrev-long-idle-timer nil
"Timer which adds whole buffer.
There are two idle timers which run for function `pabbrev-mode'. This
one doesn't start for a while, but once it has will work its way
through the whole buffer. In prints out a message to say what its
doing, and stops on user input. The variable
`pabbrev-short-idle-timer' is the other.
The idea here is that the short timer will pick up most of the recent
changes, and will not bother the user. The long timer will slowly
gather up the whole buffer, telling the user what it is doing, in case
it takes up too much processor. If this happened after a second it
would be irritating in the extreme.")
(defvar pabbrev-short-idle-timer nil
"Timer which adds a few words.
See `pabbrev-long-idle-timer'.")
(defun pabbrev-ensure-idle-timer()
(unless nil
(if (not (and pabbrev-short-idle-timer
pabbrev-long-idle-timer))
(pabbrev-start-idle-timer))))
(defun pabbrev-start-idle-timer()
(setq pabbrev-long-idle-timer
(run-with-idle-timer 5 t 'pabbrev-idle-timer-function))
(setq pabbrev-short-idle-timer
(run-with-idle-timer 1 t 'pabbrev-short-idle-timer)))
;;(setq pabbrev-disable-timers t)
(defvar pabbrev-disable-timers nil)
;; I don't understand why this is necessary but it seems to help the
;; slow idle timer work in the correct buffer. I suspect someother
;; timer is screwing up with the current buffer...
(defvar pabbrev-timer-buffer nil)
(defun pabbrev-short-idle-timer(&optional buffer)
"Add a few words to the dictionary."
(save-excursion
(set-buffer (or buffer (current-buffer)))
;; remember which buffer we have just looked at.
(setq pabbrev-timer-buffer (current-buffer))
(if (and pabbrev-mode (not pabbrev-disable-timers))
(progn
(pabbrev-debug-message "running short idle timer")
;;(message "Running short timer in %s" (current-buffer))
(pabbrev-scavenge-some)
(pabbrev-debug-message "Dictionary size %s total usage %s"
(pabbrev-get-usage-dictionary-size)
(pabbrev-get-total-usages-dictionary))))))
(defun pabbrev-idle-timer-function(&optional buffer)
;; so this only works on the current buffer. Might want to scavenge
;; over other buffers
(save-excursion
(set-buffer (or buffer pabbrev-timer-buffer (current-buffer)))
(if (and pabbrev-mode (not pabbrev-disable-timers))
(pabbrev-idle-timer-function-0)
(pabbrev-debug-message "idle running in non pabbrev-mode"))))
;; for some reason that I do not understand yet, this sometimes
;; appears to work in the wrong buffer. I really have not got any idea
;; why this is the case.
(defun pabbrev-idle-timer-function-0()
"Add all words to the buffer.
`pabbrev-scavenge-buffer' does this more efficiently interactively.
If this takes up too much processor power, see `pabbrev-scavenge-some-chunk-size'."
(let ((forward-marker (point))
(backward-marker (point))
(forward-complete nil)
(backward-complete nil)
(repeat t))
(if pabbrev-idle-timer-verbose
(message "pabbrev scavenging..."))
(pabbrev-debug-message "running idle timer at %s" (point))
(while
(and repeat
(not (and forward-complete backward-complete)))
(save-excursion
(unless backward-complete
(goto-char backward-marker)
(setq backward-marker
(pabbrev-scavenge-words -1
(* 2 pabbrev-scavenge-some-chunk-size)))
(setq backward-complete
(eq (point-min) backward-marker))
(pabbrev-debug-message "searching backward to %s complete %s"
backward-marker backward-complete))
(unless forward-complete
(goto-char forward-marker)
(setq forward-marker
(pabbrev-scavenge-words 1 pabbrev-scavenge-some-chunk-size))
(setq forward-complete
(eq (point-max) forward-marker))
(pabbrev-debug-message "searching forward to %s complete %s"
forward-marker forward-complete)))
(pabbrev-debug-message "Dictionary size %s total usage %s"
(pabbrev-get-usage-dictionary-size)
(pabbrev-get-total-usages-dictionary))
(if pabbrev-idle-timer-verbose
(message "pabbrev scavenging (%s words %s buffer)..." (pabbrev-get-usage-dictionary-size)
(buffer-name (current-buffer))))
(setq repeat (sit-for 0.1)))
(if pabbrev-idle-timer-verbose
(progn
(message "pabbrev scavenging...done")
(sit-for 2)
(message nil)))))
(defun pabbrev-shut-up()
"Switch off verbose messages..."
(interactive)
(message "Swiching off pabbrev messages" )
(setq pabbrev-idle-timer-verbose nil))
;;; The following are debug functions.
(defvar pabbrev-debug-buffer nil)
;;(setq pabbrev-debug-enabled t)
(defvar pabbrev-debug-enabled nil)
(defun pabbrev-debug-get-buffer()
(get-buffer-create "*pabbrev-debug"))
(defmacro pabbrev-debug-message(&rest body)
`(if pabbrev-debug-enabled
(let ((insert
(concat (format ,@body) "\n")))
(save-excursion
(set-buffer
(pabbrev-debug-get-buffer))
(goto-char (point-max))
(insert insert)
(pabbrev-debug-frame-scroll)))))
(defun pabbrev-debug()
(interactive)
(pabbrev-debug-frame)
(setq pabbrev-debug-enabled t))
(defvar pabbrev-debug-frame nil)
(defun pabbrev-debug-frame()
(interactive)
(if (not pabbrev-debug-frame)
(progn
(setq pabbrev-debug-frame
(make-frame '((width . 30)
(height . 30))))
(select-frame pabbrev-debug-frame)
(switch-to-buffer (pabbrev-debug-get-buffer)))))
(defun pabbrev-debug-frame-scroll()
(save-excursion
(if pabbrev-debug-frame
(progn
(select-frame pabbrev-debug-frame)
(switch-to-buffer (pabbrev-debug-get-buffer))
(goto-char (point-max))))))
;;(setq pabbrev-debug-display t)
(defvar pabbrev-debug-display nil
"If t visible mark the progress of function `pabbrev-mode' through the buffer.
This looks very ugly. Note that this only shows newly added words. Use
`pabbrev-debug-remove-properties' to clear this invisible markers. Use
`pabbrev-debug-show-all-properties' to show existing markers.")
(defun pabbrev-debug-display(start end)
(if pabbrev-debug-display
(overlay-put
(make-overlay start end)
'face 'pabbrev-debug-display-label-face)))
(defface pabbrev-debug-display-label-face
'((t
(:underline "navy")))
"Font Lock mode face used to highlight suggestions"
:group 'pabbrev)
(defun pabbrev-debug-erase-all-overlays()
"Kill all visible overlays from the current buffer. "
(interactive)
(pabbrev-debug-remove-properties)
(mapcar
(lambda(overlay)
(if
(eq 'pabbrev-debug-display-label-face
(overlay-get overlay 'face))
(delete-overlay overlay)))
(overlays-in
(point-min) (point-max))))
(defun pabbrev-debug-show-all-properties()
"Show all existing markers.
This can be rather slow."
(interactive)
(goto-char (point-min))
(let ((on-mark-state nil)
(on-mark))
(while t
(progn
(setq on-mark (get-text-property (point) 'pabbrev-added))
(message "On line %s"
(count-lines (point-min) (point)))
(cond
;; just moved onto marked area
((and on-mark (not on-mark-state))
(setq on-mark-state (point)))
;; just moved off a marked area
((and on-mark-state (not on-mark))
(progn
(overlay-put
(make-overlay on-mark-state (point))
'face 'underline)
(setq on-mark-state nil)))))
(forward-char))))
(defun pabbrev-debug-restart-idle-timer()
"Kill and restart the idle timers."
(interactive)
(pabbrev-debug-kill-idle-timer)
(pabbrev-ensure-idle-timer))
(defun pabbrev-debug-kill-idle-timer()
"Kill the idle timers.
Toggling `pabbrev-mode' will tend to turn them on again, as
will `pabbrev-debug-restart-idle-timer'."
(interactive)
(if pabbrev-short-idle-timer
(progn
(cancel-timer pabbrev-short-idle-timer)
(setq pabbrev-short-idle-timer nil)))
(if pabbrev-long-idle-timer
(progn
(cancel-timer pabbrev-long-idle-timer)
(setq pabbrev-long-idle-timer nil))))
(defun pabbrev-debug-clear()
(pabbrev-debug-clear-all-hashes)
(pabbrev-debug-remove-properties))
(defun pabbrev-debug-remove-properties()
"Remove all the `pabbrev-added' properties from the buffer.
This means all the words in the buffer will be open for addition
to the dictionary."
(interactive)
(remove-text-properties
(point-min)
(point-max)
'(pabbrev-added)))
(defun pabbrev-debug-clear-hashes(&optional mode)
"Clear the dictionary for major mode MODE, or the current mode."
(interactive)
(if (not mode)
(setq mode major-mode))
(setq pabbrev-prefix-hash-modes
(delq mode pabbrev-prefix-hash-modes))
(setq pabbrev-usage-hash-modes
(delq mode pabbrev-usage-hash-modes))
;; help the GC a bit..
(if (pabbrev-get-usage-hash)
(progn
(clrhash (pabbrev-get-usage-hash))
(put mode 'pabbrev-usage-hash nil)))
(if (pabbrev-get-prefix-hash)
(progn
(clrhash (pabbrev-get-prefix-hash))
(put mode 'pabbrev-get-prefix-hash nil))))
(defun pabbrev-debug-clear-all-hashes()
"Clear all hashes for all modes."
(interactive)
(mapcar 'pabbrev-debug-clear-hashes pabbrev-prefix-hash-modes))
(defun pabbrev-debug-print-hashes()
"Print the hashes for the current mode."
(interactive)
(let ((usage (pabbrev-get-usage-hash))
(prefix (pabbrev-get-prefix-hash)))
(switch-to-buffer
(get-buffer-create "*pabbrev hash*"))
(erase-buffer)
(if (not usage)
(insert "Usage hash nil"))
(insert "Usage hash size "
(number-to-string
(hash-table-count usage)) "\n")
(if (not prefix)
(insert "Prefix hash nil")
(insert "Prefix hash size "
(number-to-string
(hash-table-count prefix)) "\n"))
(insert "Usage hash:\n")
(pabbrev-debug-print-hash usage)
(insert "Prefix hash:\n")
(pabbrev-debug-print-hash prefix)))
(defun pabbrev-debug-print-hash(hash)
"Pretty print a hash."
(if hash
(progn
(pp hash (current-buffer))
(insert "\n")
(insert (hash-table-count hash))
(insert "\n")
(maphash
(lambda(key value)
(insert (concat "KEY: " key "\n"))
(pp value (current-buffer)))
hash))))
;; nobble pabbrev -- useful for profiling.
;;
;; nobble core data structures...
;;(defun pabbrev-add-word(word))
;;
;; nobble text properties...
;; (defun pabbrev-mark-add-word (bounds))
;; Working.el hack. Use working.el if it's around, or don't if it's
;; not.
(eval-and-compile
(condition-case nil
(require 'working)
(error
(progn
(defmacro working-status-forms (message donestr &rest forms)
"Contain a block of code during which a working status is shown."
(list 'let (list (list 'msg message) (list 'dstr donestr)
'(ref1 0))
(cons 'progn forms)))
(defun working-status (&optional percent &rest args)
"Called within the macro `working-status-forms', show the status."
(message "%s%s" (apply 'format msg args)
(if (eq percent t) (concat "... " dstr)
(format "... %3d%%"
(or percent
(floor (* 100.0 (/ (float (point))
(point-max)))))))))
(defun working-dynamic-status (&optional number &rest args)
"Called within the macro `working-status-forms', show the status."
(message "%s%s" (apply 'format msg args)
(format "... %c" (aref [ ?- ?/ ?| ?\\ ] (% ref1 4))))
(setq ref1 (1+ ref1)))
(put 'working-status-forms 'lisp-indent-function 2)))))
(provide 'pabbrev)
;;; pabbrev.el ends here