unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: Mathias Dahl <mathias.dahl@gmail.com>
To: monnier@iro.umontreal.ca, emacs-devel@gnu.org
Subject: Re: Abbrev suggestions - feedback appreciated
Date: Tue, 12 May 2020 00:39:07 +0200	[thread overview]
Message-ID: <CABrcCQ7vZXMNC4f3cfdJ++VuvEG+vnewtRyg0g=TD64+BoqsXw@mail.gmail.com> (raw)
In-Reply-To: <CABrcCQ4sz2rN3Zj7ZEkLSaziMSfM-6+RTT1ss3OCd8czxF41zg@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 13769 bytes --]

Tried out the "why focus on words?" approach and it seems to work well.
Here is the gist of that change:

(defun abbrev-suggest-previous-chars (n)
  (buffer-substring (- (point) n) (point)))

(defun abbrev-suggest-expansion-exist-at-point (expansion)
  (string= (car expansion)
           (abbrev-suggest-previous-chars (length (car expansion)))))

(defun abbrev-suggest ()
  "Suggest an abbrev to the user based on the text before point.
Uses `abbrev-suggest-hint-threshold' to find out if the user should be
informed about the existing abbrev."
  (let (abbrev-found)
    (dolist (expansion (abbrev-get-active-expansions))
      (when (and (abbrev-suggest-above-threshold expansion)
                 (abbrev-suggest-expansion-exist-at-point expansion))
            (setq abbrev-found (abbrev-suggest-best-abbrev
                                expansion abbrev-found))))
    (when abbrev-found
      (abbrev-suggest-inform-user abbrev-found))))

The new abbrev-suggest function above also have that little optimization
you suggested, checking the threshold early on to avoid looking around in
the buffer.

I have yet to make some of the other changes.

/Mathias


On Mon, May 11, 2020 at 11:37 PM Mathias Dahl <mathias.dahl@gmail.com>
wrote:

> Hi Stefan,
>
> Thanks for your feedback. Now, one and a half year later I started working
> on this again :) Still think it is a great idea to have this as part of the
> abbrev concept.
>
> On Tue, Sep 18, 2018 at 4:06 AM Stefan Monnier <monnier@iro.umontreal.ca>
> wrote:
>
>> [ Note: your patch seems to have its TABs converted into SPCs, messing
>>   up indentation.  I tried to fix those when quoting your code, but
>>   I may have mishandled some parts.  ]
>>
>
> I'll have a look at that at my next attempt. Are you saying there should
> be TABs in Emacs' .el files?
>
> > +(defcustom abbrev-suggest nil
>> > +  "Non-nil means we should 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 instead."
>> > +  :type 'boolean
>> > +  :group 'abbrev-mode
>> > +  :group 'convenience)
>>
>> Does it really need to be in `convenience` as well?
>>
>
> Looking at the other defcustoms in the same file, probably not. I guess I
> added that when I first wrote absug.el, thinking it was a convenience
> thing. To be honest I think abbrevs are a convenience thing, but I will not
> fight for tagging it as such...
>
>
>> What would be the disadvantages/risks of enabling it by default?
>>
>
> Of the top of my head, some people might be annoyed by the constant
> "nagging" about using abbrevs. They could of course turn it off, if we
> enable it by default. Since I personally like it I would not bother keeping
> it on and perhaps new users would like it too. The other disadvantage could
> be performance, but we have discussed that already and perhaps it will
> never be a problem, not sure.
>
> By the way, here are the two top defcustoms in abbrev.el:
>
> (defcustom abbrev-file-name
>   (locate-user-emacs-file "abbrev_defs" ".abbrev_defs")
>   "Default name of file from which to read abbrevs."
>   :initialize 'custom-initialize-delay
>   :type 'file)
>
> (defcustom only-global-abbrevs nil
>   "Non-nil means user plans to use global abbrevs only.
> This makes the commands that normally define mode-specific abbrevs
> define global abbrevs instead."
>   :type 'boolean
>   :group 'abbrev-mode
>   :group 'convenience)
>
> The first does not have a group set and the second has convenience as
> group as well. I have never thought about if this is a normal or not. Is it?
>
>
>> > -(defvar abbrev-expand-function #'abbrev--default-expand
>> > -  "Function that `expand-abbrev' uses to perform abbrev expansion.
>> > +(defvar abbrev-expand-function #'abbrev--try-expand-maybe-suggest
>> > +    "Function that `expand-abbrev' uses to perform abbrev expansion.
>> [...]
>> > +(defun abbrev--try-expand-maybe-suggest ()
>> > +  "Try to expand abbrev, look for suggestions if enabled.
>> > +This is set as `abbrev-expand-function'.  If no abbrev expansion
>> > +is found by `abbrev--default-expand', see if there is an abbrev
>> > +defined for the word before point, and suggest it to the user."
>> > +  (or (abbrev--default-expand)
>> > +      (if abbrev-suggest
>> > +          (abbrev-suggest-maybe-suggest)
>> > +        nil)))
>>
>> If you put the code directly into abbrev.el, then I think you may as
>> well modify expand-abbrev rather than hooking into abbrev-expand-function.
>>
>
> Yes, I was looking for other places to put this, but it felt better to me
> to have the suggestions come at the end of the existing code, when abbrevs
> was tried and not found. Also, I was worried to mess with what is said here:
>
> (defun abbrev--default-expand ()
>   "Default function to use for `abbrev-expand-function'.
> This also respects the obsolete wrapper hook `abbrev-expand-functions'.
> \(See `with-wrapper-hook' for details about wrapper hooks.)
> Calls `abbrev-insert' to insert any expansion, **and returns what it
> does.**" <====
>   (subr--with-wrapper-hook-no-warnings abbrev-expand-functions ()
>     (pcase-let ((`(,sym ,name ,wordstart ,wordend) (abbrev--before-point)))
>     ...
>
> That is mentioned in other places too, that it should return what was
> inserted, if any. It did not feel right to mess with that, but perhaps it
> is okay... Here is an attempt:
>
> (defun expand-abbrev ()
>   "Expand the abbrev before point, if there is an abbrev there.
> Effective when explicitly called even when `abbrev-mode' is nil.
> Before doing anything else, runs `pre-abbrev-expand-hook'.
> 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)
>   (run-hooks 'pre-abbrev-expand-hook)
>   (or (funcall abbrev-expand-function)
>       (abbrev-suggest-maybe-suggest)))
>
> Is something like that what you had in mind?
>
>
>> The rest of the code defines abbrev--suggest-maybe-suggest rather than
>> abbrev-suggest-maybe-suggest.
>>
>
> Seems to be a search and replace-mistake :)
>
>
>> > +(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 differs 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."
>> > +  :type 'number
>> > +  :group 'abbrev-mode
>> > +  :group 'convenience)
>>
>> Do we really need to add it to `convenience`?
>> Just as above, I'd recommend you just remove both :group keywords and
>> let the default behavior kick in (which will put it into `abbrev-mode`).
>>
>
> Sure, I'm fine with that.
>
>
>> > +(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
>>
>> I think you'd be better served defining a dolist-style macro or
>> a mapc-style function to loop over all abbrevs without creating an
>> intermediate list.
>>
>
> Sorry, I don't understand what you are suggesting, and why (performance,
> or saving memory?).
>
>
>> > +                      (setq expansions
>> > +                            (cons (cons value (symbol-name e))
>> > +                                  expansions)))))
>>
>> Aka   (push (cons value (symbol-name e)) expansions))))
>>
>
> You learn something new every day :) I'll try that out.
>
>
>> > +(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)))
>>
>> Why does your code pay attention to words?
>>
>
> I'll think about that... Are you thinking that only characters matter? I
> think my focus is primarily using abbrevs for sentences. I also have some
> abbrevs that are only short forms for longer words, but I think I feel that
> I get more benefit from sentence-like abbrevs... Again, I will think about
> this and see if words are what I want to pay attention to.
>
> > +(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))))
>>
>> Please don't quote your lambdas.  `abbrev.el` uses lexical-binding, so
>> you can just write
>>
>>        (run-with-idle-timer
>>         1 nil
>>         (lambda ()
>>           (message "You can write `%s' using the abbrev `%s'."
>>                    (car expansion) (cdr expansion))))
>>
>
> Got it!
>
>
>> > +    (setq abbrev--suggest-saved-recommendations
>> > +          (cons expansion abbrev--suggest-saved-recommendations)))
>>
>> Aka  (push expansion abbrev--suggest-saved-recommendations))
>>
>
> Changed it.
>
>
>> BTW, won't this list contain repetitions?
>>
>
> Yes it will, and it should. Or, at least I am doing that now to show the
> user a count of how many times an expansion was typed manually. Have a look
> at `abbrev--suggest-get-totals'.
>
>
>> Maybe you should use `add-to-list` or `cl-pushnew` instead?
>>
>> One more thing: I think it'd be even better to put abbrev objects (which
>> are implemented as symbols) in there, so you have easy accesss to
>> a more info than just the expansion.
>>
>
> I have access to the expansion as well as the abbrev, and this is now
> added to the optional report shown to the user. Not sure what I need more...
>
> > +(defun abbrev--suggest-shortest-abbrev (new current)
>> > +    "Return the shortest abbrev.
>> > +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)))
>>
>> Maybe rather than the shortest abbrev, it would be better to choose the
>> abbrev with the largest difference between abbrev and expansion.
>>
>
> It sounds like an interesting idea. I will try it out.
>
>
>> > +(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))
>>
>> Why not use something like
>>
>>     (buffer-substring (- (point) (length (car expansion))) (point))?
>>
>> and when it's equal to (car expansion), then maybe check that there's
>> a word boundary?
>>
>
> I'll experiment with not thinking about words. I think I cannot just
> compare things like that because of newlines, but that can be handled since
> I did that in my "word-based code".
>
>
>> > +      (let ((case-fold-search t))
>>
>> Some abbrevs are case-sensitive.
>>
>
> Okay... Will think about that too. Not sure if I added that because I
> actually needed it to solve a problem, or if I was proactive and did not
> need to be...
>
>
>> > +        (when (and (> word-count 0)
>> > +                   (string-match (car expansion) words)
>>
>> (car expansion) is a string, not a regular expression, so you'd need to
>> regexp-quote it before passing it to string-match.
>>
>
> Okay, thanks!
>
>
>> > +                   (abbrev--suggest-above-threshold expansion))
>> > +          (setq abbrev-found (abbrev--suggest-shortest-abbrev
>> > +                              expansion abbrev-found)))))
>> > +    (when abbrev-found
>> > +      (abbrev--suggest-inform-user abbrev-found))))
>>
>> I suspect that abbrev--suggest-above-threshold will eliminate a fairly
>> large number of abbrevs for some users (e.g. those using abbrevs to fix
>> recurring typos) and it's a cheap test which doesn't need to allocate
>> any memory, so I'd recommend performing it before the calls to
>> abbrev--suggest-count-words and abbrev--suggest-get-previous-words.
>>
>
> Will try that.
>
> > +(defun abbrev-suggest-try-expand-maybe-suggest ()
>> > +  "Try to expand abbrev, look for suggestions of no abbrev found.
>> > +This is the main entry to the abbrev suggest mechanism.  This is
>> > +set as the default value for `abbrev-expand-function'.  If no
>> > +abbrev expansion is found by `abbrev--default-expand', see if
>> > +there is an abbrev defined for the word before point, and suggest
>> > +it to the user."
>> > +  (or (abbrev--default-expand)
>> > +      (if abbrev-suggest
>> > +          (abbrev-suggest-maybe-suggest))))
>>
>> Looks identical to the earlier abbrev--try-expand-maybe-suggest
>>
>
> Yes, it was a leftover.
>
> I'll try to cook a new version now. Let's see if it takes another two
> years :)
>
> Thanks for all the suggestions and time spent on this.
>
> /Mathias
>
>

[-- Attachment #2: Type: text/html, Size: 19395 bytes --]

  reply	other threads:[~2020-05-11 22:39 UTC|newest]

Thread overview: 34+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-09-16  7:51 Abbrev suggestions - feedback appreciated Mathias Dahl
2017-09-16  8:46 ` Eli Zaretskii
2017-09-17 13:15   ` Mathias Dahl
2017-09-16 13:22 ` Stefan Monnier
2017-09-17 13:22   ` Mathias Dahl
2017-09-17 14:03     ` Mathias Dahl
2017-09-17 21:23     ` Stefan Monnier
2017-09-17 21:56       ` Mathias Dahl
2017-10-03 12:51         ` Kaushal Modi
2017-10-07 15:13           ` Mathias Dahl
2017-10-07 15:29             ` Stefan Monnier
2017-10-07 17:18               ` Mathias Dahl
2017-10-07 18:40                 ` Mathias Dahl
2017-10-07 22:29                   ` Ian Dunn
2017-10-07 22:44                     ` Stefan Monnier
2017-10-08 16:38                       ` Ian Dunn
2018-09-17 21:48                         ` Mathias Dahl
2018-09-18  2:05                           ` Stefan Monnier
2020-05-11 21:37                             ` Mathias Dahl
2020-05-11 22:39                               ` Mathias Dahl [this message]
2020-05-11 22:58                               ` Stefan Monnier
2020-05-16 22:10                                 ` Mathias Dahl
2020-05-16 22:22                                   ` Mathias Dahl
2020-05-17  3:13                                     ` Stefan Monnier
2020-05-17 14:59                                       ` Mathias Dahl
2020-05-17 15:45                                         ` Eli Zaretskii
2020-05-17 18:43                                           ` Mathias Dahl
2020-05-17 21:20                                             ` Stefan Monnier
2020-05-18 22:00                                               ` Mathias Dahl
2020-06-04 20:14                                                 ` Mathias Dahl
2017-10-07 22:40                 ` Stefan Monnier
2017-10-08 15:28                   ` Mathias Dahl
  -- strict thread matches above, loose matches on Subject: below --
2017-10-08  8:15 Seweryn Kokot
2017-10-08 15:25 ` Mathias Dahl

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='CABrcCQ7vZXMNC4f3cfdJ++VuvEG+vnewtRyg0g=TD64+BoqsXw@mail.gmail.com' \
    --to=mathias.dahl@gmail.com \
    --cc=emacs-devel@gnu.org \
    --cc=monnier@iro.umontreal.ca \
    /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).