summaryrefslogtreecommitdiff
path: root/lisp/abbrev.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/abbrev.el')
-rw-r--r--lisp/abbrev.el169
1 files changed, 157 insertions, 12 deletions
diff --git a/lisp/abbrev.el b/lisp/abbrev.el
index be6f9ee3437..f35c637eed5 100644
--- a/lisp/abbrev.el
+++ b/lisp/abbrev.el
@@ -189,17 +189,21 @@ the ones defined from the buffer now."
(table (read buf))
abbrevs name hook exp count sys)
(forward-line 1)
- (while (progn (forward-line 1)
- (not (eolp)))
- (setq name (read buf) count (read buf))
- (if (equal count '(sys))
- (setq sys t count (read buf))
- (setq sys nil))
- (setq exp (read buf))
- (skip-chars-backward " \t\n\f")
- (setq hook (if (not (eolp)) (read buf)))
- (skip-chars-backward " \t\n\f")
- (setq abbrevs (cons (list name exp hook count sys) abbrevs)))
+ (while (and (not (eobp))
+ ;; Advance as long as we're looking at blank lines
+ ;; or we have an abbrev.
+ (looking-at "[ \t\n]\\|\\(\"\\)"))
+ (when (match-string 1)
+ (setq name (read buf) count (read buf))
+ (if (equal count '(sys))
+ (setq sys t count (read buf))
+ (setq sys nil))
+ (setq exp (read buf))
+ (skip-chars-backward " \t\n\f")
+ (setq hook (if (not (eolp)) (read buf)))
+ (skip-chars-backward " \t\n\f")
+ (setq abbrevs (cons (list name exp hook count sys) abbrevs)))
+ (forward-line 1))
(define-abbrev-table table abbrevs)))))
(defun read-abbrev-file (&optional file quietly)
@@ -824,6 +828,145 @@ see `define-abbrev' for details."
"Function that `expand-abbrev' uses to perform abbrev expansion.
Takes no argument and should return the abbrev symbol if expansion took place.")
+(defcustom abbrev-suggest nil
+ "Non-nil means suggest using abbrevs to save typing.
+When abbrev mode is active and this option is non-nil, Emacs will
+suggest in the echo area to use an existing abbrev if doing so
+will save enough typing. See `abbrev-suggest-hint-threshold' for
+the definition of \"enough typing\"."
+ :type 'boolean
+ :version "28.1")
+
+(defcustom abbrev-suggest-hint-threshold 3
+ "Threshold for when to suggest to use an abbrev to save typing.
+The threshold is the amount of typing, in terms of the number of
+characters, that would be saved by using the abbrev. The
+thinking is that if the expansion is only a few characters
+longer than the abbrev, the benefit of informing the user is not
+significant. If you always want to be informed about existing
+abbrevs for the text you type, set this value to zero or less.
+This setting only applies if `abbrev-suggest' is non-nil."
+ :type 'number
+ :version "28.1")
+
+(defun abbrev--suggest-get-active-tables-including-parents ()
+ "Return a list of all active abbrev tables, including parent tables."
+ (let* ((tables (abbrev--active-tables))
+ (all tables))
+ (dolist (table tables)
+ (setq all (append (abbrev-table-get table :parents) all)))
+ all))
+
+(defun abbrev--suggest-get-active-abbrev-expansions ()
+ "Return a list of all the active abbrev expansions.
+Includes expansions from parent abbrev tables."
+ (let (expansions)
+ (dolist (table (abbrev--suggest-get-active-tables-including-parents))
+ (mapatoms (lambda (e)
+ (let ((value (symbol-value (abbrev--symbol e table))))
+ (when value
+ (push (cons value (symbol-name e)) expansions))))
+ table))
+ expansions))
+
+(defun abbrev--suggest-count-words (expansion)
+ "Return the number of words in EXPANSION.
+Expansion is a string of one or more words."
+ (length (split-string expansion " " t)))
+
+(defun abbrev--suggest-get-previous-words (n)
+ "Return the N words before point, spaces included."
+ (let ((end (point)))
+ (save-excursion
+ (backward-word n)
+ (replace-regexp-in-string
+ "\\s " " "
+ (buffer-substring-no-properties (point) end)))))
+
+(defun abbrev--suggest-above-threshold (expansion)
+ "Return non-nil if the abbrev in EXPANSION provides significant savings.
+A significant saving, here, is the difference in length between
+the abbrev and the abbrev expansion. EXPANSION is a cons cell
+where the car is the expansion and the cdr is the abbrev."
+ (>= (- (length (car expansion))
+ (length (cdr expansion)))
+ abbrev-suggest-hint-threshold))
+
+(defvar abbrev--suggest-saved-recommendations nil
+ "Keeps a list of expansions that have abbrevs defined.
+The user can show this list by calling
+`abbrev-suggest-show-report'.")
+
+(defun abbrev--suggest-inform-user (expansion)
+ "Display a message to the user about the existing abbrev.
+EXPANSION is a cons cell where the `car' is the expansion and the
+`cdr' is the abbrev."
+ (run-with-idle-timer
+ 1 nil
+ (lambda ()
+ (message "You can write `%s' using the abbrev `%s'."
+ (car expansion) (cdr expansion))))
+ (push expansion abbrev--suggest-saved-recommendations))
+
+(defun abbrev--suggest-shortest-abbrev (new current)
+ "Return the shortest abbrev of NEW and CURRENT.
+NEW and CURRENT are cons cells where the `car' is the expansion
+and the `cdr' is the abbrev."
+ (if (not current)
+ new
+ (if (< (length (cdr new))
+ (length (cdr current)))
+ new
+ current)))
+
+(defun abbrev--suggest-maybe-suggest ()
+ "Suggest an abbrev to the user based on the word(s) before point.
+Uses `abbrev-suggest-hint-threshold' to find out if the user should be
+informed about the existing abbrev."
+ (let (words abbrev-found word-count)
+ (dolist (expansion (abbrev--suggest-get-active-abbrev-expansions))
+ (setq word-count (abbrev--suggest-count-words (car expansion))
+ words (abbrev--suggest-get-previous-words word-count))
+ (let ((case-fold-search t))
+ (when (and (> word-count 0)
+ (string-match (car expansion) words)
+ (abbrev--suggest-above-threshold expansion))
+ (setq abbrev-found (abbrev--suggest-shortest-abbrev
+ expansion abbrev-found)))))
+ (when abbrev-found
+ (abbrev--suggest-inform-user abbrev-found))))
+
+(defun abbrev--suggest-get-totals ()
+ "Return a list of all expansions and how many times they were used.
+Each expansion is a cons cell where the `car' is the expansion
+and the `cdr' is the number of times the expansion has been
+typed."
+ (let (total cell)
+ (dolist (expansion abbrev--suggest-saved-recommendations)
+ (if (not (assoc (car expansion) total))
+ (push (cons (car expansion) 1) total)
+ (setq cell (assoc (car expansion) total))
+ (setcdr cell (1+ (cdr cell)))))
+ total))
+
+(defun abbrev-suggest-show-report ()
+ "Show a buffer with the list of abbrevs you could have used.
+This shows the abbrevs you've \"missed\" because you typed the
+full text instead of the abbrevs that expand into that text."
+ (interactive)
+ (let ((totals (abbrev--suggest-get-totals))
+ (buf (get-buffer-create "*abbrev-suggest*")))
+ (set-buffer buf)
+ (erase-buffer)
+ (insert "** Abbrev expansion usage **
+
+Below is a list of expansions for which abbrevs are defined, and
+the number of times the expansion was typed manually. To display
+and edit all abbrevs, type `M-x edit-abbrevs RET'\n\n")
+ (dolist (expansion totals)
+ (insert (format " %s: %d\n" (car expansion) (cdr expansion))))
+ (display-buffer buf)))
+
(defun expand-abbrev ()
"Expand the abbrev before point, if there is an abbrev there.
Effective when explicitly called even when `abbrev-mode' is nil.
@@ -831,7 +974,9 @@ Calls the value of `abbrev-expand-function' with no argument to do
the work, and returns whatever it does. (That return value should
be the abbrev symbol if expansion occurred, else nil.)"
(interactive)
- (funcall abbrev-expand-function))
+ (or (funcall abbrev-expand-function)
+ (if abbrev-suggest
+ (abbrev--suggest-maybe-suggest))))
(defun abbrev--default-expand ()
"Default function to use for `abbrev-expand-function'.