Subject: Re: bug#75794: [PATCH] feat(icomplete): markers and vertical alignment

From: Rahul Martim Juliato <[email-masked]>
Date: Wed, 12 Mar 2025 21:09:18 -0300

jixiuf <[email-masked]> writes:

>
> I could reproduce with:
>
> (setq show-paren-when-point-inside-paren t)
>

Thanks for your feedback!

Please find attached a new version of this patch.

Could you try it and tell if this one fixes this bug? For me, it did
fixed it! :)

-- 

Rahul Martim Juliato

>From e6f4d614b79949ecf1799e4cc4dc312fd33a5bbd Mon Sep 17 00:00:00 2001
From: Rahul Martim Juliato <[email-masked]>
Date: Wed, 12 Mar 2025 21:00:27 -0300
Subject: [PATCH] Enhance 'icomplete-vertical-mode'

New user options have been added to enhance 'icomplete-vertical-mode':

 + 'icomplete-vertical-in-buffer-adjust-list': Aligns in-buffer
completion to the original cursor column.
 + 'icomplete-vertical-render-prefix-indicator': When enabled, adds a
prefix indicator to completion candidates.
 + 'icomplete-vertical-selected-prefix-indicator': Specifies the prefix
string for the selected candidate.
 + 'icomplete-vertical-unselected-prefix-indicator': Specifies the prefix
string for unselected candidates.

New faces introduced:

 + 'icomplete-vertical-selected-prefix-indicator-face': Controls the
appearance of the selected candidate prefix.
 + 'icomplete-vertical-unselected-prefix-indicator-face': Controls the
appearance of unselected candidate prefixes.

* etc/NEWS: Document the new user options and faces.
* lisp/icomplete.el(icomplete-vertical-mode): Implement the new options
---
 etc/NEWS          |  19 ++++++++
 lisp/icomplete.el | 119 ++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 135 insertions(+), 3 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 258f0dcc4ba..bf4afb8dac0 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -269,6 +269,25 @@ return value windows whose buffers share their text with BUFFER-OR-NAME.
 With such an entry, 'display-buffer-reuse-window' may also choose a
 window whose buffer shares text with the buffer to display.
 
++++
+*** New user options for 'icomplete-vertical-mode'.
+New user options have been added to enhance 'icomplete-vertical-mode':
+
+ - 'icomplete-vertical-in-buffer-adjust-list': Aligns in-buffer
+    completion to the original cursor column.
+ - 'icomplete-vertical-render-prefix-indicator': When enabled, adds a
+    prefix indicator to completion candidates.
+ - 'icomplete-vertical-selected-prefix-indicator': Specifies the prefix
+    string for the selected candidate.
+ - 'icomplete-vertical-unselected-prefix-indicator': Specifies the prefix
+    string for unselected candidates.
+
+New faces:
+
+ - 'icomplete-vertical-selected-prefix-indicator-face': Controls the
+    appearance of the selected candidate prefix.
+ - 'icomplete-vertical-unselected-prefix-indicator-face': Controls the
+    appearance of unselected candidate prefixes.
 
 ** Frames
 
diff --git a/lisp/icomplete.el b/lisp/icomplete.el
index c58bffbb36b..fca941a09db 100644
--- a/lisp/icomplete.el
+++ b/lisp/icomplete.el
@@ -115,6 +115,18 @@ icomplete-section
   "Face used by `icomplete-vertical-mode' for the section title."
   :version "28.1")
 
