jcs-elpa / company-fuzzy

Fuzzy matching for `company-mode'
GNU General Public License v3.0
121 stars 9 forks source link
auto-complete emacs ycmd

License: GPL v3 JCS-ELPA MELPA MELPA Stable

company-fuzzy

Fuzzy matching for `company-mode'.

CI

Pure elisp fuzzy completion for company-mode. This plugin search through all the buffer local company-backends and fuzzy search all candidates.

πŸ† Features

πŸ§ͺ Differences from other alternatives

πŸ’Ύ Quickstart

(use-package company-fuzzy
  :hook (company-mode . company-fuzzy-mode)
  :init
  (setq company-fuzzy-sorting-backend 'flx
        company-fuzzy-reset-selection t
        company-fuzzy-prefix-on-top nil
        company-fuzzy-trigger-symbols '("." "->" "<" "\"" "'" "@")))

πŸ”§ Usage

You can enable it globally by adding this line to your config

(global-company-fuzzy-mode 1)

Or you can just enable it in any specific buffer/mode you want.

(company-fuzzy-mode 1)

Make sure you have called either of these functions after all company-backends are set and config properly. Because this plugin will replace all backends to this minor mode specific backend (basically take all backends away, so this mode could combine all sources and do the fuzzy work).

πŸ” Sorting/Scoring backend

There are multiple sorting algorithms for auto-completion. You can choose your backend by customizing the company-fuzzy-sorting-backend variable like this.

(setq company-fuzzy-sorting-backend 'alphabetic)

Currently supports these values,

Or implements your sorting algorithm yourself? Assgin the function to company-fuzzy-sorting-function variable like this.

(setq company-fuzzy-sorting-function (lambda (candidates)
                                       (message "%s" candidates)
                                       candidates))  ; Don't forget to return the candidaites!

πŸ” Prefix On Top

If you wish the prefix matechs on top of all other selections, customize this variable to t like the line below.

(setq company-fuzzy-prefix-on-top t)

P.S. If you set company-fuzzy-sorting-backend to 'flx then you probably don't need this to be on because the flx scoring engine already takes care of that!

πŸ” For annotation

You can toggle company-fuzzy-show-annotation for showing annotation or not.

(setq company-fuzzy-show-annotation t)

You can also customize annotation using the format variable.

πŸ” Excluding

You can customize variable company-fuzzy-passthrough-backends to exclude some of the backends from polluting the fuzzy matching.

(setq company-fuzzy-passthrough-backends '(company-capf))

P.S. This is designed to be used with semantic backends, like lsp-mode (uses company-capf), or eglot, etc.

πŸ’¬ Details

Since company grants most control to users, every company backend developer has a different method of implementing the company backend. It is hard to manage all backends to one by various rules.

πŸ” Reset the selection

If you are using an intelligent fuzzy matching algorithm, it is more intuitive to reset the selected candidate to the first candidate.

(setq company-fuzzy-reset-selection t)

Recommended Settings

The company has designed something oddly to achieve this. The plugin operates smoothly, and I suggest configuring these company variables.

(use-package company
  :init
  (setq company-require-match nil            ; Don't require match, so you can still move your cursor as expected.
        company-tooltip-align-annotations t  ; Align annotation to the right side.
        company-eclim-auto-save nil          ; Stop eclim auto save.
        company-dabbrev-downcase nil)        ; No downcase when completion.
  :config
  ;; Enable downcase only when completing the completion.
  (defun jcs--company-complete-selection--advice-around (fn)
    "Advice execute around `company-complete-selection' command."
    (let ((company-dabbrev-downcase t))
      (call-interactively fn)))
  (advice-add 'company-complete-selection :around #'jcs--company-complete-selection--advice-around))

P.S. For the full configuration, you can check out my configuration here.

❓ FAQ

πŸ’« Why is company-fuzzy not working?

Try to log out the company-backends and make sure company-fuzzy-all-other-backends is the only backend in your list. If it's not, enable company-fuzzy-mode to swap out all backends and hand it over to company-fuzzy to manage it.

(message "%s" company-backends)         ; '(company-fuzzy-all-other-backends)
(message "%s" company-fuzzy--backends)  ; .. backends has been handed over to `company-fuzzy`

πŸ’« When should I call company-fuzzy-mode?

You should call company-fuzzy-mode after you have done configured variable company-backends.

(setq company-backends '(company-capf company-yasnippets)  ; configure backends

.. (other configuration)

(company-fuzzy-mode 1)                                     ; enable fuzzy matching at the very last

πŸ’« What if I want to add backends to specific major-mode?

You can add any backends as long as you call company-fuzzy-mode at the very end of your mode hook. You can log out variable company-fuzzy--backends and see what backends are currently handled by company-fuzzy-mode!

Or, you can hack through by configuring variable company-fuzzye--backends directly but this is not recommended since after you disable company-fuzzy-mode it will not be restored back to company-backends. Unless you change it with variable company-fuzzy--recorded-backends simutamiously so it can be restored back to your company-backends' true form.

❗ UPDATE: You can now use the following functions to accomplish these tasks in a much elegant way:

(company-fuzzy-backend-add    'company-capf)
(company-fuzzy-backend-remove 'company-yasnippets)

πŸ’« Why do some candidates aren't showing up?

This can cause by various reasons. The common causes are:

πŸ”Ž 1. Cause by Semantic backend rules

company-fuzzy respects backends' rule. Meaning the candidates can be restricted by the backend you are using. For example,

(defvar my-variable)  ; You declare a variable

(my-vari.. )          ; but you are trying to use the variable as a function

The my-variable would not show up since the backend thinks it should be a function and not a variable.

πŸ”Ž 2. Cause by completion-styles

Candidates are first filtered by Emacs built-on completion engine. Try tweaking the variable completion-styles with other possible options.

(setq completion-styles '(partial-completion))

Or hook up with the company's hooks:

(add-hook 'company-completion-started-hook
          (lambda ()
            (setq completion-styles '(partial-completion))))

(add-hook 'company-after-completion-hook
          (lambda ()
            ;; Revert `completion-styles' to original values
            (setq completion-styles ..)))

See Completion Alternatives for more information.

πŸ”Ž 3. Scores lower than 0

Another cause would be the candidate has been eliminated by the scoring engine (it scores lower than 0); hence it would not be shown.

Best way to debug this, is to feed query and candidate to the scoring functions. The following example uses flx:

(flx-score "window-system" "win-sys")  ; return score and it's match data

Another example using the liquidmetal:

(liquidmetal-score "window-system" "win-sys")  ; return score

πŸ’« Why are some variables not respected with special symbols? (@, :, etc)

company-fuzzy detects prefix depends on the Syntax Table . And those special symbols normally doesn't get treated as a whole prefix, hence you should see the completion get inserted incorrectly,

<!-- Try to complete `:link:` emoji -->
:lin

<!-- WRONG, the symbol `:` colon doesn't count as a prefix -->
::link:

How to solve this? You should configure your syntax table by doing something similar to

(add-hook 'markdown-mode-hook (lambda () (modify-syntax-entry ?: "w"))

See related issue #22 (ruby-mode with @ symbol).

πŸ› οΈ Contribute

PRs Welcome Elisp styleguide Donate on paypal Become a patron

If you would like to contribute to this project, you may either clone or make pull requests to this repository. Or you can clone the project and establish your branch of this tool. Any methods are welcome!

πŸ”¬ Development

To run the test locally, you will need the following tools:

Install all dependencies and development dependencies:

$ eask install-deps --dev

To test the package's installation:

$ eask package
$ eask install

To test compilation:

$ eask compile

πŸͺ§ The following steps are optional, but we recommend you follow these lint results!

The built-in checkdoc linter:

$ eask lint checkdoc

The standard package linter:

$ eask lint package

πŸ“ P.S. For more information, find the Eask manual at https://emacs-eask.github.io/.

⚜️ License

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.

See LICENSE for details.