Selectrum: Feature Request: display static indices

Created on 4 Sep 2020  路  20Comments  路  Source: raxod502/selectrum

Right now the indices actually range from 1 to the number of candidates. As a result the indent so that the numbers are properly aligned can be huge in things like execute-extended-command where there are a large number of candidates. Also the number of key-presses to select a candidate with a large index is high.

I think it would be better to just number the currently visible candidates (by default 10 I believe). The indices would range from 1 to 10. The indent would be reasonable. And the highest number you'd have to deal with is 10. Maybe the range can be customizable so it's only 0-9 that way each number would only be one digit.

All 20 comments

I haven't use this option but I think it probably makes sense to allow for showing only the count of the displayed candidates. As far as I remember the idea was to be able to quickly select candidates via numeric argument.

Ideally, I would want this to be very customizable. By "customizable" I mean I want to have the ability to choose which characters and which keys well be used as "quick-jump" keys.

For instance, if this was customized via an alist (not saying it should be, just an example) the default might be:

(list (cons ?0 "C-0")
      (cons ?1 "C-1")
      (cons ?2 "C-2")
      (cons ?3 "C-3")
      (cons ?4 "C-4")
      (cons ?5 "C-5")
      (cons ?6 "C-6")
      (cons ?7 "C-7")
      (cons ?8 "C-8")
      (cons ?9 "C-9"))

This way people could have something like avy-ivy or ace-jump-helm-line.

This is what I'm doing for quick selection keybindings.

I find it adequate, since the candidate I seek is usually suggested by Prescient near the top of the list or is far enough away that typing to narrow the list is faster than just scrolling.

  ;; Use M-/C- with numbers to conveniently select/insert candidates.
  ;; NOTE: Yes, the variable "i" is lexically bound.
  (define-key selectrum-minibuffer-map (kbd "M-0") #'selectrum-submit-exact-input)
  (dolist (i '(1 2 3 4 5 6 7 8 9))
    (define-key selectrum-minibuffer-map (kbd (concat "M-" (number-to-string i)))
      (lambda () (interactive) (selectrum-select-current-candidate i)))
    (define-key selectrum-minibuffer-map (kbd (concat "C-" (number-to-string i)))
      (lambda () (interactive) (selectrum-insert-current-candidate i))))

This is good! Its most of what I wanted. For now I'll adapt this to my code. However, I use 15 candidates so trying to figure out which is the 9th candidate for example may not be so obvious without any visual indicator.

EDIT: I could turn on the indices, though I'm not fond of the huge indentation for large candidates I've mentioned.

EDIT: meh, I'm being nit picky but honest this is fine for now.

I have achieved a good amount of the required behavior by editing the source code.

In selectrum--candidates-display-string, I've replaced the (when selectrum-show-indices ...) form with the following form.

```elisp
(when selectrum-show-indices
(let* ((abs-index (+ index 0))
(num (number-to-string (1+ abs-index)))
(left-padding (- (length (number-to-string selectrum-num-candidates-displayed))
(length num))))
(insert
(propertize (concat (make-string left-padding ? ) num " ") 'face 'minibuffer-prompt))))
````
EDIT:

This keeps the candidate count in the range of 1 to selectrum-num-candidates-displayed. When I figure out how to create a gif I will post it here as well.

EDIT2:
Note that right now the indices seem to be hard coded into selectrum--candidates-display-string. I do not think this is the optimal solution. I'm thinking the user could provide a function that takes in the index of the current candidate and outputs what the user wants displayed to the left of the candidate. This way the user can choose to display letters (or whatever) instead of indices.

After playing with this a bit more I managed to get essentially the setup that I wanted. Namely, I wanted to have an ivy-avy like interface where I could choose letters instead of numbers because up until the number 26, letters are one character as opposed to digits and because they are closer to the homerow keys.

(when selectrum-show-indices
            (let* ((abs-index (+ index 0))
                   ;; (num (number-to-string (+ abs-index)))
                   (num (format "%c" (+ index 97)))
                   (left-padding (- (length (number-to-string selectrum-num-candidates-displayed))
                                    (length num))))
              (insert
               (propertize (concat (make-string left-padding ? ) num " ") 'face 'minibuffer-prompt))))

I bound the keys that I needed with this.

(dotimes (i selectrum-num-candidates-displayed)
  (general-evil-define-key '(emacs insert) selectrum-minibuffer-map
    (format "C-' %c" (+ i 97)) `(lambda () (interactive)
                                  (selectrum-select-current-candidate ,(1+ i)))))

