unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: Mathias Dahl <mathias.dahl@gmail.com>
To: Robert Pluim <rpluim@gmail.com>
Cc: Eli Zaretskii <eliz@gnu.org>, emacs-devel@gnu.org
Subject: Re: [PATCH] Add abbrev suggestions
Date: Thu, 24 Sep 2020 22:02:08 +0200	[thread overview]
Message-ID: <CABrcCQ7Uby=B0L9cXJebVa5hjo6QaJ7e2cWZeZqSFzh42P3zqA@mail.gmail.com> (raw)
In-Reply-To: <CABrcCQ68bgouGt-5S2Qjf=+iTKzyW5gh1Mr02RP5pAOLRVN19A@mail.gmail.com>


[-- Attachment #1.1: Type: text/plain, Size: 808 bytes --]

On Fri, Sep 18, 2020 at 10:40 AM Mathias Dahl <mathias.dahl@gmail.com>
wrote:

>
> On Tue, Sep 15, 2020 at 10:16 AM Robert Pluim <rpluim@gmail.com> wrote:
>
>> >>>>> On Tue, 15 Sep 2020 00:04:05 +0200, Mathias Dahl <
>> mathias.dahl@gmail.com> said:
>>
>> Nitpicking below. Nothing major, it looks to be a useful feature.
>>
>
> Hi Robert, thanks for this! I will review your suggestions and incorporate
> the ones I like :)
>

Okay, so I finally got around doing this. I incorporated some of the
changes but not others. Since
it's mostly nitpicking, as you said Robert, it should be fine I think.

Eli, if this looks good, can we get this added to Emacs Git repo? If we
need small tweaks I
would rather want to add those separately instead of trying to get this
first patch 100%
correct.

Thanks!

/Mathias

