8000 lsp--capf-filter-candidates: using our own score to sort candidates (… · emacs-lsp/lsp-mode@5c9959f · GitHub
[go: up one dir, main page]

Skip to content

Commit 5c9959f

Browse files
authored
lsp--capf-filter-candidates: using our own score to sort candidates (#1726)
1 parent d5f0410 commit 5c9959f

File tree

1 file changed

+106
-27
lines changed

1 file changed

+106
-27
lines changed

lsp-mode.el

Lines changed: 106 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -605,20 +605,11 @@ ignored."
605605
:group 'lsp-mode
606606
:package-version '(lsp-mode . "6.3.2"))
607607

608-
(defcustom lsp-completion-styles (if (version<= "27.0" emacs-version)
609-
`(flex)
610-
`(substring))
611-
"List of completion styles to use for filtering completion items."
612-
:type completion--styles-type
613-
:group 'lsp-mode
614-
:package-version '(lsp-mode . "6.3.2"))
615-
616608
(defcustom lsp-completion-show-detail t
617609
"Whether or not to show detail of completion candidates."
618610
:type 'boolean
619611
:group 'lsp-mode)
620612

621-
622613
(defcustom lsp-server-trace nil
623614
"Request tracing on the server side.
624615
The actual trace output at each level depends on the language server in use.
@@ -4316,37 +4307,39 @@ When the heuristic fails to find the prefix start point, return DEFAULT value."
43164307
"filterText" filter-text
43174308
"_emacsStartPoint" start-point
43184309
"score"))
4319-
(propertize (or filter-text label)
4320-
'lsp-completion-item item
4321-
'lsp-completion-start-point start-point
4322-
'lsp-completion-score score))
4310+
(propertize (or filter-text label)
4311+
'lsp-completion-item item
4312+
'lsp-completion-start-point start-point
4313+
'lsp-completion-score score))
43234314
it)
4324-
(seq-into it 'list)
43254315
(-group-by (-partial #'get-text-property 0 'lsp-completion-start-point) it)
43264316
(sort it (-on #'< (lambda (o) (or (car o) most-positive-fixnum))))))
43274317

43284318
(cl-defun lsp--capf-filter-candidates (items
43294319
&rest plist
43304320
&key lsp-items
4331-
&allow-other-keys)
4321+
&allow-other-keys)
43324322
"List all possible completions in cached ITEMS with their prefixes.
43334323
We can pass LSP-ITEMS, which will be used when there's no cache.
43344324
Also, additional data to attached to each candidate can be passed via PLIST."
43354325
(let ((filtered-items
43364326
(if items
43374327
(->> items
4338-
(-map (lambda (item)
4339-
(--> (buffer-substring-no-properties (car item) (point))
4340-
;; TODO: roll-out our own matcher if needed.
4341-
;; https://github.com/rustify-emacs/fuz.el seems to be good candidate.
4342-
(let ((completion-styles lsp-completion-styles)
4343-
completion-regexp-list)
4344-
(completion-all-completions it (cdr item) nil (length it)))
4345-
;; completion-all-completions may return a list in form (a b . x)
4346-
;; the last cdr is not important and need to be removed
4347-
(let ((tail (last it)))
4348-
(if (consp tail) (setcdr tail nil))
4349-
it))))
4328+
(-map (-lambda ((start-point . candidates))
4329+
(let ((query (buffer-substring-no-properties start-point (point))))
4330+
(--> (lsp--regex-fuzzy query)
4331+
(-keep (lambda (cand)
4332+
(when (string-match it cand)
4333+
(setq cand (copy-sequence cand))
4334+
(put-text-property 0 1 'match-data (match-data) cand)
4335+
cand))
4336+
candidates)
4337+
(-map (lambda (cand)
4338+
(put-text-property
4339+
0 1
4340+
'completion-score (lsp--fuzzy-score query cand) cand)
4341+
cand)
4342+
it)))))
43504343
(-flatten-n 1)
43514344
(-sort (-on #'> (lambda (o)
43524345
(or (get-text-property 0 'sort-score o)
@@ -4573,6 +4566,92 @@ Others: TRIGGER-CHARS"
45734566
text
45744567
nil nil 1))
45754568

4569+
(defun lsp--regex-fuzzy (str)
4570+
"Build a regex sequence from STR. Insert .* between each char."
4571+
(apply #'concat
4572+
(cl-mapcar
4573+
#'concat
4574+
(cons "" (cdr (seq-map (lambda (c) (format "[^%c]*" c)) str)))
4575+
(seq-map (lambda (c)
4576+
(format "\\(%s\\)" (regexp-quote (char-to-string c))))
4577+
str))))
4578+
4579+
(defvar lsp--fuzzy-score-case-sensitiveness 20
4580+
"Case sensitiveness, can be in range of [0, inf).")
4581+
4582+
(defun lsp--fuzzy-score (query str)
4583+
"Calculate fuzzy score for STR with query QUERY."
4584+
(-when-let* ((md (cddr (or (get-text-property 0 'match-data str)
4585+
(let ((re (lsp--regex-fuzzy query)))
4586+
(when (string-match re str)
4587+
(match-data))))))
4588+
(start (pop md))
4589+
(len (length str))
4590+
;; To understand how this works, consider these bad
4591+
;; ascii(tm) diagrams showing how the pattern "foo"
4592+
;; flex-matches "fabrobazo", "fbarbazoo" and
4593+
;; "barfoobaz":
4594+
4595+
;; f abr o baz o
4596+
;; + --- + --- +
4597+
4598+
;; f barbaz oo
4599+
;; + ------ ++
4600+
4601+
;; bar foo baz
4602+
;; +++
4603+
4604+
;; "+" indicates parts where the pattern matched. A
4605+
;; "hole" in the middle of the string is indicated by
4606+
;; "-". Note that there are no "holes" near the edges
4607+
;; of the string. The completion score is a number
4608+
;; bound by ]0..1]: the higher the better and only a
4609+
;; perfect match (pattern equals string) will have
4610+
;; score 1. The formula takes the form of a quotient.
4611+
;; For the numerator, we use the number of +, i.e. the
4612+
;; length of the pattern. For the denominator, it
4613+
;; first computes
4614+
;;
4615+
;; hole_i_contrib = 1 + (Li-1)^(1/tightness)
4616+
;;
4617+
;; , for each hole "i" of length "Li", where tightness
4618+
;; is given by `flex-score-match-tightness'. The
4619+
;; final value for the denominator is then given by:
4620+
;;
4621+
;; (SUM_across_i(hole_i_contrib) + 1) * len
4622+
;;
4623+
;; , where "len" is the string's length.
4624+
(score-numerator 0)
4625+
(score-denominator 0)
4626+
(last-b 0)
4627+
(q-ind 0)
4628+
(update-score
4629+
(lambda (a b)
4630+
"Update score variables given match range (A B)."
4631+
(setq score-numerator (+ score-numerator (- b a)))
4632+
(unless (= a len)
4633+
(setq score-denominator
4634+
(+ score-denominator
4635+
(if (= a last-b) 0
4636+
(+ 1
4637+
(if (zerop last-b)
4638+
(- 0 (expt 0.8 (- a last-b)))
4639+
(expt (- a last-b 1)
4640+
0.25))))
4641+
(if (equal (aref query q-ind) (aref str a))
4642+
0
4643+
lsp--fuzzy-score-case-sensitiveness))))
4644+
(setq last-b b))))
4645+
(funcall update-score start start)
4646+
(while md
4647+
(funcall update-score start (car md))
4648+
(pop md)
4649+
(setq start (pop md))
4650+
(cl-incf q-ind))
4651+
(funcall update-score len len)
4652+
(unless (zerop len)
4653+
(/ score-numerator (* len (1+ score-denominator)) 1.0))))
4654+
45764655
(defun lsp--sort-completions (completions)
45774656
"Sort COMPLETIONS."
45784657
(--sort (let ((left (gethash "sortText" it))

0 commit comments

Comments
 (0)
0