The first code block makes selectrum uses lettered candidates instead of numbered. The second binds keys that correspond to the candidate letters to selecting the candidate at that letter.

Eventually I want to use the homerow keys instead of consequtive letters starting from a.

I also want to consider showing the letters only when I trigger it with the prefix "C-;" as well. Not sure exactly how to do this though.

I recall that @noctuid had been interested in this capability so I mention him here.

I will still be thinking of user-facing way to customize these indices and would be happy to hear any suggestions.

I will still be thinking of user-facing way to customize these indices and would be happy to hear any suggestions.

If it's just one character corresponding to one index, then I think an alist would work well, as it can describe the relationship in both directions and be easy to work with.

If it's just one character corresponding to one index, then I think an alist would work well.

True, but I in the case of letters that could require making a 26 element alist. But its really short using a function.

;; the alphabet
(defun selectrum-indice-display-transformer (index)
  (format "%c" (+ index 97)))

;; regular numbers
(defun selectrum-indice-display-transformer (index)
  index)

;; display nothing
(defun selectrum-indice-display-transformer (index)
  "")

However, I like your idea of an alist more when there isn't a pattern. For example, I wanted to map the candidates to the homerow keys.

((1 . ?a) (2 . ?s) (3 . ?d) (4 . ?f) ...)

Here is a basic example with the alist, based on what you wrote. I assume that the toggling of the display of the indices can be done inside the command, but I don't know how to do that.

 selectrum.el | 32 +++++++++++++++++++++-----------
 1 file changed, 21 insertions(+), 11 deletions(-)

diff --git a/selectrum.el b/selectrum.el
index debaaf8..1f2d6ba 100644
--- a/selectrum.el
+++ b/selectrum.el
@@ -221,6 +221,18 @@ strings."
     map)
   "Keymap used by Selectrum in the minibuffer.")

+(defcustom selectrum-quick-select-keys
+  (cl-loop for char from ?a to ?z
+           for i = 1 then (cl-incf i)
+           collect (cons i char))
+  "Alist of index to character relationship.")
+
+(define-key selectrum-minibuffer-map (kbd "C-;")
+  (defun selectrum-select-by-quick-key ()
+    (interactive)
+    (selectrum-select-current-candidate
+     (car (rassq (read-key) selectrum-quick-select-keys)))))
+
 (defcustom selectrum-candidate-selected-hook nil
   "Normal hook run when the user selects a candidate.
 It gets the same arguments as `selectrum-read' got, prepended
@@ -1034,19 +1046,17 @@ candidate."
                'append displayed-candidate)))
           (insert "\n")
           (when selectrum-show-indices
-            (let* ((abs-index (+ index first-index-displayed))
-                   (num (number-to-string (1+ abs-index)))
-                   (num-digits
-                    (length
-                     (number-to-string
-                      selectrum--total-num-candidates))))
+            (let* ((char (cdr (assq index selectrum-quick-select-keys)))
+                   (num (if char
+                            (format "%c" char)
+                          ""))
+                   (left-padding
+                    (- (length (number-to-string selectrum-num-candidates-displayed))
+                       (length num))))
               (insert
                (propertize
-                (concat
-                 (make-string (- num-digits (length num)) ? )
-                 num " ")
-                'face
-                'minibuffer-prompt))))
+                (concat (make-string left-padding ? ) num " ")
+                'face 'minibuffer-prompt))))
           (insert displayed-candidate)
           (when right-margin
             (insert
-- 
2.25.1


I ran this code, looks good. However, I'm assuming that the selectrum maintainers will want number by default for selectrum-quick-select-keys. That should be easy to do (using ?0 ... ?9).

the toggling of the display of the indices can be done inside the command, but I don't know how to do that

If there were a command that could do that it would be selectrum--minibuffer-post-command-hook, but that didn't work. We somehow need to make selectrum redraw its candidates. @clemera is there currently a way to redraw candidates on demand? I feel like this will be useful in the future anyway.

Should I submit this as a PR?

is there currently a way to redraw candidates on demand?

The redraw via selectrum--minibuffer-post-command-hook happens automatically after each command when the input has changed so one way is invalidate the input by setting selectrum--previous-input-string to nil in the command which would like to trigger it even if the input hasn't changed.

Should I submit this as a PR?

PRs are always welcome, I think it should be an additional setting for selectrum-count-style, we can discuss the details in the PR review.

I found that the bindings to selectrum-select-current-candidate was not reliable and quite confusing as the arg is by default from the current selected candidate, which gets quite confusing when displayed together with the default indices (1-10)

I ended up with this, in case it helps someone:

(defun selectrum-define-keys ()
    (dotimes (i (min selectrum-num-candidates-displayed 10))
      (general-define-key
       :keymaps 'selectrum-minibuffer-map
       (format "M-%d" (mod (1+ i) 10))
       `(lambda () (interactive)
      (let ((selectrum--current-candidate-index -1))
        (selectrum-select-current-candidate ,(1+ i)))))))