+(defface icomplete-vertical-selected-prefix-indicator-face
+  '((t :inherit font-lock-keyword-face :weight bold :foreground "cyan"))
+  "Face used for the prefix set by `icomplete-vertical-selected-prefix-indicator'."
+  :group 'icomplete
+  :version "31.1")
+
+(defface icomplete-vertical-unselected-prefix-indicator-face
+  '((t :inherit font-lock-keyword-face :weight normal :foreground "gray"))
+  "Face used for the prefix set by `icomplete-vertical-unselected-prefix-indicator'."
+  :group 'icomplete
+  :version "31.1")
+
 ;;;_* User Customization variables
 (defcustom icomplete-prospects-height 2
   ;; We used to compute how many lines 100 characters would take in
@@ -166,6 +178,46 @@ icomplete-minibuffer-setup-hook
 icompletion is occurring."
   :type 'hook)
 
+(defcustom icomplete-vertical-in-buffer-adjust-list nil
+  "Control whether in-buffer completion should align the cursor position.
+If this is t and `icomplete-in-buffer' is t, and `icomplete-vertical-mode'
+is activated, the in-buffer vertical completions are shown aligned to the
+cursor position when the completion started, not on the first column, as
+the default behaviour."
+  :type 'boolean
+  :group 'icomplete
+  :version "31.1")
+
+(defcustom icomplete-vertical-render-prefix-indicator nil
+  "Control whether a indicator is added as a prefix to each candidate.
+If this is t and `icomplete-vertical-mode' is activated, a indicator,
+controlled by `icomplete-vertical-selected-prefix-indicator' is shown
+as a prefix to the current under selection candidate, while the
+remaining of the candidates will receive the indicator controlled
+by `icomplete-vertical-unselected-prefix-indicator'."
+  :type 'boolean
+  :group 'icomplete
+  :version "31.1")
+
+(defcustom icomplete-vertical-selected-prefix-indicator "» "
+  "Prefix string used to mark the selected completion candidate.
+If `icomplete-vertical-render-prefix-indicator' is t, the string
+defined here is used as a prefix of the currently selected entry in the
+list.  It can be further customized by the face
+`icomplete-vertical-selected-prefix-indicator-face'."
+  :type 'string
+  :group 'icomplete
+  :version "31.1")
+
+(defcustom icomplete-vertical-unselected-prefix-indicator "  "
+  "Prefix string used on the unselected completion candidates.
+If `icomplete-vertical-render-prefix-indicator' is t, the string
+defined here is used as a prefix for all unselected entries in the list.
+list.  It can be further customized by the face
+`icomplete-vertical-unselected-prefix-indicator-face'."
+  :type 'string
+  :group 'icomplete
+  :version "31.1")
 
 ;;;_* Initialization
 
@@ -828,6 +880,58 @@ icomplete--augment
                  else collect (list tr prefix suffix ))
       annotated)))
 
+(defun icomplete-vertical--adjust-lines-for-column (lines buffer data)
+  "Adjust the LINES to align with the column in BUFFER based on DATA."
+  (if icomplete-vertical-in-buffer-adjust-list
+      (let* ((column (current-column))
+             (prefix-indicator-width
+              (if icomplete-vertical-render-prefix-indicator
+                  (max (length icomplete-vertical-selected-prefix-indicator)
+                       (length icomplete-vertical-unselected-prefix-indicator))
+                0))
+             (wrapped-line (with-current-buffer buffer
+                             (save-excursion
+                               (goto-char (car data))
+                               (beginning-of-line)
+                               (count-screen-lines (point) (car data)))))
+             (window-width (+ (window-hscroll) (window-body-width)))
+             (longest-line-width (apply #'max (mapcar #'length lines)))
+             (spaces-to-add
+              (if (> wrapped-line 1)
+                  (- column (* (- wrapped-line 1) (- window-width 5)))
+                column))
+             (spaces-to-add-avoiding-scrolling
+              (if (>= (+ spaces-to-add longest-line-width prefix-indicator-width) window-width)
+                  (- spaces-to-add longest-line-width)
+                spaces-to-add)))
+
+        (mapcar (lambda (line)
+                  (concat (make-string spaces-to-add-avoiding-scrolling ?\s) line))
+                lines))
+    lines))
+
+(defun icomplete-vertical--ensure-visible-lines-inside-buffer ()
+  "Ensure the completion list is visible in regular buffers only.
+Scrolls the screen to be at least `icomplete-prospects-height' real lines
+away from the bottom.  Counts wrapped lines as real lines."
+  (unless (minibufferp)
+    (let* ((window-height (window-body-height))
+           (current-line (count-screen-lines (window-start) (point)))
+           (lines-to-bottom (- window-height current-line)))
+      (when (< lines-to-bottom icomplete-prospects-height)
+        (scroll-up (- icomplete-prospects-height lines-to-bottom))))))
+
+(defun icomplete-vertical--add-indicator-to-selected (comp)
+  "Add indicators to the selected/unselected COMP completions."
+  (if (and icomplete-vertical-render-prefix-indicator
+           (get-text-property 0 'icomplete-selected comp))
+      (concat (propertize icomplete-vertical-selected-prefix-indicator
+                          'face 'icomplete-vertical-selected-prefix-indicator-face)
+              comp)
+    (concat (propertize icomplete-vertical-unselected-prefix-indicator
+                        'face 'icomplete-vertical-unselected-prefix-indicator-face)
+            comp)))
+
 (cl-defun icomplete--render-vertical
     (comps md &aux scroll-above scroll-below
            (total-space ; number of mini-window lines available
@@ -843,12 +947,17 @@ icomplete--render-vertical
   ;; - both nil, there is no manual scroll;
   ;; - both non-nil, there is a healthy manual scroll that doesn't need
   ;;   to be readjusted (user just moved around the minibuffer, for
-  ;;   example)l
+  ;;   example);
   ;; - non-nil and nil, respectively, a refiltering took place and we
   ;;   may need to readjust them to the new filtered `comps'.
+  (when (and icomplete-scroll
+             (not icomplete--scrolled-completions)
+             (not icomplete--scrolled-past))
+    (icomplete-vertical--ensure-visible-lines-inside-buffer))
   (when (and icomplete-scroll
              icomplete--scrolled-completions
              (null icomplete--scrolled-past))
+    (icomplete-vertical--ensure-visible-lines-inside-buffer)
     (cl-loop with preds
              for (comp . rest) on comps
              when (equal comp (car icomplete--scrolled-completions))
@@ -903,13 +1012,14 @@ icomplete--render-vertical
      when section
      collect (propertize section 'face 'icomplete-section) into lines-aux
      and count 1 into nsections-aux
+     for comp = (icomplete-vertical--add-indicator-to-selected comp)
      when (get-text-property 0 'icomplete-selected comp)
      do (add-face-text-property 0 (length comp)
                                 'icomplete-selected-match 'append comp)
      collect (concat prefix
-                     (make-string (- max-prefix-len (length prefix)) ? )
+                     (make-string (max 0 (- max-prefix-len (length prefix))) ? )
                      (completion-lazy-hilit comp)
-                     (make-string (- max-comp-len (length comp)) ? )
+                     (make-string (max 0 (- max-comp-len (length comp))) ? )
                      suffix)
      into lines-aux
      finally (setq lines lines-aux
@@ -924,6 +1034,9 @@ icomplete--render-vertical
                  ((> (length scroll-above) (length scroll-below)) nsections)
                  (t (min (ceiling nsections 2) (length scroll-above))))
            lines))
+    (when icomplete--in-region-buffer
+      (setq lines (icomplete-vertical--adjust-lines-for-column
+                   lines icomplete--in-region-buffer completion-in-region--data)))
     ;; At long last, render final string return value.  This may still
     ;; kick out lines at the end.
     (concat " \n"
-- 
2.39.5