all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
* bug#66948: [PATCH] Add Completion Preview mode
@ 2023-11-05 10:26 Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2023-11-05 18:26 ` Philip Kaludercic
  2023-11-15 10:28 ` Sean Whitton
  0 siblings, 2 replies; 29+ messages in thread
From: Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2023-11-05 10:26 UTC (permalink / raw)
  To: 66948; +Cc: João Távora, Dmitry Gutov

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

Tags: patch

Following the recent discussion on emacs-devel[0], I'm attaching a patch
that adds a new Completion Preview minor mode.

I've been testing this patch with different completion backends, and so
far I'm pretty happy with the results.  I've made some tweaks compared
to the version I posted on emacs-devel to make the implementation more
robust, and in particular to accommodate for some peculiarities of
LSP-based completion, which I've tested mostly with `eglot` but also
with `lsp-mode`.

In terms of documentation, this patch extends "(emacs)Symbol Completion"
with a description of Completion Preview mode.  That seemed like a good
place to discuss this mode, but let me know if somewhere else would be
preferable.

Another question is whether to have RET/TAB/both accept and insert the
completion suggestion.  In VS Code, both RET and TAB do that AFAICT.  If
we want to, we can easily do that too by binding RET/TAB in
`completion-preview-active-mode-map`.  Alternatively, we can just
suggest doing so in the documentation.  The current patch binds neither
RET nor TAB, so to accept the suggestion you complete with C-M-i.
Personally, I find that convenient enough, but I can imagine that many
users might expect TAB or RET to work too.

[0] https://lists.gnu.org/archive/html/emacs-devel/2023-10/msg00693.html


