From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Robert Pluim Newsgroups: gmane.emacs.devel Subject: Re: [PATCH] Add abbrev suggestions Date: Tue, 15 Sep 2020 10:16:02 +0200 Message-ID: References: <83pn8rgwib.fsf@gnu.org> <83o8o4c8s4.fsf@gnu.org> <838seimytk.fsf@gnu.org> Mime-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="27999"; mail-complaints-to="usenet@ciao.gmane.io" Cc: Eli Zaretskii , emacs-devel@gnu.org To: Mathias Dahl Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Tue Sep 15 10:17:07 2020 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kI69C-00079P-V2 for ged-emacs-devel@m.gmane-mx.org; Tue, 15 Sep 2020 10:17:07 +0200 Original-Received: from localhost ([::1]:43546 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kI69C-0004cM-0p for ged-emacs-devel@m.gmane-mx.org; Tue, 15 Sep 2020 04:17:06 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:48590) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kI68H-0002NX-QM for emacs-devel@gnu.org; Tue, 15 Sep 2020 04:16:09 -0400 Original-Received: from mail-wr1-x42b.google.com ([2a00:1450:4864:20::42b]:43984) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1kI68F-0002Ew-4R; Tue, 15 Sep 2020 04:16:09 -0400 Original-Received: by mail-wr1-x42b.google.com with SMTP id k15so2253134wrn.10; Tue, 15 Sep 2020 01:16:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:references:gmane-reply-to-list:date:in-reply-to :message-id:mime-version:content-transfer-encoding; bh=h2eq3ADu1ItuTY0Oy0YpkUsLLkRAPMar4N+oZeBCQrQ=; b=IVVOYMhjwll5J7tt3fJhf+zQaFiPHHIpv+jLEJzjUSoIcHTFvrTKVlR9SkEoUBjMRF Rw1YPGiHljhIFWJr+Yks9PkzmbG9CMoBZ79M9HAcq3Rz9SAb+h/vub3HzSCBvNiUfraK CjG8ySBpHpVveV0IDWZDOlzEkvm6UM+oYHl3dT7X2M1CuHY5j4XMjWOPnfUq8i9C8BRk ayf2382/508ey14VhmKkq7jdS842FAdo37kqVWycJWo7nqnSgVgoLqP5Mdo9vpvEKGRL liGY67gqtMafct+dtdJ871g+xSSrHWDgvZO6dRTR/+FnNZFbnQ8Wh/k28DgcgNt8fh4n CEfQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:references :gmane-reply-to-list:date:in-reply-to:message-id:mime-version :content-transfer-encoding; bh=h2eq3ADu1ItuTY0Oy0YpkUsLLkRAPMar4N+oZeBCQrQ=; b=jqIOY0AFy4p93J8vqA0d4Enp0G5piOnSRt6tSe5gYUq7X0lHdg0v66/SV/1Ty87Sui eLSPAc8TYxMsqBd0RfheQc+bboXYrC2Al/12Fgb1BxlNDN+4Ob1ZkPS9YyXcwh64d4Vs Zb8Luo1Enx0WRirxER+jrmcxue4IVEClTY9KUhL77JiSOUY2vbyx4vDUsoPtTotjIc06 3ZhNqagymezMGVCmM7KrX+m2WTn74cwG0JFU2yVCA6m2P8rqScphegtXCLKflzIvLMzR dHlsmHu92sPQzAfEaDaSzKsot9f1T9wKrSmQGZ93XBv1K+EA3fkxrKaKn8e3vaiKP8rY 0yIQ== X-Gm-Message-State: AOAM533IMSmd2XzW23kvw88mwmpzpzTIfSJLGMlxW5J7xSFlK2IXQLBT lRvc3eycAKSlWWGPDgfmZnnmB1DD6ns= X-Google-Smtp-Source: ABdhPJzMirzSxujCAuyxDrCA42R6TBKQUizsc5GsMAdA/xiy/3U857l1RZhpFQy+uLFm4nF3vgLExg== X-Received: by 2002:adf:e852:: with SMTP id d18mr21300317wrn.40.1600157764301; Tue, 15 Sep 2020 01:16:04 -0700 (PDT) Original-Received: from rpluim-mac ([2a01:e34:ecfc:a860:1cb9:65e9:2391:c618]) by smtp.gmail.com with ESMTPSA id t17sm853177wrx.82.2020.09.15.01.16.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Sep 2020 01:16:03 -0700 (PDT) Gmane-Reply-To-List: yes In-Reply-To: (Mathias Dahl's message of "Tue, 15 Sep 2020 00:04:05 +0200") Received-SPF: pass client-ip=2a00:1450:4864:20::42b; envelope-from=rpluim@gmail.com; helo=mail-wr1-x42b.google.com X-detected-operating-system: by eggs.gnu.org: No matching host in p0f cache. That's all we know. X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.io gmane.emacs.devel:255721 Archived-At: >>>>> On Tue, 15 Sep 2020 00:04:05 +0200, Mathias Dahl said: Mathias> Hi Eli, Mathias> Finally! I sat down and documented this, updated NEWS as well = as tried Mathias> to format the commit message in the correct manner. Let me kno= w how Mathias> the new patch looks like. Mathias> Keeping my fingers crossed... Nitpicking below. Nothing major, it looks to be a useful feature. Mathias> From b0b1bb04098a11e7f6c5fffbcbf7cd30be4679e0 Mon Sep 17 00:00= :00 2001 Mathias> From: Mathias Dahl Mathias> Date: Mon, 14 Sep 2020 17:05:11 -0400 Mathias> Subject: [PATCH] Add abbrev suggestions Mathias> Add abbrev suggestions You=CA=BCre allowed to be a little bit more verbose in the commit messages :-) Tell us briefly what this does. Mathias> * lisp/abbrev.el (abbrev-suggest): New defcustom. Mathias> (abbrev-suggest-hint-threshold): New defcustom. Mathias> (abbrev--suggest-get-active-tables-including-parents): New= defun. Mathias> (abbrev--suggest-get-active-abbrev-expansions): New defun. Mathias> (abbrev--suggest-count-words): New defun. Mathias> (abbrev--suggest-get-previous-words): New defun. Mathias> (abbrev--suggest-above-threshold): New defun. Mathias> (abbrev--suggest-saved-recommendations): New defvar. Mathias> (abbrev--suggest-inform-user): New defun. Mathias> (abbrev--suggest-shortest-abbrev): New defun. Mathias> (abbrev--suggest-maybe-suggest): New defun. Mathias> (abbrev--suggest-get-totals): New defun. Mathias> (abbrev-suggest-show-report): New defun. Mathias> (expand-abbrev): If the previous word was not an abbrev, m= aybe Mathias> suggest an abbrev to the user. Mathias> * doc/emacs/abbrevs.texi (Abbrev suggestions): New section. Mathias> * etc/NEWS: Announce abbrev suggestions. Mathias> --- Mathias> doc/emacs/abbrevs.texi | 33 ++++++++++++ Mathias> etc/NEWS | 8 +++ Mathias> lisp/abbrev.el | 143 ++++++++++++++++++++++++++++++++= ++++++++++++++++- Mathias> 3 files changed, 183 insertions(+), 1 deletion(-) Mathias> diff --git a/doc/emacs/abbrevs.texi b/doc/emacs/abbrevs.texi Mathias> index 21bf8c5..fbe7117 100644 Mathias> --- a/doc/emacs/abbrevs.texi Mathias> +++ b/doc/emacs/abbrevs.texi Mathias> @@ -28,6 +28,7 @@ Abbrevs Mathias> * Abbrev Concepts:: Fundamentals of defined abbrevs. Mathias> * Defining Abbrevs:: Defining an abbrev, so it will expand w= hen typed. Mathias> * Expanding Abbrevs:: Controlling expansion: prefixes, cancel= ing expansion. Mathias> +* Abbrevs Suggestions:: Get suggestions about defined abbrevs. Mathias> * Editing Abbrevs:: Viewing or editing the entire list of d= efined abbrevs. Mathias> * Saving Abbrevs:: Saving the entire list of abbrevs for a= nother session. Mathias> * Dynamic Abbrevs:: Abbreviations for words already in the = buffer. Mathias> @@ -223,6 +224,38 @@ Expanding Abbrevs Mathias> the abbrev expansion. @xref{Abbrev Expansion,,, elisp, The E= macs Lisp Mathias> Reference Manual}. =20 Mathias> +@node Abbrev Suggestions Mathias> +@section Abbrev Suggestions Mathias> + Mathias> + You can get abbrev suggestions when you manually type text = for which Mathias> +there is currently an active defined abbrev. For example, if= there is Mathias> +an abbrev @samp{foo} with the expansion @samp{find outer otte= r}, and Mathias> +you manually type @samp{find outer otter}, the abbrev suggest= ion Mathias> +feature will notice this and show a hint in the echo when you= have Mathias> +stop typing. Either "when you stop" or "when you have stopped" Mathias> + Mathias> +@vindex abbrev-suggest Mathias> + Enable the abbrev suggestion feature by setting the variable You don=CA=BCt need "the variable" here. Mathias> +@code{abbrev-suggest} to @code{t}. Mathias> + Mathias> +@vindex abbrev-suggest-hint-threshold Mathias> + The variable @code{abbrev-suggest-hint-threshold} controls = when to Nor here. Or call it a "user option" (I assume it=CA=BCs customizable) Mathias> +suggest an abbrev to the user. The variable defines the numb= er of Mathias> +characters that the user must save in order to get a suggesti= on. For Mathias> +example, if the user types @samp{foo bar} (seven characters) = and there Mathias> +is an abbrev @samp{fubar} defined (five characters), the user= will not Mathias> +get any suggestion unless the threshold is set to the number = 2 or Mathias> +lower. With the default value 3, the user would not get any Mathias> +suggestion, because the savings in using the abbrev are too s= mall, not I=CA=BCd drop the "too small," Mathias> +above the threshold. If you always want to get abbrev sugges= tions, Mathias> +set this variable to 0. Mathias> + Mathias> +@findex abbrev-suggest-show-report Mathias> + The command @code{abbrev-suggest-show-report} can be used t= o show a Mathias> +buffer with all abbrev suggestions from the current editing s= ession. Mathias> +This can be useful if you get several abbrev suggestions and = don't Mathias> +remember them all. Mathias> + Mathias> @node Editing Abbrevs Mathias> @section Examining and Editing Abbrevs =20 Mathias> diff --git a/etc/NEWS b/etc/NEWS Mathias> index 4076630..b4dc52a 100644 Mathias> --- a/etc/NEWS Mathias> +++ b/etc/NEWS Mathias> @@ -1158,6 +1158,14 @@ messages, contain the error name of tha= t message now. They can be Mathias> made visible by setting user variable 'dbus-show-dbus-errors'= to Mathias> non-nil, even if protected by 'dbus-ignore-errors' otherwise. =20 Mathias> +** Abbrev mode Mathias> + Mathias> ++++ Mathias> +*** A new user option, 'abbrev-suggest', enables the new abbr= ev Mathias> +suggestion feature. When enabled, if a user manually type a = piece of Mathias> +text that could have been written by using an abbrev, a hint = will be Mathias> +displayed mentioning the abbrev that could have been used ins= tead. Mathias> + The first line should be a complete sentence, with details below, so: "Abbrev can now suggest pre-existing abbreviations based on typed text." Mathias> Mathias> * New Modes and Packages in Emacs 28.1 =20 Mathias> diff --git a/lisp/abbrev.el b/lisp/abbrev.el Mathias> index be6f9ee..a249dc0 100644 Mathias> --- a/lisp/abbrev.el Mathias> +++ b/lisp/abbrev.el Mathias> @@ -824,6 +824,145 @@ abbrev-expand-function Mathias> "Function that `expand-abbrev' uses to perform abbrev expan= sion. Mathias> Takes no argument and should return the abbrev symbol if expa= nsion took place.") =20 Mathias> +(defcustom abbrev-suggest nil Mathias> + "Non-nil means suggest abbrevs to the user. Mathias> +By enabling this option, if abbrev mode is enabled and if the Mathias> +user has typed some text that exists as an abbrev, suggest to= the Mathias> +user to use the abbrev instead." How is the suggestion displayed? Echo area, popup frame....? Mathias> + :type 'boolean Mathias> + :group 'abbrev-mode Mathias> + :version "28.0") "28.1" Mathias> + Mathias> +(defcustom abbrev-suggest-hint-threshold 3 Mathias> + "Threshold for when to inform the user that there is an abb= rev. Mathias> +The threshold is the number of characters that differ between= the Mathias> +length of the abbrev and the length of the expansion. The Mathias> +thinking is that if the expansion is only one or a few charac= ters Mathias> +longer than the abbrev, the benefit of informing the user is = not Mathias> +that big. If you always want to be informed, set this value = to Mathias> +`0' or less. This setting only applies if `abbrev-suggest' is Mathias> +non-nil." Mathias> + :type 'number Mathias> + :group 'abbrev-mode Mathias> + :version "28.0") And again. Plus the :group is derived from the last seen 'defgroup', so it=CA=BCs optional here. Mathias> + Mathias> +(defun abbrev--suggest-get-active-tables-including-parents () Mathias> + "Return a list of all active abbrev tables, including paren= t tables." Mathias> + (let* ((tables (abbrev--active-tables)) Mathias> + (all tables)) You've got indent-tabs-mode set to t, even though our .dir-locals.el sets it to nil for elisp. Mathias> + (dolist (table tables) Mathias> + (setq all (append (abbrev-table-get table :parents) all= ))) Mathias> + all)) Mathias> + Mathias> +(defun abbrev--suggest-get-active-abbrev-expansions () Mathias> + "Return a list of all the active abbrev expansions. Mathias> +Includes expansions from parent abbrev tables." Mathias> + (let (expansions) Mathias> + (dolist (table (abbrev--suggest-get-active-tables-inclu= ding-parents)) Mathias> + (mapatoms (lambda (e) Mathias> + (let ((value (symbol-value (abbrev--symbol e table)))) Mathias> + (when value Mathias> + (push (cons value (symbol-name e)) ex= pansions)))) Mathias> + table)) Mathias> + expansions)) Mathias> + Mathias> +(defun abbrev--suggest-count-words (expansion) Mathias> + "Return the number of words in EXPANSION. Mathias> +Expansion is a string of one or more words." Mathias> + (length (split-string expansion " " t))) Mathias> + Mathias> +(defun abbrev--suggest-get-previous-words (n) Mathias> + "Return the N words before point, spaces included. Mathias> +Changes newlines into spaces." Well, anything with whitespace syntax. I=CA=BCm not sure you need to mention that. Mathias> + (let ((end (point))) Mathias> + (save-excursion Mathias> + (backward-word n) Mathias> + (replace-regexp-in-string Mathias> + "\\s " " " Mathias> + (buffer-substring-no-properties (point) end))))) Mathias> + Mathias> +(defun abbrev--suggest-above-threshold (expansion) Mathias> + "Return non-nil if an abbrev in EXPANSION provides signific= ant savings. "the abbrev", no? I think EXPANSION only contains one abbrev. Mathias> +A significant saving, here, is the difference in length betwe= en Mathias> +the abbrev and the abbrev expansion. EXPANSION is a cons cell Mathias> +where the car is the expansion and the cdr is the abbrev." Mathias> + (>=3D (- (length (car expansion)) Mathias> + (length (cdr expansion))) Mathias> + abbrev-suggest-hint-threshold)) Mathias> + Mathias> +(defvar abbrev--suggest-saved-recommendations nil Mathias> + "Keeps a list of expansions that have abbrevs defined. Mathias> +The user can show this list by calling Mathias> +`abbrev-suggest-show-report'.") Mathias> + Mathias> +(defun abbrev--suggest-inform-user (expansion) Mathias> + "Display a message to the user about the existing abbrev. Mathias> +EXPANSION is a cons cell where the `car' is the expansion and= the Mathias> +`cdr' is the abbrev." Mathias> + (run-with-idle-timer Mathias> + 1 nil Mathias> + (lambda () Mathias> + (message "You can write `%s' using the abbrev `%s'." Mathias> + (car expansion) (cdr expan= sion)))) I=CA=BCd put the abbrev first, and the expansion second. "abbrev `%s' can be used to write `%s'." Mathias> + (push expansion abbrev--suggest-saved-recommendations)) Mathias> + Mathias> +(defun abbrev--suggest-shortest-abbrev (new current) Mathias> + "Return the shortest abbrev of NEW and CURRENT. Mathias> +NEW and CURRENT are cons cells where the `car' is the expansi= on Mathias> +and the `cdr' is the abbrev." Mathias> + (if (not current) Mathias> + new Mathias> + (if (< (length (cdr new)) Mathias> + (length (cdr current))) Mathias> + new Mathias> + current))) Mathias> + Mathias> +(defun abbrev--suggest-maybe-suggest () Mathias> + "Suggest an abbrev to the user based on the word(s) befor= e point. Mathias> +Uses `abbrev-suggest-hint-threshold' to find out if the user = should be Mathias> +informed about the existing abbrev." Mathias> + (let (words abbrev-found word-count) Mathias> + (dolist (expansion (abbrev--suggest-get-active-abbrev-e= xpansions)) Mathias> + (setq word-count (abbrev--suggest-count-words (car expansion= )) Mathias> + words (abbrev--suggest-get-previous-words word-count)) Mathias> + (let ((case-fold-search t)) Mathias> + (when (and (> word-count 0) Personally I=CA=BCm allergic to "(when (and", but opinions differ :-) Mathias> + (string-match (car expansion) words) Mathias> + (abbrev--suggest-above-threshold expansion)) Mathias> + (setq abbrev-found (abbrev--suggest-shortest-abbrev Mathias> + expansion abbrev-found))))) Mathias> + (when abbrev-found Mathias> + (abbrev--suggest-inform-user abbrev-found)))) Mathias> + Mathias> +(defun abbrev--suggest-get-totals () Mathias> + "Return a list of all expansions and how many times they = were used. Mathias> +Each expansion is a cons cell where the `car' is the expansion Mathias> +and the `cdr' is the number of times the expansion has been Mathias> +typed." Mathias> + (let (total cell) Mathias> + (dolist (expansion abbrev--suggest-saved-recommendation= s) Mathias> + (if (not (assoc (car expansion) total)) Mathias> + (push (cons (car expansion) 1) total) Mathias> + (setq cell (assoc (car expansion) total)) Mathias> + (setcdr cell (1+ (cdr cell))))) Mathias> + total)) Mathias> + Mathias> +(defun abbrev-suggest-show-report () Mathias> + "Show the user a report of abbrevs he could have used." Mathias> + (interactive) Mathias> + (let ((totals (abbrev--suggest-get-totals)) Mathias> + (buf (get-buffer-create "*abbrev-suggest*"))) Mathias> + (set-buffer buf) Mathias> + (erase-buffer) Mathias> + (insert "** Abbrev expansion usage ** Mathias> + Mathias> +Below is a list of expansions for which abbrevs are defined, = and Mathias> +the number of times the expansion was typed manually. To dis= play Mathias> +and edit all abbrevs, type `M-x edit-abbrevs RET'\n\n") Mathias> + (dolist (expansion totals) Mathias> + (insert (format " %s: %d\n" (car expansion) (cdr expansion= )))) Mathias> + (display-buffer buf))) `pop-to-buffer' rather than `set-buffer' + `display-buffer'? Mathias> + Mathias> (defun expand-abbrev () Mathias> "Expand the abbrev before point, if there is an abbrev ther= e. Mathias> Effective when explicitly called even when `abbrev-mode' is n= il. Mathias> @@ -831,7 +970,9 @@ expand-abbrev Mathias> the work, and returns whatever it does. (That return value s= hould Mathias> be the abbrev symbol if expansion occurred, else nil.)" Mathias> (interactive) Mathias> - (funcall abbrev-expand-function)) Mathias> + (or (funcall abbrev-expand-function) Mathias> + (if abbrev-suggest Mathias> + (abbrev--suggest-maybe-suggest)))) =20 Mathias> (defun abbrev--default-expand () Mathias> "Default function to use for `abbrev-expand-function'. Mathias> --=20 Mathias> 1.9.1