Repository: haorenW1025/completion-nvim Branch: master Commit: 87b0f86da3df Files: 26 Total size: 101.2 KB Directory structure: gitextract_3s7g_cxh/ ├── .github/ │ └── ISSUE_TEMPLATE/ │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .luacheckrc ├── .travis.yml ├── LICENSE ├── README.md ├── autoload/ │ ├── completion.vim │ └── health/ │ └── completion_nvim.vim ├── doc/ │ └── completion-nvim.txt ├── lua/ │ ├── completion/ │ │ ├── chain_completion.lua │ │ ├── complete.lua │ │ ├── health.lua │ │ ├── hover.lua │ │ ├── manager.lua │ │ ├── matching.lua │ │ ├── option.lua │ │ ├── signature_help.lua │ │ ├── source/ │ │ │ ├── ins_complete.lua │ │ │ ├── lsp.lua │ │ │ ├── path.lua │ │ │ └── snippet.lua │ │ ├── source.lua │ │ └── util.lua │ └── completion.lua └── plugin/ └── completion.vim ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- - Please read through [this section](https://github.com/haorenW1025/completion-nvim/wiki/trouble-shooting) before posting a bug report. **My testing minimal init.vim** Post your init.vim to help me reproduce this issue **How to reproduce** Detailed step to reproduce this issue. **Expected behavior** A clear and concise description of what you expected to happen. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. ================================================ FILE: .gitignore ================================================ doc/tags .vim ================================================ FILE: .luacheckrc ================================================ globals = { "vim", } ================================================ FILE: .travis.yml ================================================ language: generic os: - linux before_install: - sudo apt-get update - sudo apt-get install - sudo apt install -y lua5.1 luarocks - sudo luarocks install luacheck jobs: include: - stage: luacheck # build plugin first, then run the test from neovim script: luacheck lua/* os: linux git: depth: 3 ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ ## WARNING: completion.nvim is no longer maintained If you are looking for an autocompletion plugin, the neovim LSP team recommends either [nvim-cmp](https://github.com/hrsh7th/nvim-cmp/) or [coq_nvim](https://github.com/ms-jpq/coq_nvim). [![Build Status](https://travis-ci.com/haorenW1025/completion-nvim.svg?branch=master)](https://travis-ci.com/haorenW1025/completion-nvim) [![Gitter](https://badges.gitter.im/completion-nvim/community.svg)](https://gitter.im/completion-nvim/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) # completion-nvim completion-nvim is an auto completion framework that aims to provide a better completion experience with neovim's built-in LSP. Other LSP functionality is not supported. ## Features - Asynchronous completion using the `libuv` api. - Automatically open hover windows when popupmenu is available. - Automatically open signature help if it's available. - Snippets integration with UltiSnips, Neosnippet, vim-vsnip, and snippets.nvim. - Apply *additionalTextEdits* in LSP spec if it's available. - Chain completion support inspired by [vim-mucomplete](https://github.com/lifepillar/vim-mucomplete) ## Demo Demo using `sumneko_lua` ![](https://user-images.githubusercontent.com/35623968/76489411-3ca1d480-6463-11ea-8c3a-7f0e3c521cdb.gif) ## Prerequisites - Neovim nightly - You should set up your language server of choice with the help of [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) ## Install - Install with any plugin manager by using the path on GitHub. ```vim Plug 'nvim-lua/completion-nvim' ``` ## Setup - completion-nvim requires several autocommands set up to work properly. You should set it up using the `on_attach` function like this. ```vim lua require'lspconfig'.pyls.setup{on_attach=require'completion'.on_attach} ``` - Change `pyls` to whichever language server you're using. - If you want completion-nvim to be set up for all buffers instead of only being used when lsp is enabled, call the `on_attach` function directly: ```vim " Use completion-nvim in every buffer autocmd BufEnter * lua require'completion'.on_attach() ``` *NOTE* It's okay to set up completion-nvim without lsp. It will simply use another completion source instead(Ex: snippets). ## Supported Completion Source - built-in sources * lsp: completion source for neovim's built-in LSP. * snippet: completion source for snippet. * path: completion source for path from current file. - ins-complete sources * See `:h ins-completion` and [wiki](https://github.com/haorenW1025/completion-nvim/wiki/chain-complete-support) - external sources * [completion-buffers](https://github.com/steelsojka/completion-buffers): completion for buffers word. * [completion-treesitter](https://github.com/nvim-treesitter/completion-treesitter): treesitter based completion sources. * [vim-dadbod-completion](https://github.com/kristijanhusak/vim-dadbod-completion): completion sources for `vim-dadbod`. * [completion-tabnine](https://github.com/aca/completion-tabnine): AI code completion tool TabNine integration. * [completion-tags](https://github.com/kristijanhusak/completion-tags): Slightly improved ctags completion * [completion-tmux](https://github.com/albertoCaroM/completion-tmux): tmux panels completion * [completion-vcard](https://github.com/cbarrete/completion-vcard): email completion from vCards ## Configuration ### Recommended Setting ```vim " Use and to navigate through popup menu inoremap pumvisible() ? "\" : "\" inoremap pumvisible() ? "\" : "\" " Set completeopt to have a better completion experience set completeopt=menuone,noinsert,noselect " Avoid showing message extra message when using completion set shortmess+=c ``` ### Enable/Disable auto popup - By default auto popup is enabled, turn it off by ```vim let g:completion_enable_auto_popup = 0 ``` - Or you can toggle auto popup on the fly by using command `CompletionToggle` - You can manually trigger completion with mapping key by ```vim "map to manually trigger completion imap (completion_trigger) ``` - Or you want to use `` as trigger keys ```vim imap (completion_smart_tab) imap (completion_smart_s_tab) ``` ### Enable Snippets Support - By default other snippets source support are disabled, turn them on by ```vim " possible value: 'UltiSnips', 'Neosnippet', 'vim-vsnip', 'snippets.nvim' let g:completion_enable_snippet = 'UltiSnips' ``` - Supports `UltiSnips`, `Neosnippet`, `vim-vsnip` and `snippets.nvim` ### LSP Based Snippet parsing - Some language server have snippet support but neovim couldn't handle that for now, `completion-nvim` can integrate with other LSP snippet parsing plugin for this support. Right now, [vim-vsnip](https://github.com/hrsh7th/vim-vsnip) (requiring [vim-vsnip-integ](https://github.com/hrsh7th/vim-vsnip-integ)) and [snippets.nvim](https://github.com/norcalli/snippets.nvim) are supported. ### Chain Completion Support - completion-nvim supports chain completion, which use other completion sources and `ins-completion` as a fallback for lsp completion. - See [wiki](https://github.com/haorenW1025/completion-nvim/wiki/chain-complete-support) for details on how to set this up. ### Changing Completion Confirm key - By default `` is used to confirm completion and expand snippets, change it by ```vim let g:completion_confirm_key = "\" ``` - Make sure to use `" "` and add escape key `\` to avoid parsing issues. - If the confirm key has a fallback mapping, for example when using the auto pairs plugin, it maps to ``. You can avoid using the default confirm key option and use a mapping like this instead. ```.vim let g:completion_confirm_key = "" imap pumvisible() ? complete_info()["selected"] != "-1" ? \ "\(completion_confirm_completion)" : "\\" : "\" ``` ### Enable/Disable auto hover - By default when navigating through completion items, LSP's hover is automatically called and displays in a floating window. Disable it by ```vim let g:completion_enable_auto_hover = 0 ``` ### Enable/Disable auto signature - By default signature help opens automatically whenever it's available. Disable it by ```vim let g:completion_enable_auto_signature = 0 ``` ### Sorting completion items - You can decide how your items being sorted in the popup menu. The default value is `"alphabet"`, change it by ```vim " possible value: "length", "alphabet", "none" let g:completion_sorting = "length" ``` - If you don't want any sorting, you can set this value to `"none"`. ### Matching Strategy - There are three different kind of matching technique implement in completion-nvim: `substring`, `fuzzy`, `exact` or `all`. - You can specify a list of matching strategy, completion-nvim will loop through the list and assign priority from high to low. For example ```vim let g:completion_matching_strategy_list = ['exact', 'substring', 'fuzzy', 'all'] ``` *NOTE* Fuzzy match highly dependent on what language server you're using. It might not work as you expect on some language server. - You can also enable ignore case matching by ```vim g:completion_matching_ignore_case = 1 ``` - Or smart case matching by ```vim g:completion_matching_smart_case = 1 ``` ### Trigger Characters - By default, `completion-nvim` respect the trigger character of your language server, if you want more trigger characters, add it by ```vim let g:completion_trigger_character = ['.', '::'] ``` *NOTE* use `:lua print(vim.inspect(vim.lsp.buf_get_clients()[1].server_capabilities.completionProvider.triggerCharacters))` to see the trigger character of your language server. - If you want different trigger character for different languages, wrap it in an autocommand like ```vim augroup CompletionTriggerCharacter autocmd! autocmd BufEnter * let g:completion_trigger_character = ['.'] autocmd BufEnter *.c,*.cpp let g:completion_trigger_character = ['.', '::'] augroup end ``` ### Trigger keyword length - You can specify keyword length for triggering completion, if the current word is less then keyword length, completion won't be triggered. ```vim let g:completion_trigger_keyword_length = 3 " default = 1 ``` **NOTE** `completion-nvim` will ignore keyword length if you're on trigger character. ### Trigger on delete - `completion-nvim` doesn't trigger completion on delete by default because sometimes I've found it annoying. However, you can enable it by ```vim let g:completion_trigger_on_delete = 1 ``` ### Timer Adjustment - completion-nvim uses a timer to control the rate of completion. You can adjust the timer rate by ```vim let g:completion_timer_cycle = 200 "default value is 80 ``` ### Per Server Setup - You can have different setup for each server in completion-nvim using lua, see [wiki] (https://github.com/nvim-lua/completion-nvim/wiki/per-server-setup-by-lua) for more guide. ## Trouble Shooting - This plugin is in the early stages and might have unexpected issues. Please follow [wiki](https://github.com/haorenW1025/completion-nvim/wiki/trouble-shooting) for trouble shooting. - Feel free to post issues on any unexpected behavior or open a feature request! ================================================ FILE: autoload/completion.vim ================================================ " Perform a Hack to confirm completion function! completion#completion_confirm() abort lua require'completion'.confirmCompletion() call nvim_feedkeys("\", "n", v:true) endfunction function! completion#wrap_completion() abort if pumvisible() != 0 && complete_info()["selected"] != "-1" call completion#completion_confirm() else call nvim_feedkeys("\\", "n", v:true) let key = g:completion_confirm_key call nvim_feedkeys(key, "n", v:true) endif endfunction " Depracated " Wrapper to get manually trigger working " Please send me a pull request if you know how to do this properly... function! completion#completion_wrapper() lua require'completion'.triggerCompletion() return '' endfunction " Depracated function! completion#trigger_completion() return "\=completion#completion_wrapper()\" endfunction " Depracated " Wrapper of getting buffer variable " Avoid accessing to unavailable variable function! completion#get_buffer_variable(str) return get(b:, a:str, v:null) endfunction function! completion#enable_in_comment() let l:list = g:completion_chain_complete_list if type(l:list) == v:t_dict && has_key(l:list, 'default') \ && type(l:list.default) == v:t_dict \ && has_key(l:list.default, 'comment') call remove(g:completion_chain_complete_list, 'comment') endif endfunction ================================================ FILE: autoload/health/completion_nvim.vim ================================================ function! health#completion_nvim#check() lua require 'completion.health'.checkHealth() endfunction ================================================ FILE: doc/completion-nvim.txt ================================================ *completion-nvim.txt* Async completion framework that aims to provide completion for neovim's built-in LSP, written in Lua. CONTENTS *completion-nvim* 0. Introduction ......... |completion-introduction| 1. Features ............. |completion-feature| 1. Prerequisite ......... |completion-prerequisite| 2. Setup ................ |completion-setup| 3. Options .............. |completion-option| ============================================================================== INTRODUCTION *completion-introduction* completion-nvim is an auto completion framework that aims to provide a better completion experience with neovim's built-in LSP. Other LSP functionality is not supported. ============================================================================== FEATURES *completion-features* - Asynchronous completion using libuv api. - Automatically open hover windows when popupmenu is available. - Automatically open signature help if it's available. - Snippets integration with UltiSnips and Neosnippet and vim-vsnip. - ins-complete method integration - Apply additionalTextEdits in LSP spec if it's available. - Chain completion support inspired by vim-mucomplete ============================================================================== PREREQUISITES *completion-prerequisites* - Neovim 5.0 - You should be setting up language server with the help of nvim-lspconfig ============================================================================== SETUP *completion-setup* - completion-nvim requires several autocommands set up to work properly, you should set it up using the `on_attach` function like this. > lua require'lspconfig'.pyls.setup{on_attach=require'completion'.on_attach} - Change `pyls` to whichever language server you are using. - If you want completion-nvim to be set up for all buffers instead of only being used when lsp is enabled, call the `on_attach` function directly: > " Use completion-nvim in every buffer autocmd BufEnter * lua require'completion'.on_attach() < Note: It's okay to set up completion-nvim without lsp. It will simply use another completion source instead(Ex: snippets). ============================================================================== OPTION *completion-option* g:completion_enable_auto_popup *g:completion_enable_auto_popup* This variable enable automatically popup window for completion. Set this value to 0 if you don't want automatically popup window. If you disable auto popup menu, you can manually trigger completion by mapping keys. For example: > " map to manually trigger completion imap (completion_trigger) < Or you want to use to trigger completion without modifying the usage to keys. > imap (completion_smart_tab) imap (completion_smart_s_tab) < default value: 1 g:completion_enable_snippet *g:completion_enable_snippet* You can specify which snippet engines you want to use. Possible values are |UltiSnips| and |Neosnippet| and |vim-vsnip|. Note: Snippet engines will not work without setting this variables. default value: v:null g:completion_confirm_key *g:completion_confirm_key* You can specify which keys to use for confirm completion(which will select the completion items and expand snippets if available). Note: Make sure to use a proper escape sequence to avoid parsing issues, for example: > " Change confirm key to let g:completion_confirm_key = "\" < default value: "\" If the confirm key has a fallback mapping, for example when using the auto pairs plugin, it maps to ``. You can avoid using the default confirm key option and use a mapping like this instead: > let g:completion_confirm_key = "" imap pumvisible() ? complete_info()["selected"] != "-1" ? \ "\(completion_confirm_completion)" : \ "\\" : "\" < g:completion_enable_auto_hover *g:completion_enable_auto_hover* By default, completion-nvim will automatically open a hover window when you navigate through the complete items(including basic information of snippets). You can turn this off by setting this option to zero. default value: 1 g:completion_enable_auto_signature *g:completion_enable_auto_signature* By default signature help opens automatically whenever it is available. You can turn it off by setting this option to zero. default value: 1 g:completion_popup_border *g:completion_popup_border* This variable sets border for auto hover popup and signature help popup. The variable is not created by default so that there is no border by default. You can set it as per neovim's popup/preview window sepcifications. available options: 'single', 'double', 'rounded', 'solid' and 'shadow' default value: variable not declared g:completion_enable_auto_paren *g:completion_enable_auto_paren* Enable the auto insert parenthesis feature. completion-nvim will insert parenthesis when completing methods or functions. default value: 0 g:completion_trigger_character *g:completion_trigger_character* You can add or disable a trigger character that will trigger completion. For example, disable trigger character: > let g:completion_trigger_character = [] < Or having multiple trigger characters: > let g:completion_trigger_character = ['.', '::'] < Use an autocmd if you want a different trigger character for different languages: > augroup CompletionTriggerCharacter autocmd! autocmd BufEnter * let g:completion_trigger_character = ['.'] autocmd BufEnter *.c,*.cpp let g:completion_trigger_character = ['.', '::'] augroup end < default value: ['.'] g:completion_enable_server_trigger *g:completion_enable_server_trigger* Whether or not to use the trigger characters provided by the language server for triggering the popup menu. You can turn it off by setting this option to zero. default value: 1 g:completion_trigger_keyword_length *g:completion_trigger_keyword_length* You can specify keyword length for triggering completion, if the current word is less than keyword length, completion won't be triggered. Note: completion-nvim will ignore keyword length if you're on trigger character. default value: 1 g:completion_trigger_on_delete *g:completion_trigger_on_delete* completion-nvim doesn't trigger completion on delete by default, as this can be a nuisance. However, you can enable it via: > let g:completion_trigger_on_delete = 1 < g:completion_timer_cycle *g:completion_timer_cycle* completion-nvim uses a timer to control the rate of completion. Adjust the timer rate by setting this value. Note: any values lower than the default is not recommended. default value: 80 g:completion_chain_complete_list *g:completion_chain_complete_list* completion-nvim has chain completion support inspired by vim-mucomplete. In short, you can divide completion sources in groups and have an ins-completion method as backup completion. You can specify different completion list for different filetypes. By default, possible sources are 'lsp', 'snippet', 'path' and various ins-complete sources. Specify 'mode' as your key for ins-complete sources, 'complete_items' for other sources. For example: > let g:completion_chain_complete_list = { \'default' : [ \ {'complete_items': ['lsp', 'snippet']}, \ {'mode': ''}, \ {'mode': ''} \] \} < You can easily switch to next or previous sources by mapping keys in insert mode. For example, using to switch to previous sources and to switch to next sources: > imap (completion_next_source) imap (completion_prev_source) < Customizing your completion sources is easy. For non ins-complete items, you can choose to put them in the same source or separate them. For example, if you want to separate lsp and snippet into two different sources: > let g:completion_chain_complete_list = { \'default' : [ \ {'complete_items': ['lsp']}, \ {'complete_items': ['snippet']}, \ {'mode': ''}, \ {'mode': ''} \] \} < There is a few completion source built in for now, here's a list. > "lsp": lsp completion "snippet": snippet sources based on g:completion_enable_snippet "path": path completion relative to the current file. "UltiSnips": ultisnips source "Neosnippet": neosnippet source "vim-vsnip": vim-vsnip source < For ins-complete sources, possible 'mode' to the actual key in vim are listed below. > "" : i_CTRL-N "" : i_CTRL-P "cmd" : i_CTRL-X_CTRL-V "defs": i_CTRL-X_CTRL-D "dict": i_CTRL-X_CTRL-K "file": i_CTRL-X_CTRL-F "incl": i_CTRL-X_CTRL-I "keyn": i_CTRL-X_CTRL-N "keyp": i_CTRL-X_CTRL-P "omni": i_CTRL-X_CTRL-O "line": i_CTRL-X_CTRL-L "spel": i_CTRL-X_s "tags": i_CTRL-X_CTRL-] "thes": i_CTRL-X_CTRL-T "user": i_CTRL-X_CTRL-U < You can also specify different completion lists for different filetypes, for example: > let g:completion_chain_complete_list = { \ 'vim': [ \ {'mode': ''}, \ {'mode': ''} \], \ 'lua': [ \ {'mode': ''}, \ {'mode': ''} \], \ 'default': [ \ {'complete_items': ['lsp', 'snippet']}, \ {'mode': ''}, \ {'mode': ''} \] \} < You can take a step further to specify different 'scope' of different filetypes. 'scope' is literally syntax in your file. Say that you want different completion lists in comments and function calls, strings, etc, you can do that easily. Here is an example > let g:completion_chain_complete_list = { \ 'lua': [ \ 'string': [ \ {'mode': ''}, \ {'mode': ''}], \ 'func' : [ \ {'complete_items': ['lsp']}], \ 'default': [ \ {'complete_items': ['lsp', 'snippet']}, \ {'mode': ''}, \ {'mode': ''}], \], \ 'default' : { \ 'default': [ \ {'complete_items': ['lsp', 'snippet']}, \ {'mode': ''}, \ {'mode': ''}], \ 'comment': [] \ } \} < For every completion source, you can specify `triggered_only` key. This completion source will only trigger when this key is press. For example: > let g:completion_chain_complete_list = { \ 'default' : { \ 'default': [ \ {'complete_items': ['lsp', 'snippet']}, \ {'complete_items': ['path'], 'triggered_only': ['/']}, \ {'mode': ''}, \ {'mode': ''}], \ 'comment': [] \ } \} < Note: Every syntax highlighter has a different syntax name defined(most of them are similar though). You can check your syntax name under your cursor by this command: > :echo synIDattr(synID(line('.'), col('.'), 1), "name") < You just need to specify a part of a result in the scope since it uses a regex pattern to match it (For example: if the result is 'luaComment' you only need to specified 'comment', case doesn't matter). g:completion_auto_change_source *g:completion_auto_change_source* You can let completion-nvim changes source whenever current source has no complete items by setting this option to 1. default value: 0 g:completion_matching_strategy_list *g:completion_matching_strategy_list* There are three different kind of matching technique implement in completion-nvim: 'substring', 'fuzzy', 'exact' or 'all'. You can specify a list of matching strategy, completion-nvim will loop through the list and assign priority from high to low. For example: > let g:completion_matching_strategy_list = ['exact', 'substring', 'fuzzy', 'all'] < default value: ['exact'] g:completion_matching_ignore_case *g:completion_matching_ignore_case* Enable ignore case matching in all matching strategy. For example: > let g:completion_matching_ignore_case = 1 < default value: &ignorecase g:completion_matching_smart_case *g:completion_matching_smart_case* Enable smart case matching in all matching strategy. For example > let g:completion_matching_smart_case = 1 < default value: &smartcase g:completion_sorting *g:completion_sorting* You can determine how you want to sort the completion items in popup menu. Possible values are 'alphabet', 'length', 'none' default value: 'alphabet' g:completion_abbr_length *g:completion_abbr_length* Some language server have long snippets items, which can make your completion menu super long. This option enable you to truncate item.abbr with a maximum length. default value: 0(which means no truncates) g:completion_menu_length *g:completion_menu_length* Similar to `g:completion_abbr_length`, language server may populate the completion menus with long menu items. This option enable you truncate item.menu with a maximum length. default value: 0(which means no truncates) ============================================================================== vim:tw=78:ts=8:ft=help:norl:noet:fen:noet: ================================================ FILE: lua/completion/chain_completion.lua ================================================ local vim = vim local api = vim.api local util = require 'completion.util' local opt = require 'completion.option' local M ={} -------------------------------------------------------------- -- local function to parse completion_chain_complete_list -- -------------------------------------------------------------- local function chain_list_to_tree(complete_list) if util.is_list(complete_list) then return { default = { default= complete_list } } else local complete_tree = {} for ft, c_list in pairs(complete_list) do if util.is_list(c_list) then complete_tree[ft] = { default=c_list } else complete_tree[ft] = c_list end end -- Be sure that default.default exists if not complete_tree.default then complete_tree.default = { default = { { complete_items={ 'lsp', 'snippet' } } } } end return complete_tree end end local function getScopedChain(ft_subtree) local syntax_getter = function() local pos = api.nvim_win_get_cursor(0) return vim.fn.synIDattr(vim.fn.synID(pos[1], pos[2]-1, 1), "name") end -- If this option is effectively a function, use it to determine syntax group at point local syntax_at_point = opt.get_option("syntax_at_point") if syntax_at_point then if vim.is_callable(syntax_at_point) then syntax_getter = syntax_at_point elseif type(syntax_at_point) == "string" and vim.fn.exists("*" .. syntax_at_point) then syntax_getter = vim.fn[syntax_at_point] end end local atPoint = syntax_getter():lower() for syntax_regex, complete_list in pairs(ft_subtree) do if type(syntax_regex) == "string" and string.match(atPoint, '.*' .. syntax_regex:lower() .. '.*') ~= nil and syntax_regex ~= "default" then return complete_list end end return nil end -- preserve compatiblity of completion_chain_complete_list function M.getChainCompleteList(filetype) local chain_complete_list = chain_list_to_tree(opt.get_option('chain_complete_list')) -- check if chain_complete_list is a array if chain_complete_list[filetype] then return getScopedChain(chain_complete_list[filetype]) or getScopedChain(chain_complete_list.default) or chain_complete_list[filetype].default or chain_complete_list.default.default else return getScopedChain(chain_complete_list.default) or chain_complete_list.default.default end end function M.checkHealth(complete_items_map) local completion_list = vim.g.completion_chain_complete_list local health_ok = vim.fn['health#report_ok'] local health_error = vim.fn['health#report_error'] local error = false for filetype, _ in pairs(completion_list) do local chain_complete_list if filetype ~= 'default' then chain_complete_list = M.getChainCompleteList(filetype) else chain_complete_list = getScopedChain(completion_list.default) or completion_list.default.default end if chain_complete_list ~= nil then for _,complete_source in ipairs(chain_complete_list) do if vim.fn.has_key(complete_source, "complete_items") > 0 then for _,item in ipairs(complete_source.complete_items) do if complete_items_map[item] == nil then health_error(item.." is not a valid completion source (in filetype "..filetype..")") error = true end end else local ins = require 'completion.source.ins_complete' if ins.checkHealth(complete_source.mode) then health_error(complete_source.mode.." is not a valid insert completion mode") end end end end end if not error then health_ok("all completion sources are valid") end end return M ================================================ FILE: lua/completion/complete.lua ================================================ local vim = vim local api = vim.api local util = require 'completion.util' local ins = require 'completion.source.ins_complete' local match = require'completion.matching' local lsp = require'completion.source.lsp' local opt = require 'completion.option' local manager = require 'completion.manager' local M = {} local cache_complete_items = {} local function checkCallback(callback_array) for _,val in ipairs(callback_array) do if not val then return false end if type(val) == 'function' then if val() == false then return end end end return true end local function getCompletionItems(items_array, prefix) local complete_items = {} for _,func in ipairs(items_array) do vim.list_extend(complete_items, func(prefix)) end return complete_items end M.clearCache = function() cache_complete_items = {} lsp.isIncomplete = true end -- perform completion M.performComplete = function(complete_source, complete_items_map, params) manager.insertChar = false if vim.fn.has_key(complete_source, "mode") > 0 then -- ins-complete source ins.triggerCompletion(complete_source.mode) elseif vim.fn.has_key(complete_source, "complete_items") > 0 then local callback_array = {} local items_array = {} -- collect getCompleteItems function of current completion source for _, item in ipairs(complete_source.complete_items) do -- check isIncomplete for lsp local complete_items = complete_items_map[item] -- special case to handle lsp isIncomplete flag if item == 'lsp' then if lsp.isIncomplete then cache_complete_items = {} table.insert(callback_array, complete_items.callback) complete_items.trigger(manager, params) table.insert(items_array, complete_items.item) end else if complete_items ~= nil then if complete_items.callback == nil then table.insert(callback_array, true) else table.insert(callback_array, complete_items.callback) -- TODO: still pass in manager here because there's external sources using it -- will remove it when refactoring aysnc sources complete_items.trigger(manager, params) end table.insert(items_array, complete_items.item) end end end if #cache_complete_items == 0 then -- use callback_array to handle async behavior local timer = vim.loop.new_timer() timer:start(20, 50, vim.schedule_wrap(function() if manager.insertChar == true and not timer:is_closing() then timer:stop() timer:close() end -- only perform complete when callback_array are all true if checkCallback(callback_array) == true and timer:is_closing() == false then if api.nvim_get_mode()['mode'] == 'i' or api.nvim_get_mode()['mode'] == 'ic' then local items = getCompletionItems(items_array, params.prefix) if opt.get_option('sorting') ~= "none" then util.sort_completion_items(items) end if #items ~= 0 then -- reset insertChar and handle auto changing source cache_complete_items = items vim.fn.complete(params.textMatch+1, items) manager.changeSource = false else manager.changeSource = true end end timer:stop() timer:close() end end)) else if api.nvim_get_mode()['mode'] == 'i' or api.nvim_get_mode()['mode'] == 'ic' then local items = {} for _, item in ipairs(cache_complete_items) do match.matching(items, params.prefix, item) end if opt.get_option('sorting') ~= "none" then util.sort_completion_items(items) end if #items ~= 0 then local matching_strategy = opt.get_option("matching_strategy_list") -- don't re-trigger complete when exact matching to avoid flickering -- reset insertChar and handle auto changing source cache_complete_items = items if #matching_strategy == 1 and matching_strategy[1] == 'exact' then return else vim.fn.complete(params.textMatch+1, items) end manager.changeSource = false else cache_complete_items = {} manager.changeSource = true end end end end end return M ================================================ FILE: lua/completion/health.lua ================================================ local vim = vim local api = vim.api local source = require 'completion.source' local opt = require 'completion.option' local health_start = vim.fn["health#report_start"] local health_ok = vim.fn['health#report_ok'] local health_info = vim.fn['health#report_info'] local health_error = vim.fn['health#report_error'] local M = {} local checkCompletionSource = function() source.checkHealth() end local checkSnippetSource = function() local snippet_source = opt.get_option('enable_snippet') if snippet_source == nil then health_info("You haven't set up any snippet source") else local rtp = string.lower(api.nvim_get_option("rtp")) local unknown_snippet_source = true local snippet_sources = { ["UltiSnips"] = "ultisnips", ["Neosnippet"] = "neosnippet.vim", ["vim-vsnip"] = "vsnip", ["snippets.nvim"] = "snippets.nvim" } for k,v in pairs(snippet_sources) do if snippet_source == k then unknown_snippet_source = false if string.match(rtp, ".*"..v..".*") then health_ok("You are using "..k.." as your snippet source") else health_error(k.." is not available! Check if you installed "..k.." correctly") end break end end if unknown_snippet_source then health_error("Your snippet source is not available! Possible values are: UltiSnips, Neosnippet, vim-vsnip, snippets.nvim") end end end function M.checkHealth() health_start("general") if vim.tbl_filter == nil then health_error("vim.tbl_filter is not found!", {'consider recompiling neovim from the latest master branch'}) else health_ok("neovim version is supported") end health_start("completion source") checkCompletionSource() health_start("snippet source") checkSnippetSource() end return M ================================================ FILE: lua/completion/hover.lua ================================================ -- define some hover related function modified from neovim source code local vim = vim local validate = vim.validate local api = vim.api local opt = require 'completion.option' local manager = require 'completion.manager' local M = {} local function ok_or_nil(status, ...) if not status then return end return ... end local function npcall(fn, ...) return ok_or_nil(pcall(fn, ...)) end local function find_window_by_var(name, value) for _, win in ipairs(api.nvim_list_wins()) do if npcall(api.nvim_win_get_var, win, name) == value then return win end end end M.focusable_float = function(unique_name, fn) if npcall(api.nvim_win_get_var, 0, unique_name) then return api.nvim_command("wincmd p") end local bufnr = api.nvim_get_current_buf() do local win = find_window_by_var(unique_name, bufnr) if win then api.nvim_win_close(win, true) end end local pbufnr, pwinnr = fn() if pbufnr then api.nvim_win_set_var(pwinnr, unique_name, bufnr) return pbufnr, pwinnr end end --------------------------------- -- floating window for hover -- --------------------------------- local make_floating_popup_options = function(width, height, opts) validate { opts = { opts, 't', true }; } opts = opts or {} validate { ["opts.offset_x"] = { opts.offset_x, 'n', true }; ["opts.offset_y"] = { opts.offset_y, 'n', true }; } local lines_above = vim.fn.winline() - 1 local lines_below = vim.fn.winheight(0) - lines_above local col if lines_above < lines_below then height = math.min(lines_below, height) else height = math.min(lines_above, height) end if opts.align == 'right' then col = opts.col + opts.width else col = opts.col - width - 1 end local default_border = { {"", "NormalFloat"}, {"", "NormalFloat"}, {"", "NormalFloat"}, {" ", "NormalFloat"}, {"", "NormalFloat"}, {"", "NormalFloat"}, {"", "NormalFloat"}, {" ", "NormalFloat"}, } return { col = col, height = height, relative = 'editor', row = opts.row, focusable = false, style = 'minimal', width = width, border = vim.g.completion_popup_border or default_border } end local fancy_floating_markdown = function(contents, opts) local pad_left = opts and opts.pad_left local pad_right = opts and opts.pad_right local stripped = {} local highlights = {} local max_width if opts.align == 'right' then local columns = api.nvim_get_option('columns') max_width = columns - opts.col - opts.width else max_width = opts.col - 1 end do local i = 1 while i <= #contents do local line = contents[i] local ft = line:match("^```([a-zA-Z0-9_]*)$") if ft then local start = #stripped i = i + 1 while i <= #contents do line = contents[i] if line == "```" then i = i + 1 break end if #line > max_width then while #line > max_width do local trimmed_line = string.sub(line, 1, max_width) local index = trimmed_line:reverse():find(" ") if index == nil or index > #trimmed_line/2 then break else table.insert(stripped, string.sub(line, 1, max_width-index)) line = string.sub(line, max_width-index+2, #line) end end table.insert(stripped, line) else table.insert(stripped, line) end i = i + 1 end table.insert(highlights, { ft = ft; start = start + 1; finish = #stripped + 1 - 1 }) else if #line > max_width then while #line > max_width do local trimmed_line = string.sub(line, 1, max_width) -- local index = math.max(trimmed_line:reverse():find(" "), trimmed_line:reverse():find("/")) local index = trimmed_line:reverse():find(" ") if index == nil or index > #trimmed_line/2 then break else table.insert(stripped, string.sub(line, 1, max_width-index)) line = string.sub(line, max_width-index+2, #line) end end table.insert(stripped, line) else table.insert(stripped, line) end i = i + 1 end end end local width = 0 for i, v in ipairs(stripped) do v = v:gsub("\r", "") if pad_left then v = (" "):rep(pad_left)..v end if pad_right then v = v..(" "):rep(pad_right) end stripped[i] = v width = math.max(width, #v) end if opts.align == 'right' then local columns = api.nvim_get_option('columns') if opts.col + opts.row + width > columns then width = columns - opts.col - opts.width -1 end else if width > opts.col then width = opts.col - 1 end end local insert_separator = true if insert_separator then for i, h in ipairs(highlights) do h.start = h.start + i - 1 h.finish = h.finish + i - 1 if h.finish + 1 <= #stripped then table.insert(stripped, h.finish + 1, string.rep("─", width)) end end end -- Make the floating window. local height = #stripped local bufnr = api.nvim_create_buf(false, true) local winnr if opt.get_option('docked_hover') == 1 then if height > opt.get_option('docked_maximum_size') then height = opt.get_option('docked_maximum_size') elseif height < opt.get_option('docked_minimum_size') then height = opt.get_option('docked_minimum_size') end local row if vim.fn.winline() > api.nvim_get_option('lines')/2 then row = 0 else row = api.nvim_get_option('lines') - height end winnr = api.nvim_open_win(bufnr, false, { col = 0, height = height, relative = 'editor', row = row, focusable = true, style = 'minimal', width = api.nvim_get_option('columns'), }) else local opt = make_floating_popup_options(width, height, opts) if opt.width <= 0 then return end winnr = api.nvim_open_win(bufnr, false, opt) end vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, stripped) -- setup a variable for floating window, fix #223 vim.api.nvim_buf_set_var(bufnr, "lsp_floating", true) local cwin = vim.api.nvim_get_current_win() vim.api.nvim_set_current_win(winnr) vim.cmd("ownsyntax markdown") local idx = 1 local function highlight_region(ft, start, finish) if ft == '' then return end local name = ft..idx idx = idx + 1 local lang = "@"..ft:upper() -- TODO(ashkan): better validation before this. if not pcall(vim.cmd, string.format("syntax include %s syntax/%s.vim", lang, ft)) then return end vim.cmd(string.format("syntax region %s start=+\\%%%dl+ end=+\\%%%dl+ contains=%s", name, start, finish + 1, lang)) end for _, h in ipairs(highlights) do highlight_region(h.ft, h.start, h.finish) end vim.api.nvim_set_current_win(cwin) return bufnr, winnr end local function handler_function(_, method, result) if vim.fn.pumvisible() == 1 then M.focusable_float(method, function() if not (result and result.contents) then -- return { 'No information available' } return end local markdown_lines = vim.lsp.util.convert_input_to_markdown_lines(result.contents) markdown_lines = vim.lsp.util.trim_empty_lines(markdown_lines) if vim.tbl_isempty(markdown_lines) then -- return { 'No information available' } return end local bufnr, winnr -- modified to open hover window align to popupmenu local position = vim.fn.pum_getpos() -- Set max width option to avoid overlapping with popup menu local total_column = api.nvim_get_option('columns') local align local col = position['col'] if position['col'] < total_column/2 then align = 'right' if position['scrollbar'] then col = col + 1 end else align = 'left' end bufnr, winnr = fancy_floating_markdown(markdown_lines, { pad_left = 0; pad_right = 1; col = col; width = position['width']; row = position['row']-1; align = align }) M.winnr = winnr if winnr ~= nil and api.nvim_win_is_valid(winnr) then vim.lsp.util.close_preview_autocmd({"CursorMoved", "BufHidden", "InsertCharPre"}, winnr) end local hover_len = #vim.api.nvim_buf_get_lines(bufnr,0,-1,false)[1] local win_width = vim.api.nvim_win_get_width(0) if hover_len > win_width then vim.api.nvim_win_set_width(winnr,math.min(hover_len,win_width)) vim.api.nvim_win_set_height(winnr,math.ceil(hover_len/win_width)) vim.wo[winnr].wrap = true end return bufnr, winnr end) end end M.autoOpenHoverInPopup = function() if vim.fn.pumvisible() ~= 1 then return end local bufnr = api.nvim_get_current_buf() if api.nvim_call_function('pumvisible', {}) == 1 then -- Auto open hover local items = api.nvim_call_function('complete_info', {{"eval", "selected", "items", "user_data"}}) if items['selected'] ~= manager.selected then manager.textHover = true if M.winnr ~= nil and api.nvim_win_is_valid(M.winnr) then api.nvim_win_close(M.winnr, true) end M.winnr = nil end if manager.textHover == true and items['selected'] ~= -1 then if items['selected'] == -2 then items['selected'] = 0 end local item = items['items'][items['selected']+1] local user_data = item['user_data'] if user_data ~= nil and #user_data ~= 0 then user_data = vim.fn.json_decode(item['user_data']) end if user_data ~= nil and user_data['lsp'] == nil then if user_data['hover'] ~= nil and type(user_data['hover']) == 'string' and #user_data['hover'] ~= 0 then local markdown_lines = vim.lsp.util.convert_input_to_markdown_lines(user_data['hover']) markdown_lines = vim.lsp.util.trim_empty_lines(markdown_lines) local position = vim.fn.pum_getpos() -- Set max width option to avoid overlapping with popup menu local total_column = api.nvim_get_option('columns') local align if position['col'] < total_column/2 then align = 'right' else align = 'left' end local _, winnr = fancy_floating_markdown(markdown_lines, { pad_left = 1; pad_right = 1; col = position['col']; width = position['width']; row = position['row']-1; align = align }) M.winnr = winnr end else local has_hover = false for _, value in pairs(vim.lsp.buf_get_clients(0)) do if value.resolved_capabilities.hover then has_hover = true break end end if not has_hover then return end local row, col = unpack(api.nvim_win_get_cursor(0)) row = row - 1 local line = api.nvim_buf_get_lines(0, row, row+1, true)[1] col = vim.str_utfindex(line, col) local params = { textDocument = vim.lsp.util.make_text_document_params(); position = { line = row; character = col-string.len(item.word); } } vim.lsp.buf_request(bufnr, 'textDocument/hover', params, handler_function) end manager.textHover = false end manager.selected = items['selected'] end end return M ================================================ FILE: lua/completion/manager.lua ================================================ local manager = {} ------------------------------------------------------------------------ -- plugin variables and states -- ------------------------------------------------------------------------ -- Global variables table, accessed in scripts as manager.variable_name manager = { -- not used for now... -- canTryCompletion = true, -- chains = {}, -- here we store validated chains for each buffer -- activeChain = nil, -- currently used completion chain insertChar = false, -- flag for InsertCharPre event, turn off imediately when performing completion insertLeave = false, -- flag for InsertLeave, prevent every completion if true textHover = false, -- handle auto hover selected = -1, -- handle selected items in v:complete-items for auto hover changedTick = 0, -- handle changeTick confirmedCompletion = false, -- flag for manual confirmation of completion forceCompletion = false, -- flag for forced manual completion/source change chainIndex = 1, -- current index in loaded chain } -- reset manager -- called on insertEnter function manager.init() -- manager.activeChain = nil manager.insertLeave = false -- manager.canTryCompletion = true manager.insertChar = false manager.textHover = false manager.selected = -1 manager.confirmedCompletion = false manager.forceCompletion = false manager.chainIndex = 1 end -- TODO: change this when we have proper logger function manager.debug() print( 'canTryCompletion = ' .. vim.inspect(manager.canTryCompletion) .. '\n' .. 'insertChar = ' .. vim.inspect(manager.insertChar) .. '\n' .. 'insertLeave = ' .. vim.inspect(manager.insertLeave) .. '\n' .. 'textHover = ' .. vim.inspect(manager.textHover) .. '\n' .. 'selected = ' .. vim.inspect(manager.selected) .. '\n' .. 'changedTick = ' .. vim.inspect(manager.changedTick) .. '\n' .. 'confirmedCompletion = ' .. vim.inspect(manager.confirmedCompletion) .. '\n' .. 'forceCompletion = ' .. vim.inspect(manager.forceCompletion) .. '\n' .. 'chainIndex = ' .. vim.inspect(manager.chainIndex) ) end return manager ================================================ FILE: lua/completion/matching.lua ================================================ local vim = vim local util = require 'completion.util' local opt = require 'completion.option' local M = {} local function setup_case(prefix, word) local ignore_case = opt.get_option('matching_ignore_case') == 1 if ignore_case and opt.get_option('matching_smart_case') == 1 and prefix:match('[A-Z]') then ignore_case = false end if ignore_case then return string.lower(prefix), string.lower(word) end return prefix, word end local function fuzzy_match(prefix, word) prefix, word = setup_case(prefix, word) local score = util.fuzzy_score(prefix, word) if score < 1 then return true, score else return false end end local function substring_match(prefix, word) prefix, word = setup_case(prefix, word) if string.find(word, prefix, 1, true) then return true else return false end end local function exact_match(prefix, word) prefix, word = setup_case(prefix, word) if vim.startswith(word, prefix) then return true else return false end end local function all_match() return true end local matching_strategy = { fuzzy = fuzzy_match, substring = substring_match, exact = exact_match, all = all_match, } M.matching = function(complete_items, prefix, item) local matcher_list = opt.get_option('matching_strategy_list') local matching_priority = 2 for _, method in ipairs(matcher_list) do local is_match, score = matching_strategy[method](prefix, item.word) if is_match then if item.abbr == nil then item.abbr = item.word end item.score = score if item.priority ~= nil then item.priority = item.priority + 10*matching_priority else item.priority = 10*matching_priority end util.addCompletionItems(complete_items, item) break end matching_priority = matching_priority - 1 end end return M ================================================ FILE: lua/completion/option.lua ================================================ local M = {} -- fallback to using global variable as default local completion_opt_metatable = { __index = function(_, key) key = 'completion_'..key return vim.g[key] end } local option_table = setmetatable({}, completion_opt_metatable) M.set_option_table = function(opt) if opt ~= nil then option_table = setmetatable(opt, completion_opt_metatable) else option_table = setmetatable({}, completion_opt_metatable) end end M.get_option = function(opt) return option_table[opt] end return M ================================================ FILE: lua/completion/signature_help.lua ================================================ local vim = vim local validate = vim.validate local api = vim.api local util = require 'completion.util' local M = {} ---------------------- -- signature help -- ---------------------- M.autoOpenSignatureHelp = function() local pos = api.nvim_win_get_cursor(0) local line = api.nvim_get_current_line() local line_to_cursor = line:sub(1, pos[2]) if vim.lsp.buf_get_clients() == nil then return end local triggered for _, value in pairs(vim.lsp.buf_get_clients(0)) do if value.resolved_capabilities.signature_help == false or value.server_capabilities.signatureHelpProvider == nil then return end line_to_cursor = vim.trim(line_to_cursor) triggered = util.checkTriggerCharacter(line_to_cursor, value.server_capabilities.signatureHelpProvider.triggerCharacters) end if triggered then -- overwrite signature help here to disable "no signature help" message local params = vim.lsp.util.make_position_params() local filetype = vim.api.nvim_buf_get_option(0, 'filetype') vim.lsp.buf_request(0, 'textDocument/signatureHelp', params, function(err, method, result, client_id) local client = vim.lsp.get_client_by_id(client_id) local handler = client and client.handlers['textDocument/signatureHelp'] if handler then handler(err, method, result, client_id) return end if not (result and result.signatures and result.signatures[1]) then return end local lines = vim.lsp.util.convert_signature_help_to_markdown_lines(result, filetype) if vim.tbl_isempty(lines) then return end -- if `lines` can be trimmed, it is modified in place local trimmed_lines_filetype = vim.lsp.util.try_trim_markdown_code_blocks(lines) local opts = {} if vim.g.completion_popup_border then opts.border = vim.g.completion_popup_border end local bufnr, _ = vim.lsp.util.open_floating_preview( -- TODO show popup when signatures is empty? vim.lsp.util.trim_empty_lines(lines), trimmed_lines_filetype, opts ) -- setup a variable for floating window, fix #223 vim.api.nvim_buf_set_var(bufnr, "lsp_floating", true) end) end end return M ================================================ FILE: lua/completion/source/ins_complete.lua ================================================ -- luacheck: globals vim local vim = vim local api = vim.api local manager = require 'completion.manager' local M = {} local ins_complete_table = { ['line'] = "", ['cmd'] = "", ['defs'] = "", ['dict'] = "", ['file'] = "", ['incl'] = "", ['keyn'] = "", ['keyp'] = "", ['omni'] = "", ['spel'] = "s", ['tags'] = "", ['thes'] = "", ['user'] = "", [''] = "", [''] = "", } -- HACK workaround to handle delay of ins-complete local checkEmptyCompletion = function() local timer = vim.loop.new_timer() timer:start(200, 0, vim.schedule_wrap(function() if vim.fn.pumvisible() == 0 then manager.changeSource = true else manager.changeSource = false end timer:stop() timer:close() end)) end M.checkHealth = function(mode) if ins_complete_table[mode] == nil then return false end end M.triggerCompletion = function(mode) if ins_complete_table[mode] == nil then return end if vim.fn.pumvisible() == 0 then if vim.api.nvim_get_mode()['mode'] == 'i' or vim.api.nvim_get_mode()['mode'] == 'ic' then local mode_keys = ins_complete_table[mode] -- See https://github.com/neovim/neovim/issues/12297. mode_keys = api.nvim_replace_termcodes(mode_keys, true, false, true) api.nvim_feedkeys(mode_keys, 'n', true) checkEmptyCompletion() end else manager.insertChar = false end end return M ================================================ FILE: lua/completion/source/lsp.lua ================================================ local vim = vim local protocol = require 'vim.lsp.protocol' local util = require 'completion.util' local match = require 'completion.matching' local opt = require 'completion.option' local M = {} M.callback = false M.isIncomplete = true M.getCompletionItems = function(_, _) return M.items end local function sort_completion_items(items) table.sort(items, function(a, b) return (a.sortText or a.label) < (b.sortText or b.label) end) end local function get_completion_word(item, prefix, suffix) if item.textEdit ~= nil and item.textEdit ~= vim.NIL and item.textEdit.newText ~= nil and (item.insertTextFormat ~= 2 or vim.fn.exists('g:loaded_vsnip_integ')) then local start_range = item.textEdit.range["start"] local end_range = item.textEdit.range["end"] local newText if start_range.line == end_range.line and start_range.character == end_range.character then newText = prefix .. item.textEdit.newText else newText = item.textEdit.newText end if not item.insertTextFormat or protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" or opt.get_option('enable_snippet') == "snippets.nvim" then return newText else return vim.lsp.util.parse_snippet(newText) end elseif item.insertText ~= nil and item.insertText ~= vim.NIL then if not item.insertTextFormat or protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" or opt.get_option('enable_snippet') == "snippets.nvim" then return item.insertText else return vim.lsp.util.parse_snippet(item.insertText) end end return item.label end local function get_context_aware_snippets(item, completion_item, line_to_cursor) if protocol.InsertTextFormat[completion_item.insertTextFormat] == "PlainText" then return end local line = vim.api.nvim_get_current_line() local nextWord = line:sub(#line_to_cursor+1, #line_to_cursor+1) if #nextWord == 0 then return end for _,ch in ipairs(vim.g.completion_expand_characters) do if nextWord == ch then return end end item.user_data = {} local matches, word word, matches = item.word:gsub("%(.*%)$", "") if matches == 0 then word, matches = item.word:gsub("<.*>$", "") end if matches ~= 0 then item.word = word end end local function text_document_completion_list_to_complete_items(result, params) local items = vim.lsp.util.extract_completion_items(result) if vim.tbl_isempty(items) then return {} end local customize_label = opt.get_option('customize_lsp_label') -- items = remove_unmatch_completion_items(items, prefix) sort_completion_items(items) local matches = {} for _, completion_item in ipairs(items) do local item = {} local info = ' ' local documentation = completion_item.documentation if documentation then if type(documentation) == 'string' and documentation ~= '' then info = documentation elseif type(documentation) == 'table' and type(documentation.value) == 'string' then info = documentation.value end end item.info = info item.word = get_completion_word(completion_item, params.prefix, params.suffix) item.word = item.word:gsub('\n', ' ') item.word = vim.trim(item.word) item.dup = opt.get_option("items_duplicate")['lsp'] item.user_data = { lsp = { completion_item = completion_item, } } if protocol.InsertTextFormat[completion_item.insertTextFormat] == 'Snippet' and opt.get_option('enable_snippet') == "snippets.nvim" then item.user_data.actual_item = item.word item.word = vim.trim(completion_item.label) end local kind = protocol.CompletionItemKind[completion_item.kind] item.kind = customize_label[kind] or kind item.abbr = vim.trim(completion_item.label) if params.suffix ~= nil and #params.suffix ~= 0 then local index = item.word:find(params.suffix) if index ~= nil then local newWord = item.word newWord = newWord:sub(1, index-1) item.word = newWord item.user_data = {} end end get_context_aware_snippets(item, completion_item, params.line_to_cursor) item.priority = opt.get_option('items_priority')[item.kind] or opt.get_option('items_priority')[kind] item.menu = completion_item.detail or '' match.matching(matches, params.prefix, item) end return matches end M.getCallback = function() return M.callback end M.triggerFunction = function(_, params) local position_param = vim.lsp.util.make_position_params() M.callback = false M.items = {} if vim.tbl_isempty(vim.lsp.buf_get_clients()) then M.callback = true return end vim.lsp.buf_request(params.bufnr, 'textDocument/completion', position_param, function(err, _, result) if err or not result then M.callback = true return end local matches = text_document_completion_list_to_complete_items(result, params) M.items = matches M.isIncomplete = result.isIncomplete M.callback = true end) end return M ================================================ FILE: lua/completion/source/path.lua ================================================ local M = {} local vim = vim local loop = vim.loop local api = vim.api local util = require 'completion.util' local opt = require 'completion.option' M.items = {} M.callback = false -- onDirScanned handler for vim.loop local function onDirScanned(_, data) if data then local function iter() return vim.loop.fs_scandir_next(data) end for name, type in iter do table.insert(M.items, {type = type, name=name}) end end M.callback = true end local fileTypesMap = setmetatable({ file = "(file)", directory = "(dir)", char = "(char)", link = "(link)", block = "(block)", fifo = "(pipe)", socket = "(socket)" }, {__index = function() return '(unknown)' end }) M.getCompletionItems = function(prefix) local complete_items = {} local kind = 'Path' kind = opt.get_option('customize_lsp_label')[kind] or kind for _, val in ipairs(M.items) do local score = util.fuzzy_score(prefix, val.name) if score < #prefix/3 or #prefix == 0 then table.insert(complete_items, { word = val.name, kind = kind, menu = fileTypesMap[val.type], score = score, icase = 1, dup = 1, empty = 1, }) end end return complete_items end M.getCallback = function() return M.callback end M.triggerFunction = function(_, opt) local keyword if vim.v.completed_item ~= nil and vim.v.completed_item.kind == 'Path' and opt.line_to_cursor:find(vim.v.completed_item.word) then keyword = M.keyword..vim.v.completed_item.word..'/' else M.keyword = nil keyword = opt.line_to_cursor:match("[^%s\"\']+%S*/?$") end if keyword ~= nil and keyword ~= '/' then local index = string.find(keyword:reverse(), '/') if index == nil then index = keyword:len() + 1 end local length = string.len(keyword) - index + 1 keyword = string.sub(keyword, 1, length) end local path = vim.fn.expand('%:p:h') if keyword ~= nil then local expanded_keyword = vim.fn.glob(keyword) local home = vim.fn.expand("$HOME") if expanded_keyword:sub(1, 1) == '/' or string.find(expanded_keyword, home) ~= nil then path = expanded_keyword else path = vim.fn.expand('%:p:h') path = path..'/'..keyword end end M.keyword = keyword M.items = {} loop.fs_scandir(path, onDirScanned) end return M ================================================ FILE: lua/completion/source/snippet.lua ================================================ local vim = vim local api = vim.api local match = require'completion.matching' local opt = require 'completion.option' local M = {} M.getUltisnipItems = function(prefix) if vim.fn.exists("*UltiSnips#SnippetsInCurrentScope") == 0 then return {} end local snippetsList = vim.call('UltiSnips#SnippetsInCurrentScope') local complete_items = {} if vim.tbl_isempty(snippetsList) then return {} end local priority = vim.g.completion_items_priority['UltiSnips'] or 1 local kind = 'UltiSnips' local dup = opt.get_option('items_duplicate')[kind] or 1 kind = opt.get_option('customize_lsp_label')[kind] or kind for key, val in pairs(snippetsList) do local item = {} item.word = key item.kind = kind item.priority = priority item.dup = dup local user_data = {snippet_source = 'UltiSnips', hover = val} item.user_data = user_data match.matching(complete_items, prefix, item) end return complete_items end M.getNeosnippetItems = function(prefix) if vim.fn.exists("*neosnippet#helpers#get_completion_snippets") == 0 then return {} end local snippetsList = vim.call('neosnippet#helpers#get_completion_snippets') local complete_items = {} if vim.tbl_isempty(snippetsList) == 0 then return {} end local kind = 'Neosnippet' kind = opt.get_option('customize_lsp_label')[kind] or kind local dup = opt.get_option('items_duplicate')[kind] or 1 local priority = vim.g.completion_items_priority['Neosnippet'] for key, val in pairs(snippetsList) do local description if val == nil or type(val) ~= "table" then description = nil else description = val.description end local user_data = {snippet_source = 'Neosnippet', hover = description} local item = {} item.word = key item.kind = kind item.priority = priority item.dup = dup item.user_data = user_data match.matching(complete_items, prefix, item) end return complete_items end M.getVsnipItems = function(prefix) if vim.fn.exists('g:loaded_vsnip') == 0 then return {} end local snippetsList = api.nvim_call_function('vsnip#source#find', {api.nvim_get_current_buf()}) local complete_items = {} if vim.tbl_isempty(snippetsList) == 0 then return {} end local kind = 'vim-vsnip' kind = opt.get_option('customize_lsp_label')[kind] or kind local priority = vim.g.completion_items_priority['vim-vsnip'] local dup = opt.get_option('items_duplicate')[kind] or 1 for _, source in pairs(snippetsList) do for _, snippet in pairs(source) do for _, word in pairs(snippet.prefix) do local user_data = {snippet_source = 'vim-vsnip', snippet_body = snippet.body, hover = snippet.description} local item = {} item.word = word item.kind = kind item.menu = snippet.label item.dup = dup item.priority = priority item.user_data = user_data match.matching(complete_items, prefix, item) end end end return complete_items end -- Cribbed almost wholesale from snippets.lookup_snippet() M.getSnippetsNvimItems = function(prefix) local snippets = require 'snippets' if not snippets then return {} end local ft = vim.bo.filetype local snippetsList = vim.tbl_extend('force', snippets.snippets._global or {}, snippets.snippets[ft] or {}) local complete_items = {} if vim.tbl_isempty(snippetsList) == 0 then return {} end local priority = vim.g.completion_items_priority['snippets.nvim'] or 1 local kind = 'snippets.nvim' local dup = opt.get_option('items_duplicate')[kind] or 1 kind = opt.get_option('customize_lsp_label')[kind] or kind for short, long in pairs(snippetsList) do -- TODO: We cannot put the parsed snippet itself in userdata, since it may -- contain Lua functions (see -- https://github.com/norcalli/snippets.nvim#notes-because-this-is-beta-release-software) local user_data = {snippet_source = 'snippets.nvim'} local item = {} item.word = short item.kind = kind item.dup = dup -- TODO: Turn actual snippet text into label/description? item.menu = short item.priority = priority item.user_data = user_data match.matching(complete_items, prefix, item) end return complete_items end M.getCompletionItems = function(prefix) local source = opt.get_option('enable_snippet') local snippet_list = {} if source == 'UltiSnips' then snippet_list = M.getUltisnipItems(prefix) elseif source == 'Neosnippet' then snippet_list = M.getNeosnippetItems(prefix) elseif source == 'vim-vsnip' then snippet_list = M.getVsnipItems(prefix) elseif source == 'snippets.nvim' then snippet_list = M.getSnippetsNvimItems(prefix) end return snippet_list end return M ================================================ FILE: lua/completion/source.lua ================================================ local vim = vim local api = vim.api local util = require 'completion.util' local complete = require 'completion.complete' local chain_completion = require 'completion.chain_completion' local lsp = require 'completion.source.lsp' local snippet = require 'completion.source.snippet' local path = require 'completion.source.path' local opt = require 'completion.option' local manager = require 'completion.manager' local M = {} local complete_items_map = { ['lsp'] = { trigger = lsp.triggerFunction, callback = lsp.getCallback, item = lsp.getCompletionItems }, ['snippet'] = { item = snippet.getCompletionItems }, ['path'] = { item = path.getCompletionItems, callback = path.getCallback, trigger = path.triggerFunction, trigger_character = {'/'} }, ['UltiSnips'] = { item = snippet.getUltisnipItems }, ['vim-vsnip'] = { item = snippet.getVsnipItems }, ['Neosnippet'] = { item = snippet.getNeosnippetItems }, ['snippets.nvim'] = { item = snippet.getSnippetsNvimItems } } M.prefixLength = 0 M.stop_complete = false ------------------------------------------------------------------------ -- local function -- ------------------------------------------------------------------------ local getTriggerCharacter = function() local triggerCharacter = {} local complete_source = M.chain_complete_list[manager.chainIndex] if complete_source ~= nil and vim.fn.has_key(complete_source, "complete_items") > 0 then for _, item in ipairs(complete_source.complete_items) do local complete_items = complete_items_map[item] if complete_items ~= nil and complete_items.trigger_character ~= nil then for _,val in ipairs(complete_items.trigger_character) do table.insert(triggerCharacter, val) end end end end return triggerCharacter end local triggerCurrentCompletion = function(bufnr, line_to_cursor, prefix, textMatch, suffix, force) -- avoid rebundant calling of completion if manager.insertChar == false then return end -- get current completion source M.chain_complete_list = chain_completion.getChainCompleteList(api.nvim_buf_get_option(0, 'filetype')) M.chain_complete_length = #M.chain_complete_list local complete_source = M.chain_complete_list[manager.chainIndex] if complete_source == nil then return end -- handle source trigger character and user defined trigger character local source_trigger_character = getTriggerCharacter(complete_source) local triggered triggered = util.checkTriggerCharacter(line_to_cursor, source_trigger_character) or util.checkTriggerCharacter(line_to_cursor, opt.get_option('trigger_character')) if complete_source.complete_items ~= nil then for _, source in ipairs(complete_source.complete_items) do if source == 'lsp' and vim.lsp.buf_get_clients() ~= nil then for _, value in pairs(vim.lsp.buf_get_clients()) do if value.server_capabilities.completionProvider == nil then break end if opt.get_option('enable_server_trigger') == 1 then triggered = triggered or util.checkTriggerCharacter(line_to_cursor, value.server_capabilities.completionProvider.triggerCharacters) end end break end end end -- handle user defined only triggered character if complete_source['triggered_only'] ~= nil then local triggered_only = util.checkTriggerCharacter(line_to_cursor, complete_source['triggered_only']) if not triggered_only then if opt.get_option('auto_change_source') == 1 then manager.changeSource = true end return end end local length = opt.get_option('trigger_keyword_length') if #prefix < length and not triggered and not force then return end if triggered then complete.clearCache() manager.chainIndex = 1 end complete.performComplete(complete_source, complete_items_map, {bufnr=bufnr, prefix=prefix, textMatch=textMatch, suffix=suffix, line_to_cursor=line_to_cursor}) end local getPositionParam = function() local bufnr = api.nvim_get_current_buf() local pos = api.nvim_win_get_cursor(0) local line = api.nvim_get_current_line() local line_to_cursor = line:sub(1, pos[2]) local cursor_to_end = line:sub(pos[2]+1, #line) return bufnr, line_to_cursor, cursor_to_end end ------------------------------------------------------------------------ -- member function -- ------------------------------------------------------------------------ -- Activate when manually triggered completion or manually changing completion source function M.triggerCompletion(force) complete.clearCache() if force then manager.chainIndex = 1 end local bufnr, line_to_cursor, cursor_to_end = getPositionParam() local textMatch = vim.fn.match(line_to_cursor, '\\k*$') local prefix = line_to_cursor:sub(textMatch+1) local suffix = cursor_to_end:sub(1, vim.fn.matchend(cursor_to_end, '^\\k*')) manager.insertChar = true -- force is used when manually trigger, so it doesn't repect the trigger word length triggerCurrentCompletion(bufnr, line_to_cursor, prefix, textMatch, suffix, force) end -- Handler for auto completion function M.autoCompletion() local bufnr, line_to_cursor, cursor_to_end = getPositionParam() local textMatch = vim.fn.match(line_to_cursor, '\\k*$') local prefix = line_to_cursor:sub(textMatch+1) local suffix = cursor_to_end:sub(1, vim.fn.matchend(cursor_to_end, '^\\k*')) local length = opt.get_option('trigger_keyword_length') -- reset completion when deleting character in insert mode if #prefix < M.prefixLength and vim.fn.pumvisible() == 0 then manager.chainIndex = 1 -- not sure if I should clear cache here complete.clearCache() -- api.nvim_input("") if opt.get_option('trigger_on_delete') == 1 then M.triggerCompletion(false) end M.stop_complete = false end M.prefixLength = #prefix -- force reset chain completion if (#prefix < length) then complete.clearCache() manager.chainIndex = 1 M.stop_complete = false manager.changeSource = false end if (#prefix == 0) then complete.clearCache() end -- stop auto completion when all sources return no complete-items if M.stop_complete == true then return end triggerCurrentCompletion(bufnr, line_to_cursor, prefix, textMatch, suffix) end -- provide api for custom complete items function M.addCompleteItems(key, complete_item) complete_items_map[key] = complete_item end function M.nextCompletion() if manager.chainIndex ~= #M.chain_complete_list then manager.chainIndex = manager.chainIndex + 1 else manager.chainIndex = 1 end end function M.prevCompletion() if manager.chainIndex ~= 1 then manager.chainIndex = manager.chainIndex - 1 else manager.chainIndex = #M.chain_complete_list end end function M.checkHealth() chain_completion.checkHealth(complete_items_map) end return M ================================================ FILE: lua/completion/util.lua ================================================ ------------------------------------------------------------------------ -- utility function that are modified from neovim's source -- ------------------------------------------------------------------------ local vim = vim local api = vim.api local opt = require 'completion.option' local M = {} function M.is_list(thing) return vim.fn.type(thing) == api.nvim_get_vvar("t_list") end ------------------------ -- completion items -- ------------------------ local function compare_strings(a, b) if opt.get_option("sorting") == 'alphabet' then return a.word < b.word elseif opt.get_option("sorting") == 'length_desc' then return string.len(a.word) > string.len(b.word) else return string.len(a.word) < string.len(b.word) end end local function compare_scores_then_strings(a, b) if a.score == b.score or a.score == nil or b.score == nil then return compare_strings(a, b); end return a.score < b.score end function M.sort_completion_items(items) table.sort(items, function(a, b) if a.priority == b.priority or a.priority == nil or b.priority == nil then return compare_scores_then_strings(a, b) end return a.priority > b.priority end) end function M.addCompletionItems(item_table, item) -- word cannot be nil if item.word == nil then return end local abbr_length = opt.get_option('abbr_length') local menu_length = opt.get_option('menu_length') if menu_length ~= 0 then if item.menu ~= nil and string.len(item.menu) > menu_length then item.menu = string.sub(item.menu, 0, menu_length).."..." end end if item.abbr ~= nil and abbr_length ~= 0 then if string.len(item.abbr) > abbr_length then item.abbr = string.sub(item.abbr, 0, abbr_length).."..." end end table.insert(item_table, { word = item.word, abbr = item.abbr or '', kind = item.kind or '', menu = item.menu or '', info = item.info or '', priority = item.priority or 1, icase = 1, dup = item.dup or 1, empty = 1, user_data = item.user_data or {}, }) end -- Levenshtein algorithm for fuzzy matching -- https://gist.github.com/james2doyle/e406180e143da3bdd102 function M.fuzzy_score(str1, str2) local len1 = #str1 local len2 = #str2 local matrix = {} local cost local min = math.min; -- quick cut-offs to save time if (len1 == 0) then return len2 elseif (len2 == 0) then return len1 elseif (str1 == str2) then return 0 end -- initialise the base matrix values for i = 0, len1, 1 do matrix[i] = {} matrix[i][0] = i end for j = 0, len2, 1 do matrix[0][j] = j end -- actual Levenshtein algorithm for i = 1, len1, 1 do for j = 1, len2, 1 do if (str1:byte(i) == str2:byte(j)) then cost = 0 else cost=1 end matrix[i][j] = min(matrix[i-1][j] + 2, matrix[i][j-1], matrix[i-1][j-1] + cost) end end -- return the last value - this is the Levenshtein distance return matrix[len1][len2] end -- Check trigger character M.checkTriggerCharacter = function(line_to_cursor, trigger_character) if trigger_character == nil then return end for _, ch in ipairs(trigger_character) do local current_char = string.sub(line_to_cursor, #line_to_cursor-#ch+1, #line_to_cursor) if current_char == ch then return true end end return false end return M ================================================ FILE: lua/completion.lua ================================================ local vim = vim local api = vim.api local match = require'completion.matching' local source = require 'completion.source' local signature = require'completion.signature_help' local hover = require'completion.hover' local opt = require'completion.option' local manager = require'completion.manager' local M = {} ------------------------------------------------------------------------ -- external commands -- ------------------------------------------------------------------------ M.insertCompletionItems = function(completed_items, prefix, item) match.matching(completed_items, prefix, item) end M.addCompletionSource = function(key, completed_item) source.addCompleteItems(key, completed_item) end M.nextSource = function() source.nextCompletion() end M.prevSource = function() source.prevCompletion() end M.triggerCompletion = function() source.triggerCompletion(true, manager) end M.completionToggle = function() local enable = vim.b.completion_enable if enable == nil then M.on_attach() elseif enable == 0 then vim.b.completion_enable = 1 else vim.b.completion_enable = 0 end end ------------------------------------------------------------------------ -- smart tab -- ------------------------------------------------------------------------ function M.smart_tab() if vim.fn.pumvisible() ~= 0 then api.nvim_eval([[feedkeys("\", "n")]]) return end local col = vim.fn.col('.') - 1 if col == 0 or vim.fn.getline('.'):sub(col, col):match('%s') then api.nvim_eval([[feedkeys("\", "n")]]) return end source.triggerCompletion(true, manager) end function M.smart_s_tab() if vim.fn.pumvisible() ~= 0 then api.nvim_eval([[feedkeys("\", "n")]]) return end api.nvim_eval([[feedkeys("\", "n")]]) end ------------------------------------------------------------------------ -- confirm completion -- ------------------------------------------------------------------------ -- I want to deprecate this... local function autoAddParens(completed_item) if completed_item.kind == nil then return end if string.match(completed_item.kind, '.*Function.*') ~= nil or string.match(completed_item.kind, '.*Method.*') then api.nvim_input("()") end end -- Workaround to avoid expand snippets when not confirm -- confirmCompletion is now triggered by CompleteDone autocmd to solve issue with noselect -- Will cause snippets to expand with not pressing confirm key -- Add a flag completionConfirm to avoid this issue function M.confirmCompletion(completed_item) manager.confirmedCompletion = true end -- apply additionalTextEdits in LSP specs local function applyAddtionalTextEdits(completed_item) local lnum = api.nvim_win_get_cursor(0)[1] if completed_item.user_data.lsp ~= nil then local item = completed_item.user_data.lsp.completion_item -- vim-vsnip have better additional text edits... if vim.fn.exists('g:loaded_vsnip_integ') == 1 then api.nvim_call_function('vsnip_integ#do_complete_done', { { completed_item = completed_item, completion_item = item, apply_additional_text_edits = true } }) else if item.additionalTextEdits then local bufnr = api.nvim_get_current_buf() local edits = vim.tbl_filter( function(x) return x.range.start.line ~= (lnum - 1) end, item.additionalTextEdits ) vim.lsp.util.apply_text_edits(edits, bufnr) end end end end -- handle completeDone stuff here local function hasConfirmedCompletion() local completed_item = api.nvim_get_vvar('completed_item') if completed_item.user_data == nil then return end if completed_item.user_data.lsp ~= nil then applyAddtionalTextEdits(completed_item) if opt.get_option('enable_snippet') == "snippets.nvim" then require 'snippets'.expand_at_cursor(completed_item.user_data.actual_item, completed_item.word) end end if opt.get_option('enable_auto_paren') == 1 then autoAddParens(completed_item) end if completed_item.user_data.snippet_source == 'UltiSnips' then api.nvim_call_function('UltiSnips#ExpandSnippet', {}) elseif completed_item.user_data.snippet_source == 'Neosnippet' then api.nvim_input("".."=neosnippet#expand('"..completed_item.word.."')".."") elseif completed_item.user_data.snippet_source == 'vim-vsnip' then api.nvim_call_function('vsnip#anonymous', { table.concat(completed_item.user_data.snippet_body, "\n"), { prefix = completed_item.word } }) elseif completed_item.user_data.snippet_source == 'snippets.nvim' then require'snippets'.expand_at_cursor() end end ------------------------------------------------------------------------ -- autocommands -- ------------------------------------------------------------------------ function M.on_InsertCharPre() manager.insertChar = true manager.textHover = true manager.selected = -1 end function M.on_InsertLeave() manager.insertLeave = true end -- TODO: need further refactor, very messy now:( function M.on_InsertEnter() local enable = vim.b.completion_enable if enable == nil or enable == 0 then return end local timer = vim.loop.new_timer() -- setup variable manager.init() -- TODO: remove this local autoChange = false if opt.get_option('auto_change_source') == 1 then autoChange = true end -- reset source manager.chainIndex = 1 source.stop_complete = false local l_complete_index = manager.chainIndex local timer_cycle = opt.get_option('timer_cycle') timer:start(100, timer_cycle, vim.schedule_wrap(function() local l_changedTick = api.nvim_buf_get_changedtick(0) -- complete if changes are made if l_changedTick ~= manager.changedTick then manager.changedTick = l_changedTick if opt.get_option('enable_auto_popup') == 1 then source.autoCompletion() end if opt.get_option('enable_auto_hover') == 1 then hover.autoOpenHoverInPopup(manager) end if opt.get_option('enable_auto_signature') == 1 then signature.autoOpenSignatureHelp() end end -- change source if no item is available if manager.changeSource and autoChange then manager.changeSource = false if manager.chainIndex ~= source.chain_complete_length then manager.chainIndex = manager.chainIndex + 1 l_complete_index = manager.chainIndex manager.insertChar = true source.triggerCompletion(false, manager) else source.stop_complete = true end end -- force trigger completion when manaully chaging source if l_complete_index ~= manager.chainIndex then -- force clear completion if vim.api.nvim_get_mode()['mode'] == 'i' or vim.api.nvim_get_mode()['mode'] == 'ic' then vim.fn.complete(vim.api.nvim_win_get_cursor(0)[2], {}) end source.triggerCompletion(false, manager) l_complete_index = manager.chainIndex end -- closing timer if leaving insert mode if manager.insertLeave == true and timer:is_closing() == false then timer:stop() timer:close() end end)) end -- handle completion confirmation and dismiss hover popup function M.on_CompleteDone() if manager.confirmedCompletion then manager.confirmedCompletion = false hasConfirmedCompletion() -- auto trigger signature help when we confirm completion if vim.g.completion_enable_auto_signature ~= 0 then signature.autoOpenSignatureHelp() end end if hover.winnr ~= nil and api.nvim_win_is_valid(hover.winnr) then api.nvim_win_close(hover.winnr, true) end end M.on_attach = function(option) -- setup completion_option tables opt.set_option_table(option) local disable_filetypes = opt.get_option("disable_filetypes") local ft = vim.bo.filetype for _, disable_ft in ipairs(disable_filetypes) do if ft == disable_ft then return end end -- setup autocommand -- TODO: Modified this if lua callbacks for autocmd is merged api.nvim_command("augroup CompletionCommand") api.nvim_command("autocmd! * ") api.nvim_command("autocmd InsertEnter lua require'completion'.on_InsertEnter()") api.nvim_command("autocmd InsertLeave lua require'completion'.on_InsertLeave()") api.nvim_command("autocmd InsertCharPre lua require'completion'.on_InsertCharPre()") api.nvim_command("autocmd CompleteDone lua require'completion'.on_CompleteDone()") api.nvim_command("augroup end") if string.len(opt.get_option('confirm_key')) ~= 0 then api.nvim_buf_set_keymap(0, 'i', opt.get_option('confirm_key'), 'pumvisible() ? complete_info()["selected"] != "-1" ? "\\(completion_confirm_completion)" :'.. ' "\\\\" : "\\"', {silent=false, noremap=false, expr=true}) end vim.b.completion_enable = 1 end return M ================================================ FILE: plugin/completion.vim ================================================ " Last Change: 2020 avr 01 if exists('g:loaded_completion') | finish | endif let s:save_cpo = &cpo set cpo&vim if ! exists('g:completion_enable_snippet') let g:completion_enable_snippet = v:null endif if ! exists('g:completion_confirm_key') let g:completion_confirm_key = "\" endif if ! exists('g:completion_confirm_key_rhs') let g:completion_confirm_key_rhs = '' endif if ! exists('g:completion_enable_auto_paren') let g:completion_enable_auto_paren = 0 endif if ! exists('g:completion_enable_auto_hover') let g:completion_enable_auto_hover = 1 endif if ! exists('g:completion_docked_hover') let g:completion_docked_hover = 0 endif if ! exists('g:completion_docked_minimum_size') let g:completion_docked_minimum_size = 5 endif if ! exists('g:completion_docked_maximum_size') let g:completion_docked_maximum_size = 20 endif if ! exists('g:completion_enable_focusable_hover') let g:completion_enable_focusable_hover = 0 endif if ! exists('g:completion_enable_auto_signature') let g:completion_enable_auto_signature = 1 endif if ! exists('g:completion_trigger_character') let g:completion_trigger_character = [] endif if ! exists('g:completion_enable_server_trigger') let g:completion_enable_server_trigger = 1 endif if ! exists('g:completion_enable_auto_popup') let g:completion_enable_auto_popup = 1 endif if ! exists('g:completion_trigger_on_delete') let g:completion_trigger_on_delete = 0 end if ! exists('g:completion_trigger_keyword_length') let g:completion_trigger_keyword_length = 1 endif if ! exists('g:completion_auto_change_source') let g:completion_auto_change_source = 0 endif if !exists('g:completion_timer_cycle') let g:completion_timer_cycle = 80 endif if ! exists('g:completion_sorting') let g:completion_sorting = 'alphabet' endif if ! exists('g:completion_fuzzy_match') let g:completion_enable_fuzzy_match = 0 endif if ! exists('g:completion_expand_characters') let g:completion_expand_characters = [' ', '\t', '>', ';'] endif if ! exists('g:completion_matching_ignore_case') let g:completion_matching_ignore_case = &ignorecase endif if ! exists('g:completion_matching_smart_case') let g:completion_matching_smart_case = &smartcase endif if ! exists('g:completion_disable_filetypes') let g:completion_disable_filetypes = [] endif if ! exists('g:completion_matching_strategy_list') let g:completion_matching_strategy_list = ['exact'] endif if ! exists('g:completion_chain_complete_list') let g:completion_chain_complete_list = { \ 'default' : { \ 'default': [ \ {'complete_items': ['lsp', 'snippet']}, \ {'mode': ''}, \ {'mode': ''}], \ 'comment': [] \ } \} endif if ! exists('g:completion_customize_lsp_label') let g:completion_customize_lsp_label = {} endif if ! exists('g:completion_items_priority') let g:completion_items_priority = {} endif if ! exists('g:completion_abbr_length') let g:completion_abbr_length = 0 endif if ! exists('g:completion_menu_length') let g:completion_menu_length = 0 endif if ! exists('g:completion_items_duplicate') let g:completion_items_duplicate = {} endif inoremap (completion_confirm_completion) \ call completion#wrap_completion() inoremap (completion_next_source) \ lua require'completion'.nextSource() inoremap (completion_prev_source) \ lua require'completion'.prevSource() inoremap (completion_smart_tab) \ lua require'completion'.smart_tab() inoremap (completion_smart_s_tab) \ lua require'completion'.smart_s_tab() inoremap (completion_trigger) \ lua require'completion'.triggerCompletion() command! -nargs=0 CompletionToggle lua require'completion'.completionToggle() let &cpo = s:save_cpo unlet s:save_cpo let g:loaded_completion = 1