[-- Attachment #1.2: Type: text/html, Size: 1633 bytes --]

[-- Attachment #2: 0001-Abbrev-suggestions.patch --]
[-- Type: application/octet-stream, Size: 10757 bytes --]

From 8adb089dfc52561dba703036c698c6e063976872 Mon Sep 17 00:00:00 2001
From: Mathias Dahl <mathias.dahl@gmail.com>
Date: Thu, 24 Sep 2020 15:06:23 -0400
Subject: [PATCH] Abbrev suggestions

    Add abbrev suggestions - Suggest to the user to use defined
    abbrevs instead of typing their expansion.

    * lisp/abbrev.el (abbrev-suggest): New defcustom.
    (abbrev-suggest-hint-threshold): New defcustom.
    (abbrev--suggest-get-active-tables-including-parents): New defun.
    (abbrev--suggest-get-active-abbrev-expansions): New defun.
    (abbrev--suggest-count-words): New defun.
    (abbrev--suggest-get-previous-words): New defun.
    (abbrev--suggest-above-threshold): New defun.
    (abbrev--suggest-saved-recommendations): New defvar.
    (abbrev--suggest-inform-user): New defun.
    (abbrev--suggest-shortest-abbrev): New defun.
    (abbrev--suggest-maybe-suggest): New defun.
    (abbrev--suggest-get-totals): New defun.
    (abbrev-suggest-show-report): New defun.
    (expand-abbrev): If the previous word was not an abbrev, maybe
    suggest an abbrev to the user.
    * doc/emacs/abbrevs.texi (Abbrev suggestions): New section.
    * etc/NEWS: Announce abbrev suggestions.

---
 doc/emacs/abbrevs.texi |  32 +++++++++++
 etc/NEWS               |  10 ++++
 lisp/abbrev.el         | 140 ++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 181 insertions(+), 1 deletion(-)

diff --git a/doc/emacs/abbrevs.texi b/doc/emacs/abbrevs.texi
index 21bf8c5..dfced88 100644
--- a/doc/emacs/abbrevs.texi
+++ b/doc/emacs/abbrevs.texi
@@ -28,6 +28,7 @@ Abbrevs
 * Abbrev Concepts::   Fundamentals of defined abbrevs.
 * Defining Abbrevs::  Defining an abbrev, so it will expand when typed.
 * Expanding Abbrevs:: Controlling expansion: prefixes, canceling expansion.
+* Abbrevs Suggestions:: Get suggestions about defined abbrevs.
 * Editing Abbrevs::   Viewing or editing the entire list of defined abbrevs.
 * Saving Abbrevs::    Saving the entire list of abbrevs for another session.
 * Dynamic Abbrevs::   Abbreviations for words already in the buffer.
@@ -223,6 +224,37 @@ Expanding Abbrevs
 the abbrev expansion.  @xref{Abbrev Expansion,,, elisp, The Emacs Lisp
 Reference Manual}.
 
+@node Abbrev Suggestions
+@section Abbrev Suggestions
+
+  You can get abbrev suggestions when you manually type text for which
+there is currently an active defined abbrev.  For example, if there is
+an abbrev @samp{foo} with the expansion @samp{find outer otter}, and
+you manually type @samp{find outer otter}, the abbrev suggestion
+feature will notice this and show a hint in the echo when you have
+stopped typing.
+
+@vindex abbrev-suggest
+  Enable the abbrev suggestion feature by setting
+@code{abbrev-suggest} to @code{t}.
+
+@vindex abbrev-suggest-hint-threshold
+  Controls when to suggest an abbrev to the user.  The variable
+defines the number of characters that the user must save in order to
+get a suggestion.  For example, if the user types @samp{foo bar}
+(seven characters) and there is an abbrev @samp{fubar} defined (five
+characters), the user will not get any suggestion unless the threshold
+is set to the number 2 or lower.  With the default value 3, the user
+would not get any suggestion, because the savings in using the abbrev
+are not above the threshold.  If you always want to get abbrev
+suggestions, set this variable to 0.
+
+@findex abbrev-suggest-show-report
+  The command @code{abbrev-suggest-show-report} can be used to show a
+buffer with all abbrev suggestions from the current editing session.
+This can be useful if you get several abbrev suggestions and don't
+remember them all.
+
 @node Editing Abbrevs
 @section Examining and Editing Abbrevs
 
diff --git a/etc/NEWS b/etc/NEWS
index 4076630..3e404c0 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1158,6 +1158,16 @@ messages, contain the error name of that message now.  They can be
 made visible by setting user variable 'dbus-show-dbus-errors' to
 non-nil, even if protected by 'dbus-ignore-errors' otherwise.
 
+** Abbrev mode
+
++++
+*** Abbrev can now suggest pre-existing abbrevs based on typed text.
+A new user option, 'abbrev-suggest', enables the new abbrev suggestion
+feature.  When enabled, if a user manually type a piece of text that
+could have been written by using an abbrev, a hint will be displayed
+in the echo area, mentioning the abbrev that could have been used
+instead.
+
 \f
 * New Modes and Packages in Emacs 28.1
 
diff --git a/lisp/abbrev.el b/lisp/abbrev.el
index be6f9ee..75cc439 100644
--- a/lisp/abbrev.el
+++ b/lisp/abbrev.el
@@ -824,6 +824,142 @@ abbrev-expand-function
   "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 abbrevs to the user.
+By enabling this option, if abbrev mode is enabled and if the
+user has typed some text that exists as an abbrev, suggest to the
+user to use the abbrev by displaying a message in the echo area."
+    :type 'boolean
+    :version "28.1")
+
+(defcustom abbrev-suggest-hint-threshold 3
+  "Threshold for when to inform the user that there is an abbrev.
+The threshold is the number of characters that differ between the
+length of the abbrev and the length of the expansion.  The
+thinking is that if the expansion is only one or a few characters
+longer than the abbrev, the benefit of informing the user is not
+that big.  If you always want to be informed, set this value to
+`0' 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 the user a report of abbrevs he could have used."
+  (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 +967,9 @@ expand-abbrev
 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'.
-- 
1.9.1


  reply	other threads:[~2020-09-24 20:02 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-07-05 23:40 [PATCH] Add abbrev suggestions Mathias Dahl
2020-07-19 17:40 ` Mathias Dahl
2020-07-19 19:01   ` Eli Zaretskii
2020-07-25  8:12     ` Eli Zaretskii
2020-08-11 22:16       ` Mathias Dahl
2020-08-13 13:59         ` Eli Zaretskii
2020-08-13 14:29           ` Mathias Dahl
2020-09-14 22:04             ` Mathias Dahl
2020-09-15  6:20               ` Andreas Röhler
2020-09-18  8:39                 ` Mathias Dahl
2020-09-15  8:16               ` Robert Pluim
2020-09-18  8:40                 ` Mathias Dahl
2020-09-24 20:02                   ` Mathias Dahl [this message]
2020-09-25  8:09                     ` Robert Pluim
2020-09-25 20:42                       ` Mathias Dahl
2020-09-26 14:19                         ` Robert Pluim
2020-09-26 20:56                           ` Mathias Dahl
2020-09-26 22:21                             ` Stefan Monnier
2020-09-27  6:12                             ` Eli Zaretskii

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to='CABrcCQ7Uby=B0L9cXJebVa5hjo6QaJ7e2cWZeZqSFzh42P3zqA@mail.gmail.com' \
    --to=mathias.dahl@gmail.com \
    --cc=eliz@gnu.org \
    --cc=emacs-devel@gnu.org \
    --cc=rpluim@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).