Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve completing-read-multiple (also fixes #80) #74

Merged
Merged
Changes from 2 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6684a32
Improve completing-read-multiple
clemera Apr 24, 2020
6e8679e
Remove already selected candidates from collection
clemera Apr 25, 2020
497cdb9
Implement suggestions
clemera May 3, 2020
c357bbe
Merge branch 'master' into improve-completing-read-multiple
clemera May 3, 2020
4450ae0
Merge branch 'master' into improve-completing-read-multiple
clemera May 3, 2020
346562e
Save crm-separator for recursive sessions
clemera May 3, 2020
004a86e
Behave like usual for RET
clemera May 3, 2020
1bfced1
Fix longlines
clemera May 3, 2020
9a25a5a
Refactor
clemera May 3, 2020
1148e83
Improve prompt
clemera May 3, 2020
e2d105f
Fix error
clemera May 3, 2020
14632bf
Change prompt text again
clemera May 3, 2020
24a9d1a
Handle `crm-completion-table` for recursive minibuffers
clemera May 3, 2020
a31b82b
Merge branch 'master' into improve-completing-read-multiple
clemera May 9, 2020
83bd44c
Don't add crm vars to selectrums state restore macro
clemera May 9, 2020
f77f244
Add new may modify keyword
clemera May 9, 2020
9eb3b27
Remove old multiple candidates state vars and face
clemera May 9, 2020
1436236
Change prompt message
clemera May 9, 2020
5484048
Update changelog
clemera May 9, 2020
bd792ac
Fix indentation
clemera May 9, 2020
e51851b
Update according to changes of selectrum-completing-read-multiple
clemera May 12, 2020
e7eb34a
Improve prompt description
clemera May 12, 2020
834555a
Remove :multiple keyword
clemera May 13, 2020
1bb0020
Update docstring
clemera May 13, 2020
db8d760
Merge branch 'master' into improve-completing-read-multiple
clemera May 13, 2020
246a197
Update description about unsupported features
clemera May 13, 2020
5a50669
Fix linting errors
clemera May 13, 2020
9bd00a0
Update readme
clemera May 13, 2020
8442c4b
Update changelog
clemera May 13, 2020
b781611
Fix grammar
clemera May 13, 2020
4fa0c87
Update changelog
clemera May 13, 2020
5884388
Rename `selectrum--allow-multiple-selection-p` to `selectrum--crm-p`
clemera May 13, 2020
d093187
Improve docstring
clemera May 13, 2020
6eae7a6
Update changelog
raxod502 May 14, 2020
1aa4c0f
Update README
raxod502 May 14, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 68 additions & 20 deletions selectrum.el
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
(require 'regexp-opt)
(require 'seq)
(require 'subr-x)
(require 'crm)
raxod502 marked this conversation as resolved.
Show resolved Hide resolved

;;;; Faces

Expand Down Expand Up @@ -778,6 +779,15 @@ into the user input area to start with."
(substitute-command-keys
"[\\[selectrum-select-additional] enabled] ")
(text-properties-at (point)))))))
(when crm-completion-table
(let ((inhibit-read-only t))
(save-excursion
(minibuffer-prompt-end)
(when (search-backward ":" nil t)
(insert
(apply #'propertize
" [one or more]"
(text-properties-at (point))))))))
raxod502 marked this conversation as resolved.
Show resolved Hide resolved
(setq selectrum--minibuffer (current-buffer))
(setq selectrum--start-of-input-marker (point-marker))
(if selectrum--repeat
Expand Down Expand Up @@ -869,11 +879,15 @@ Otherwise just return CANDIDATE."
(remove-text-properties
0 (length candidate)
'(face selectrum-current-candidate) candidate)
(apply
#'run-hook-with-args
'selectrum-candidate-selected-hook
candidate selectrum--read-args)
(setq selectrum--result (selectrum--get-full candidate))
(setq selectrum--result
(if (and crm-completion-table
(string-match crm-separator selectrum--previous-input-string))
selectrum--previous-input-string
(apply
#'run-hook-with-args
'selectrum-candidate-selected-hook
candidate selectrum--read-args)
(selectrum--get-full candidate)))
raxod502 marked this conversation as resolved.
Show resolved Hide resolved
(when (string-empty-p selectrum--result)
(setq selectrum--result (or selectrum--default-candidate "")))
(let ((inhibit-read-only t))
Expand Down Expand Up @@ -942,7 +956,15 @@ ignores the currently selected candidate, if one exists."
(let* ((candidate (nth selectrum--current-candidate-index
selectrum--refined-candidates))
(full (selectrum--get-full candidate)))
(insert full)
(insert (if (not crm-completion-table)
full
(let ((string ""))
(dolist (str (butlast
(split-string
selectrum--previous-input-string
crm-separator)))
(setq string (concat string str ",")))
(concat string full))))
(add-to-history minibuffer-history-variable full)
(apply
#'run-hook-with-args
Expand Down Expand Up @@ -981,10 +1003,19 @@ ARG has same meaning as in `previous-history-element'."
(when (eq history t)
(user-error "No history is recorded for this command"))
(let ((result (selectrum-read "History: " history)))
(if (and selectrum--match-required-p
(not (member result selectrum--refined-candidates)))
(user-error "That history element is not one of the candidates")
(selectrum--exit-with result)))))
(cond ((and selectrum--match-required-p
crm-completion-table
(not (cl-every (lambda (i)
(member i selectrum--refined-candidates))
(split-string result crm-separator t))))
(user-error
"History element contains elements not present in candidates"))
((and selectrum--match-required-p
(not crm-completion-table)
(not (member result selectrum--refined-candidates)))
(user-error "History element is not one of the candidates"))
(t
(selectrum--exit-with result))))))

;;;; Main entry points

Expand Down Expand Up @@ -1125,20 +1156,37 @@ HIST, DEF, and INHERIT-INPUT-METHOD, see `completing-read'."

;;;###autoload
(defun selectrum-completing-read-multiple
(prompt table &optional
predicate require-match initial-input
hist def inherit-input-method)
(prompt table &optional predicate require-match initial-input
hist def _inherit-input-method)
"Read one or more choices using Selectrum.
Replaces `completing-read-multiple'. For PROMPT, TABLE,
PREDICATE, REQUIRE-MATCH, INITIAL-INPUT, HIST, DEF, and
INHERIT-INPUT-METHOD, see `completing-read-multiple'."
(ignore initial-input inherit-input-method)
(selectrum-read
prompt (selectrum--normalize-collection table predicate)
:default-candidate (or (car-safe def) def)
:require-match require-match
:history hist
:multiple t))
(let* ((crm-completion-table table)
(coll (all-completions "" #'crm--collection-fn predicate))
(candidates
(lambda (input)
(let ((beg 0)
(inputs ()))
clemera marked this conversation as resolved.
Show resolved Hide resolved
(while (string-match crm-separator input beg)
(push (substring input beg (match-beginning 0))
inputs)
(setq beg (match-end 0)))
(let ((coll (cl-delete-if
(lambda (i)
(member i inputs))
(copy-sequence coll)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would cl-remove-if do the same thing faster?

Copy link
Collaborator Author

@clemera clemera May 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is when I use:

(cl-remove-if
 (lambda (i)
   (member i inputs))
 coll)

and try to input multiple candidates the sequence gets corrupted somehow, I don't understand why this is. To test this use it with the definition above and call describe-face. Look at the number of candidates and afterwards add one face with TAB , now look at the number of candidates again: they shrinked in size more than one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably because cl-remove-if is special-cased to return the same object if there is nothing to remove. Then something downstream destructively modifies the collection.

I looked into this and found that the docstring of selectrum-read was not really clear on whether the collection might be modified. I fixed this problem. With the latest commit, I think using the code you have right now (cl-delete-if plus copy-sequence) should work properly and should be the right thing to do. Let me know.

Copy link
Collaborator Author

@clemera clemera May 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into this and found that the docstring of selectrum-read was not really clear on whether the collection might be modified. I fixed this problem

If the collection is a function (as in this example) the returned candidates can still later be modified by the sorting function (even if may-modify-candidates was not provided), so if may-modify-candidates was not provided maybe we should copy the returned values before doing the sort in post-command-hook, too?

Another thought I had is that even when may-modify-candidates was provided it can be confusing that the candidates returned by the function above can be modified and you can't just reuse the value inside the closure. Maybe we should copy the returned candidates by default, copy-sequence is pretty fast and I think this probably wouldn't be much of a problem and would avoid that people would need to do it manually in cases like above.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that copy-sequence is fast, but it generates a lot of garbage due to duplicating the collection, and garbage collection really kills latency. What's slow in Elisp is really not obvious at first glance.

the candidates returned by the function above can be modified and you can't just reuse the value inside the closure

If we don't copy the candidates returned from a function collection when MAY-MODIFY-CANDIDATES is nil, that's a bug.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that copy-sequence is fast, but it generates a lot of garbage due to duplicating the collection, and garbage collection really kills latency. What's slow in Elisp is really not obvious at first glance.

Ah, yes thanks for pointing that out, totally forgot about gc!

If we don't copy the candidates returned from a function collection when MAY-MODIFY-CANDIDATES is nil, that's a bug.

I will open an issue for that later.

(ninput (substring input beg)))
`((input . ,ninput)
(candidates . ,coll))))))
(res (selectrum-read
prompt
candidates
:require-match require-match
:initial-input initial-input
:history hist
:default-candidate def)))
(split-string res crm-separator t)))

;;;###autoload
(defun selectrum-completion-in-region
Expand Down