@@ -605,20 +605,11 @@ ignored."
605
605
:group 'lsp-mode
606
606
:package-version '(lsp-mode . "6.3.2"))
607
607
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
-
616
608
(defcustom lsp-completion-show-detail t
617
609
"Whether or not to show detail of completion candidates."
618
610
:type 'boolean
619
611
:group 'lsp-mode)
620
612
621
-
622
613
(defcustom lsp-server-trace nil
623
614
"Request tracing on the server side.
624
615
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."
4316
4307
"filterText" filter-text
4317
4308
"_emacsStartPoint" start-point
4318
4309
"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))
4323
4314
it)
4324
- (seq-into it 'list)
4325
4315
(-group-by (-partial #'get-text-property 0 'lsp-completion-start-point) it)
4326
4316
(sort it (-on #'< (lambda (o) (or (car o) most-positive-fixnum))))))
4327
4317
4328
4318
(cl-defun lsp--capf-filter-candidates (items
4329
4319
&rest plist
4330
4320
&key lsp-items
4331
- &allow-other-keys)
4321
+ &allow-other-keys)
4332
4322
"List all possible completions in cached ITEMS with their prefixes.
4333
4323
We can pass LSP-ITEMS, which will be used when there's no cache.
4334
4324
Also, additional data to attached to each candidate can be passed via PLIST."
4335
4325
(let ((filtered-items
4336
4326
(if items
4337
4327
(->> 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)))))
4350
4343
(-flatten-n 1)
4351
4344
(-sort (-on #'> (lambda (o)
4352
4345
(or (get-text-property 0 'sort-score o)
@@ -4573,6 +4566,92 @@ Others: TRIGGER-CHARS"
4573
4566
text
4574
4567
nil nil 1))
4575
4568
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
+
4576
4655
(defun lsp--sort-completions (completions)
4577
4656
"Sort COMPLETIONS."
4578
4657
(--sort (let ((left (gethash "sortText" it))
0 commit comments