Thanks, have you seen this comment? You shouldn't need to let bind selectrum--current-candidate-index when passing the argument as it doesn't use selectrum--current-candidate-index in this case, that is only used if no argument gets passed.

@Luis-Henriquez-1 Can this issue be closed?

@clemera

Thanks, have you seen this comment? You shouldn't need to let bind selectrum--current-candidate-index when passing the argument as it doesn't use selectrum--current-candidate-index in this case, that is only used if no argument gets passed.

Yes, I have seen this, that also didn鈥檛 work due to the lexical binding of i which doesn鈥檛 exist when the lambda is evaluated later. This does not reflect my experience, before setting this variable I had very confusing selection behavior, until I figured out it was based on the position of the selection (especially noticeable when selecting buffer, where it defaults to select the one you are currently using).

Also the code is using this variable for both conditions, with or without args:
https://github.com/raxod502/selectrum/blob/master/selectrum.el#L1237-L1242

With args is used as a base for the index manipulation.

Also the code is using this variable for both conditions, with or without args:

Oh, sorry I looked at the wrong version of the code, the calculation needs to be fixed as it is indeed confusing!

I have changed the behaviour in #215, so that you choose candidate by displayed index. For configuring a personal shortcut one can use:

(dotimes (i 10)
  (define-key
    selectrum-minibuffer-map 
    (kbd (format "M-%d" (1+ i)))
    `(lambda () (interactive)
       (selectrum-select-current-candidate ,(1+ i)))))

Any additional feedback? Thanks for letting us know about the problem!

No, I think this does it, nice that you don't have to do such hacks, I wasn't sure if that behavior was intended, but seemed very strange.

One thing to note is that M-%d only supports a single character, so 10 won't work for example. Hence my limit to 10 and using modulus to turn 10 into 0.

If this is fixed I guess it will also work with the prefix arguments, e.g. C-u 5 RET and M-5 RET which is quite nice as well if people don't want to add custom bindings.

One thing to note is that M-%d only supports a single character, so 10 won't work for example. Hence my limit to 10 and using modulus to turn 10 into 0.

I edited it so it goes only up to ten, keeping it running would overwrite the previous definitions which is probably unintended.

If this is fixed I guess it will also work with the prefix arguments, e.g. C-u 5 RET and M-5 RET which is quite nice as well if people don't want to add custom bindings.

Yes this changes the meaning of the prefix argument. Choosing a candidate by the displayed index makes more sense and is more consistent than based on the currently selected candidate so I think this is a great improvement!

@clemera yes I have no problems with it being closed

Was this page helpful?
0 / 5 - 0 ratings