[-- Attachment #2: 0001-Add-Completion-Preview-mode.patch --]
[-- Type: text/patch, Size: 13495 bytes --]

From 33966f20c97cbe24fb6e6be86fced26e51d392e4 Mon Sep 17 00:00:00 2001
From: Eshel Yaron <me@eshelyaron.com>
Date: Thu, 2 Nov 2023 16:58:31 +0100
Subject: [PATCH] Add Completion Preview mode

This adds a new minor mode, 'completion-preview-mode', that displays
in-buffer completion suggestions with an inline "preview" overlay.

* lisp/completion-preview.el: New file.
* doc/emacs/programs.texi (Symbol Completion): Document it.
* etc/NEWS: Announce it.
---
 doc/emacs/programs.texi    |  61 ++++++++++++
 etc/NEWS                   |   6 ++
 lisp/completion-preview.el | 199 +++++++++++++++++++++++++++++++++++++
 3 files changed, 266 insertions(+)
 create mode 100644 lisp/completion-preview.el

diff --git a/doc/emacs/programs.texi b/doc/emacs/programs.texi
index 7746bc8bc23..91104a3446c 100644
--- a/doc/emacs/programs.texi
+++ b/doc/emacs/programs.texi
@@ -1701,6 +1701,67 @@ Symbol Completion
   In Text mode and related modes, @kbd{M-@key{TAB}} completes words
 based on the spell-checker's dictionary.  @xref{Spelling}.
 
+@cindex completion preview
+@cindex preview completion
+@cindex suggestion preview
+@cindex Completion Preview mode
+@findex completion-preview-mode
+@vindex completion-preview-mode
+  Completion Preview mode is a minor mode that shows you symbol
+completion suggestions as type.  When you enable Completion Preview
+mode in a buffer (with @kbd{M-x completion-preview-mode}), Emacs
+examines the text around point after certain commands you invoke and
+automatically suggests a possible completion.  Emacs displays this
+suggestion with an inline preview right after point, so you see in
+advance exactly how the text will look if you accept the completion
+suggestion---that's why it's called a preview.
+
+For example, suppose that you have an Emacs Lisp buffer with the
+following code:
+
+@lisp
+(defun doit (foobarbaz)
+  fo@point{}
+@end lisp
+
+If you type another @samp{o}, the preview appears after point,
+suggesting that you complete the text to @samp{foobarbaz}:
+
+@lisp
+(defun doit (foobarbaz)
+  foo@point{}barbaz
+@end lisp
+
+Here, the text @samp{barbaz} after point is the completion preview.
+You can accept the completion suggestion with @kbd{M-@key{TAB}} to
+actually inserts @samp{barbaz} and move point after it:
+
+@lisp
+(defun doit (foobarbaz)
+  foobarbaz@point{}
+@end lisp
+
+If you want to ignore a completion suggestion, just go on editing or
+moving around the buffer.  Completion Preview mode continues to update
+the suggestion as you type according to the text around point.
+
+@vindex completion-preview-exact-match-only
+@vindex completion-preview-commands
+@vindex completion-preview-minimum-symbol-length
+If you set the user option @code{completion-preview-exact-match-only}
+to non-@code{nil}, Completion Preview mode only suggests a completion
+candidate when its the only possible completion for the (partial)
+symbol at point.  The user option @code{completion-preview-commands}
+says which commands should trigger the completion preview, by default
+that is only @code{self-insert-command}, which corresponds to when you
+type regular characters in the buffer.  The user option
+@code{completion-preview-minimum-symbol-length} specifies a minimum
+number of consecutive characters with word or symbol syntax that
+should appear around point for Emacs to suggest a completion.  By
+default, this option is set to 3, so Emacs suggests a completion if
+you type @samp{foo}, but typing just @samp{fo} doesn't trigger
+completion preview.
+
 @node MixedCase Words
 @section MixedCase Words
 @cindex camel case
diff --git a/etc/NEWS b/etc/NEWS
index c06a013466f..22513ccf98f 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1020,6 +1020,12 @@ It highlights parens via ‘show-paren-mode’ and ‘blink-matching-paren’ in
 a user-friendly way, avoids reporting alleged paren mismatches and makes
 sexp navigation more intuitive.
 
++++
+*** New minor mode 'completion-preview-mode'.
+This minor mode shows you symbol completion suggestions as you type,
+using an inline preview.  New user options in the 'completion-preview'
+customization group control exactly when Emacs displays this preview.
+
 ---
 ** The highly accessible Modus themes collection has eight items.
 The 'modus-operandi' and 'modus-vivendi' are the main themes that have
diff --git a/lisp/completion-preview.el b/lisp/completion-preview.el
new file mode 100644
index 00000000000..7a8ce05bbac
--- /dev/null
+++ b/lisp/completion-preview.el
@@ -0,0 +1,199 @@
+;;; completion-preview.el --- Preview completion with inline overlay  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2023 Free Software Foundation, Inc.
+
+;; Author: Eshel Yaron <me@eshelyaron.com>
+;; Maintainer: Eshel Yaron <me@eshelyaron.com>
+;; Keywords: abbrev convenience
+
+;; 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 3 of the License, 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.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This library provides the Completion Preview mode.  This minor mode
+;; displays the top completion candidate for the symbol at point in an
+;; overlay after point.  If you want to enable Completion Preview mode
+;; in all programming modes, add the following to your Emacs init:
+;;
+;;     (add-hook 'prog-mode-hook #'completion-preview-mode)
+;;
+;; Also check out the customization group `completion-preview` for
+;; some user options that you may want to tweak.
+
+;;; Code:
+
+(defgroup completion-preview nil
+  "In-buffer completion preview."
+  :group 'completion)
+
+(defcustom completion-preview-exact-match-only nil
+  "Whether to show completion preview only when there is an exact match.
+
+If this option is non-nil, Completion Preview mode only shows the
+preview overlay when there is exactly one completion candidate
+that matches the symbol at point, otherwise it shows the top
+candidate also when there are multiple matching candidates."
+  :type 'boolean)
+
+(defcustom completion-preview-commands '(self-insert-command)
+  "List of commands that should trigger completion preview."
+  :type '(repeat (function :tag "Command" :value self-insert-command)))
+
+(defcustom completion-preview-minimum-symbol-length 3
+  "Minimum length of the symbol at point for showing completion preview."
+  :type 'natnum)
+
+(defcustom completion-preview-hook
+  '(completion-preview-require-certain-commands
+    completion-preview-require-minimum-symbol-length)
+  "Hook for functions that determine whether to show preview completion.
+
+Completion Preview mode calls each of these functions in order
+after each command, and only displays the completion preview when
+all of the functions return non-nil."
+  :type 'hook)
+
+(defvar completion-preview-sort-function #'minibuffer--sort-by-length-alpha
+  "Sort function to use for choosing a completion candidate to preview.")
+
+(defface completion-preview
+  '((t :inherit shadow))
+  "Face for completion preview overlay.")
+
+(defface completion-preview-exact
+  '((t :underline t :inherit completion-preview))
+  "Face for exact completion preview overlay.")
+
+(defvar-local completion-preview--overlay nil)
+
+(defvar-local completion-preview--skip nil)
+
+(defun completion-preview-require-certain-commands ()
+  "Check if `this-command' is one of `completion-preview-commands'."
+  (memq this-command completion-preview-commands))
+
+(defun completion-preview-require-minimum-symbol-length ()
+  "Check if the length of symbol at point is at least above a certain threshold.
+`completion-preview-minimum-symbol-length' determines that threshold."
+  (pcase (bounds-of-thing-at-point 'symbol)
+    (`(,beg . ,end)
+     (<= completion-preview-minimum-symbol-length (- end beg)))))
+
+(defun completion-preview-hide ()
+  "Hide the completion preview."
+  (when completion-preview--overlay
+    (delete-overlay completion-preview--overlay)
+    (setq completion-preview--overlay nil)))
+
+(defun completion-preview--make-overlay (pos string)
+  "Make a new completion preview overlay at POS showing STRING."
+  (completion-preview-hide)
+  (add-text-properties 0 1 '(cursor 1) string)
+  (setq completion-preview--overlay (make-overlay pos pos))
+  (overlay-put completion-preview--overlay 'after-string string)
+  completion-preview--overlay)
+
+(define-minor-mode completion-preview-active-mode
+  "Mode for when the completion preview is active."
+  :interactive nil
+  (if completion-preview-active-mode
+      (add-hook 'completion-at-point-functions #'completion-preview-insert -1 t)
+    (remove-hook 'completion-at-point-functions #'completion-preview-insert t)
+    (completion-preview-hide)))
+
+(defun completion-preview--exit-function (func)
+  "Return an exit function that hides the completion preview and calls FUNC."
+  (lambda (&rest args)
+    (completion-preview-active-mode -1)
+    (when func (apply func args))))
+
+(defun completion-preview--update ()
+  "Update completion preview."
+  (pcase (let ((completion-preview--skip t))
+           (run-hook-with-args-until-success 'completion-at-point-functions))
+    (`(,beg ,end ,table . ,plist)
+     (let* ((pred (plist-get plist :predicate))
+            (exit-fn (completion-preview--exit-function
+                      (plist-get plist :exit-function)))
+            (string (buffer-substring beg end))
+            (md (completion-metadata string table pred))
+            (sort-fn (or (completion-metadata-get md 'cycle-sort-function)
+                         (completion-metadata-get md 'display-sort-function)
+                         completion-preview-sort-function))
+            (all (completion-all-completions string table pred
+                                             (- (point) beg) md))
+            (last (last all))
+            (base (or (cdr last) 0))
+            (bbeg (+ beg base))
+            (prefix (substring string base)))
+       (when last
+         (setcdr last nil)
+         (let* ((filtered
+                 (seq-filter (apply-partially #'string-prefix-p prefix) all))
+                (sorted (funcall sort-fn filtered))
+                (multi (cadr sorted))   ; multiple candidates
+                (cand (car sorted)))
+           (when (and cand (not (and multi completion-preview-exact-match-only)))
+             (let* ((face (if multi 'completion-preview 'completion-preview-exact))
+                    (after (propertize (substring cand (length prefix)) 'face face)))
+               (unless (string-empty-p after)
+                 (overlay-put (completion-preview--make-overlay end after)
+                              'completion-preview-data
+                              (list bbeg end (list cand)
+                                    :exit-function exit-fn))
+                 (completion-preview-active-mode))))))))))
+
+(defun completion-preview--show ()
+  "Show completion preview."
+  (when completion-preview-active-mode
+    (let* ((data (overlay-get completion-preview--overlay 'completion-preview-data))
+           (beg (car data))
+           (cands (caddr data))
+           (cand (car cands))
+           (plist (cdddr data))
+           (len (length cand))
+           (end (+ beg len))
+           (after (overlay-get completion-preview--overlay 'after-string))
+           (face (get-text-property 0 'face after)))
+      (if (and (< beg (point) end)
+               (string-prefix-p (buffer-substring beg (point)) cand))
+          (overlay-put
+           (completion-preview--make-overlay
+            (point) (propertize (substring cand (- (point) beg)) 'face face))
+           'completion-preview-data (append (list beg (point) cands) plist))
+        (completion-preview-active-mode -1))))
+  (while-no-input (completion-preview--update)))
+
+(defun completion-preview--post-command ()
+  "Create, update or delete completion preview post last command."
+  (if (run-hook-with-args-until-failure 'completion-preview-hook)
+      (completion-preview--show)
+    (completion-preview-active-mode -1)))
+
+(defun completion-preview-insert ()
+  "Completion at point function for inserting the current preview."
+  (when (and completion-preview-active-mode (not completion-preview--skip))
+    (overlay-get completion-preview--overlay 'completion-preview-data)))
+
+;;;###autoload
+(define-minor-mode completion-preview-mode
+  "Show in-buffer completion preview as you type."
+  :lighter " CP"
+  (if completion-preview-mode
+      (add-hook 'post-command-hook #'completion-preview--post-command nil t)
+    (remove-hook 'post-command-hook #'completion-preview--post-command t)
+    (completion-preview-active-mode -1)))
+
+(provide 'completion-preview)
+;;; completion-preview.el ends here
-- 
2.42.0


^ permalink raw reply related	[flat|nested] 29+ messages in thread

end of thread, other threads:[~2023-11-15 19:02 UTC | newest]

Thread overview: 29+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-11-05 10:26 bug#66948: [PATCH] Add Completion Preview mode Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-11-05 18:26 ` Philip Kaludercic
2023-11-05 19:42   ` Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-11-06  7:22     ` Juri Linkov
2023-11-06 15:30       ` Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-11-06 18:05         ` Juri Linkov
2023-11-06 19:47           ` Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-11-07  7:08             ` Juri Linkov
2023-11-08  7:30             ` Juri Linkov
2023-11-08  9:14               ` Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-11-08 15:44                 ` Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-11-09  7:25                   ` Juri Linkov
2023-11-10  7:09                     ` Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-11-10  7:43                       ` Eli Zaretskii
2023-11-10  7:58                         ` Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-11-10  7:59                           ` Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-11-10 13:05                             ` Eli Zaretskii
2023-11-10 16:23                               ` Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-11-11  8:53                                 ` Eli Zaretskii
2023-11-11 12:01                                   ` Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-11-15 13:27                                     ` Eli Zaretskii
2023-11-15 14:22                                       ` Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-11-15 17:17                                         ` Eli Zaretskii
2023-11-15 19:02                                           ` Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-11-10  8:00                           ` Philip Kaludercic
2023-11-06  7:36     ` Philip Kaludercic
2023-11-06 15:37       ` Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
2023-11-15 10:28 ` Sean Whitton
2023-11-15 10:57   ` Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors

Code repositories for project(s) associated with this external index

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

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.