--- /dev/null
+ * text=auto
--- /dev/null
+ # These are supported funding model platforms
+
+ github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+ patreon: # Replace with a single Patreon username
+ open_collective: asyncomplete
+ ko_fi: # Replace with a single Ko-fi username
+ tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+ community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+ liberapay: # Replace with a single Liberapay username
+ issuehunt: # Replace with a single IssueHunt username
+ otechie: # Replace with a single Otechie username
+ custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
--- /dev/null
+ # Number of days of inactivity before an issue becomes stale
+ daysUntilStale: 60
+ # Number of days of inactivity before a stale issue is closed
+ daysUntilClose: 7
+ # Issues with these labels will never be considered stale
+ exemptLabels:
+ - pinned
+ - security
+ # Label to use when marking an issue as stale
+ staleLabel: wontfix
+ # Comment to post when marking an issue as stale. Set to `false` to disable
+ markComment: >
+ This issue has been automatically marked as stale because it has not had
+ recent activity. It will be closed if no further activity occurs. Thank you
+ for your contributions.
+ # Comment to post when closing a stale issue. Set to `false` to disable
+ closeComment: false
--- /dev/null
+ name: linux_neovim
+
+ on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+ jobs:
+ build:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ name: [neovim-v04-x64,neovim-nightly-x64]
+ include:
+ - name: neovim-v04-x64
+ os: ubuntu-latest
+ neovim_version: v0.4.3
+ - name: neovim-nightly-x64
+ os: ubuntu-latest
+ neovim_version: nightly
+ runs-on: ${{matrix.os}}
+ steps:
+ - uses: actions/checkout@v4
+ - name: Download neovim
+ shell: bash
+ run: |
+ mkdir -p ~/nvim/bin
+ curl -L https://github.com/neovim/neovim/releases/download/${{matrix.neovim_version}}/nvim.appimage -o ~/nvim/bin/nvim
+ chmod u+x ~/nvim/bin/nvim
+ - name: Download test runner
+ shell: bash
+ run: git clone --depth 1 --branch v1.5.4 --single-branch https://github.com/thinca/vim-themis ~/themis
+ - name: Run tests
+ shell: bash
+ run: |
+ export PATH=~/nvim/bin:$PATH
+ export PATH=~/themis/bin:$PATH
+ export THEMIS_VIM=nvim
+ nvim --version
+ themis --reporter spec
--- /dev/null
+ name: linux_vim
+
+ on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+ jobs:
+ build:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ name: [vim-v82-x64, vim-v81-x64]
+ include:
+ - name: vim-v82-x64
+ os: ubuntu-latest
+ vim_version: 8.2.0813
+ glibc_version: 2.15
+ - name: vim-v81-x64
+ os: ubuntu-latest
+ vim_version: 8.1.2414
+ glibc_version: 2.15
+ runs-on: ${{matrix.os}}
+ steps:
+ - uses: actions/checkout@v4
+ - name: Download vim
+ shell: bash
+ run: |
+ mkdir -p ~/vim/bin
+ curl -L https://github.com/vim/vim-appimage/releases/download/v${{matrix.vim_version}}/GVim-v${{matrix.vim_version}}.glibc${{matrix.glibc_version}}-x86_64.AppImage -o ~/vim/bin/vim
+ chmod u+x ~/vim/bin/vim
+ - name: Download test runner
+ shell: bash
+ run: git clone --depth 1 --branch v1.5.4 --single-branch https://github.com/thinca/vim-themis ~/themis
+ - name: Run tests
+ shell: bash
+ run: |
+ export PATH=~/vim/bin:$PATH
+ export PATH=~/themis/bin:$PATH
+ export THEMIS_VIM=vim
+ vim --version
+ themis --reporter spec
--- /dev/null
+ name: mac_neovim
+
+ on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+ jobs:
+ build:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [macos-latest]
+ name: [neovim-v04-x64,neovim-nightly-x64]
+ include:
+ - name: neovim-v04-x64
+ os: macos-latest
+ neovim_version: v0.4.3
+ - name: neovim-nightly-x64
+ os: macos-latest
+ neovim_version: nightly
+ runs-on: ${{matrix.os}}
+ steps:
+ - uses: actions/checkout@v4
+ - name: Download neovim
+ shell: bash
+ run: curl -L https://github.com/neovim/neovim/releases/download/${{matrix.neovim_version}}/nvim-macos.tar.gz -o ~/nvim.tar.gz
+ - name: Extract neovim
+ shell: bash
+ run: tar xzf ~/nvim.tar.gz -C ~/
+ - name: Download test runner
+ shell: bash
+ run: git clone --depth 1 --branch v1.5.4 --single-branch https://github.com/thinca/vim-themis ~/themis
+ - name: Run tests
+ shell: bash
+ run: |
+ export PATH=~/nvim-osx64/bin:$PATH
+ export PATH=~/themis/bin:$PATH
+ export THEMIS_VIM=nvim
+ nvim --version
+ themis --reporter spec
--- /dev/null
+ name: reviewdog
+
+ on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+ jobs:
+ vimlint:
+ name: runner / vint
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: vint
+ uses: reviewdog/action-vint@v1
+ with:
+ github_token: ${{ secrets.github_token }}
+ level: error
+ reporter: github-pr-review
--- /dev/null
+ name: windows_neovim
+
+ on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+ jobs:
+ build:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [windows-latest]
+ name: [neovim-v04-x64,neovim-nightly-x64]
+ include:
+ - name: neovim-v04-x64
+ os: windows-latest
+ neovim_version: v0.4.3
+ neovim_arch: win64
+ - name: neovim-nightly-x64
+ os: windows-latest
+ neovim_version: nightly
+ neovim_arch: win64
+ runs-on: ${{matrix.os}}
+ steps:
+ - uses: actions/checkout@v4
+ - name: Download neovim
+ shell: PowerShell
+ run: Invoke-WebRequest -Uri https://github.com/neovim/neovim/releases/download/${{matrix.neovim_version}}/nvim-${{matrix.neovim_arch}}.zip -OutFile neovim.zip
+ - name: Extract neovim
+ shell: PowerShell
+ run: Expand-Archive -Path neovim.zip -DestinationPath $env:USERPROFILE
+ - name: Download test runner
+ shell: PowerShell
+ run: git clone --depth 1 --branch v1.5.4 --single-branch https://github.com/thinca/vim-themis $env:USERPROFILE\themis
+ - name: Run tests
+ shell: cmd
+ run: |
+ SET PATH=%USERPROFILE%\Neovim\bin;%PATH%;
+ SET PATH=%USERPROFILE%\themis\bin;%PATH%;
+ SET THEMIS_VIM=nvim
+ nvim --version
+ themis --reporter spec
--- /dev/null
+ name: windows_vim
+
+ on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+ jobs:
+ build:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [windows-latest]
+ name: [vim-v82-x64, vim-v81-x64, vim-v80-x64]
+ include:
+ - name: vim-v82-x64
+ os: windows-latest
+ vim_version: 8.2.0813
+ vim_arch: x64
+ vim_ver_path: vim82
+ - name: vim-v81-x64
+ os: windows-latest
+ vim_version: 8.1.2414
+ vim_arch: x64
+ vim_ver_path: vim81
+ - name: vim-v80-x64
+ os: windows-latest
+ vim_version: 8.0.1567
+ vim_arch: x64
+ vim_ver_path: vim80
+ runs-on: ${{matrix.os}}
+ steps:
+ - uses: actions/checkout@v4
+ - name: Download vim
+ shell: PowerShell
+ run: Invoke-WebRequest -Uri https://github.com/vim/vim-win32-installer/releases/download/v${{matrix.vim_version}}/gvim_${{matrix.vim_version}}_${{matrix.vim_arch}}.zip -OutFile vim.zip
+ - name: Extract vim
+ shell: PowerShell
+ run: Expand-Archive -Path vim.zip -DestinationPath $env:USERPROFILE
+ - name: Download test runner
+ shell: PowerShell
+ run: git clone --depth 1 --branch v1.5.4 --single-branch https://github.com/thinca/vim-themis $env:USERPROFILE\themis
+ - name: Run tests
+ shell: cmd
+ run: |
+ SET PATH=%USERPROFILE%\vim\${{matrix.vim_ver_path}};%PATH%;
+ SET PATH=%USERPROFILE%\themis\bin;%PATH%;
+ vim --version
+ themis --reporter spec
--- /dev/null
--- /dev/null
++tags
--- /dev/null
+ cmdargs:
+ severity: style_problem
+
+ policies:
+ ProhibitUnusedVariable:
+ enabled: false
+ ProhibitImplicitScopeVariable:
+ enabled: true
+ ProhibitNoAbortFunction:
+ enabled: true
--- /dev/null
+ MIT License
+
+ Copyright (c) 2019 Prabir Shrestha
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
--- /dev/null
+ asyncomplete.vim
+ ================
+
+ Async autocompletion for Vim 8 and Neovim with |timers|.
+
+ This is inspired by [nvim-complete-manager](https://github.com/roxma/nvim-complete-manager) but written
+ in pure Vim Script.
+
+ ### Installing
+
+ ```viml
+ Plug 'prabirshrestha/asyncomplete.vim'
+ ```
+
+ #### Tab completion
+
+ ```vim
+ inoremap <expr> <Tab> pumvisible() ? "\<C-n>" : "\<Tab>"
+ inoremap <expr> <S-Tab> pumvisible() ? "\<C-p>" : "\<S-Tab>"
+ inoremap <expr> <cr> pumvisible() ? asyncomplete#close_popup() : "\<cr>"
+ ```
+
+ If you prefer the enter key to always insert a new line (even if the popup menu is visible) then
+ you can amend the above mapping as follows:
+
+ ```vim
+ inoremap <expr> <cr> pumvisible() ? asyncomplete#close_popup() . "\<cr>" : "\<cr>"
+ ```
+
+ ### Force refresh completion
+
+ ```vim
+ imap <c-space> <Plug>(asyncomplete_force_refresh)
+ " For Vim 8 (<c-@> corresponds to <c-space>):
+ " imap <c-@> <Plug>(asyncomplete_force_refresh)
+ ```
+
+ ### Auto popup
+ By default asyncomplete will automatically show the autocomplete popup menu as you start typing.
+ If you would like to disable the default behavior set `g:asyncomplete_auto_popup` to 0.
+
+ ```vim
+ let g:asyncomplete_auto_popup = 0
+ ```
+
+ You can use the above `<Plug>(asyncomplete_force_refresh)` to show the popup
+ or you can tab to show the autocomplete.
+
+ ```vim
+ let g:asyncomplete_auto_popup = 0
+
+ function! s:check_back_space() abort
+ let col = col('.') - 1
+ return !col || getline('.')[col - 1] =~ '\s'
+ endfunction
+
+ inoremap <silent><expr> <TAB>
+ \ pumvisible() ? "\<C-n>" :
+ \ <SID>check_back_space() ? "\<TAB>" :
+ \ asyncomplete#force_refresh()
+ inoremap <expr><S-TAB> pumvisible() ? "\<C-p>" : "\<C-h>"
+ ```
+
+ #### Preview Window
+
+ To enable preview window:
+
+ ```vim
+ " allow modifying the completeopt variable, or it will
+ " be overridden all the time
+ let g:asyncomplete_auto_completeopt = 0
+
+ set completeopt=menuone,noinsert,noselect,preview
+ ```
+
+ To auto close preview window when completion is done.
+
+ ```vim
+ autocmd! CompleteDone * if pumvisible() == 0 | pclose | endif
+ ```
+
+ ### Sources
+
+ asyncomplete.vim deliberately does not contain any sources. Please use one of the following sources or create your own.
+
+ #### Language Server Protocol (LSP)
+ [Language Server Protocol](https://github.com/Microsoft/language-server-protocol) via [vim-lsp](https://github.com/prabirshrestha/vim-lsp) and [asyncomplete-lsp.vim](https://github.com/prabirshrestha/asyncomplete-lsp.vim)
+
+ **Please note** that vim-lsp setup for neovim requires neovim v0.2.0 or higher, since it uses lambda setup.
+
+ ```vim
+ Plug 'prabirshrestha/asyncomplete.vim'
+ Plug 'prabirshrestha/vim-lsp'
+ Plug 'prabirshrestha/asyncomplete-lsp.vim'
+
+ if executable('pyls')
+ " pip install python-language-server
+ au User lsp_setup call lsp#register_server({
+ \ 'name': 'pyls',
+ \ 'cmd': {server_info->['pyls']},
+ \ 'allowlist': ['python'],
+ \ })
+ endif
+ ```
+
+ **Refer to [vim-lsp wiki](https://github.com/prabirshrestha/vim-lsp/wiki/Servers) for configuring other language servers.** Besides auto-complete language server support other features such as go to definition, find references, renaming symbols, document symbols, find workspace symbols, formatting and so on.
+
+ *in alphabetical order*
+
+ | Languages/FileType/Source | Links |
+ |-------------------------------|----------------------------------------------------------------------------------------------------|
+ | [Ale][ale] | [asyncomplete-ale.vim](https://github.com/andreypopp/asyncomplete-ale.vim) |
+ | Buffer | [asyncomplete-buffer.vim](https://github.com/prabirshrestha/asyncomplete-buffer.vim) |
+ | C/C++ | [asyncomplete-clang.vim](https://github.com/keremc/asyncomplete-clang.vim) |
+ | Clojure | [async-clj-omni](https://github.com/clojure-vim/async-clj-omni) |
+ | Common Lisp (vlime) | [vlime](https://github.com/vlime/vlime) |
+ | Dictionary (look) | [asyncomplete-look](https://github.com/htlsne/asyncomplete-look) |
+ | [Emmet][emmet-vim] | [asyncomplete-emmet.vim](https://github.com/prabirshrestha/asyncomplete-emmet.vim) |
+ | English | [asyncomplete-nextword.vim](https://github.com/high-moctane/asyncomplete-nextword.vim) |
+ | Emoji | [asyncomplete-emoji.vim](https://github.com/prabirshrestha/asyncomplete-emoji.vim) |
+ | Filenames / directories | [asyncomplete-file.vim](https://github.com/prabirshrestha/asyncomplete-file.vim) |
+ | [NeoInclude][neoinclude] | [asyncomplete-neoinclude.vim](https://github.com/kyouryuukunn/asyncomplete-neoinclude.vim) |
+ | Go | [asyncomplete-gocode.vim](https://github.com/prabirshrestha/asyncomplete-gocode.vim) |
+ | Git commit message | [asyncomplete-gitcommit](https://github.com/laixintao/asyncomplete-gitcommit) |
+ | JavaScript (Flow) | [asyncomplete-flow.vim](https://github.com/prabirshrestha/asyncomplete-flow.vim) |
+ | [Neosnippet][neosnippet] | [asyncomplete-neosnippet.vim](https://github.com/prabirshrestha/asyncomplete-neosnippet.vim) |
+ | Omni | [asyncomplete-omni.vim](https://github.com/yami-beta/asyncomplete-omni.vim) |
+ | PivotalTracker stories | [asyncomplete-pivotaltracker.vim](https://github.com/hauleth/asyncomplete-pivotaltracker.vim) |
+ | Rust (racer) | [asyncomplete-racer.vim](https://github.com/keremc/asyncomplete-racer.vim) |
+ | [TabNine][TabNine] powered by AI | [asyncomplete-tabnine.vim](https://github.com/kitagry/asyncomplete-tabnine.vim) |
+ | [tmux complete][tmuxcomplete] | [tmux-complete.vim][tmuxcomplete] |
+ | Typescript | [asyncomplete-tscompletejob.vim](https://github.com/prabirshrestha/asyncomplete-tscompletejob.vim) |
+ | [UltiSnips][ultisnips] | [asyncomplete-ultisnips.vim](https://github.com/prabirshrestha/asyncomplete-ultisnips.vim) |
+ | User (compl-function) | [asyncomplete-user.vim](https://github.com/jsit/asyncomplete-user.vim) |
+ | Vim Syntax | [asyncomplete-necosyntax.vim](https://github.com/prabirshrestha/asyncomplete-necosyntax.vim) |
+ | Vim tags | [asyncomplete-tags.vim](https://github.com/prabirshrestha/asyncomplete-tags.vim) |
+ | Vim | [asyncomplete-necovim.vim](https://github.com/prabirshrestha/asyncomplete-necovim.vim) |
+
+ [ale]: https://github.com/dense-analysis/ale
+ [emmet-vim]: https://github.com/mattn/emmet-vim
+ [neosnippet]: https://github.com/Shougo/neosnippet.vim
+ [neoinclude]: https://github.com/Shougo/neoinclude.vim
+ [TabNine]: https://www.tabnine.com/
+ [tmuxcomplete]: https://github.com/wellle/tmux-complete.vim
+ [ultisnips]: https://github.com/SirVer/ultisnips
+
+ *can't find what you are looking for? write one instead an send a PR to be included here or search github topics tagged with asyncomplete at https://github.com/topics/asyncomplete.*
+
+ #### Using existing vim plugin sources
+
+ Rather than writing your own completion source from scratch you could also suggests other plugin authors to provide a async completion api that works for asyncomplete.vim or any other async autocomplete libraries without taking a dependency on asyncomplete.vim. The plugin can provide a function that takes a callback which returns the list of candidates and the startcol from where it must show the popup. Candidates can be list of words or vim's `complete-items`.
+
+ ```vim
+ function s:completor(opt, ctx)
+ call mylanguage#get_async_completions({candidates, startcol -> asyncomplete#complete(a:opt['name'], a:ctx, startcol, candidates) })
+ endfunction
+
+ au User asyncomplete_setup call asyncomplete#register_source({
+ \ 'name': 'mylanguage',
+ \ 'allowlist': ['*'],
+ \ 'completor': function('s:completor'),
+ \ })
+ ```
+
+ ### Example
+
+ ```vim
+ function! s:js_completor(opt, ctx) abort
+ let l:col = a:ctx['col']
+ let l:typed = a:ctx['typed']
+
+ let l:kw = matchstr(l:typed, '\v\S+$')
+ let l:kwlen = len(l:kw)
+
+ let l:startcol = l:col - l:kwlen
+
+ let l:matches = [
+ \ "do", "if", "in", "for", "let", "new", "try", "var", "case", "else", "enum", "eval", "null", "this", "true",
+ \ "void", "with", "await", "break", "catch", "class", "const", "false", "super", "throw", "while", "yield",
+ \ "delete", "export", "import", "public", "return", "static", "switch", "typeof", "default", "extends",
+ \ "finally", "package", "private", "continue", "debugger", "function", "arguments", "interface", "protected",
+ \ "implements", "instanceof"
+ \ ]
+
+ call asyncomplete#complete(a:opt['name'], a:ctx, l:startcol, l:matches)
+ endfunction
+
+ au User asyncomplete_setup call asyncomplete#register_source({
+ \ 'name': 'javascript',
+ \ 'allowlist': ['javascript'],
+ \ 'completor': function('s:js_completor'),
+ \ })
+ ```
+
+ The above sample shows synchronous completion. If you would like to make it async just call `asyncomplete#complete` whenever you have the results ready.
+
+ ```vim
+ call timer_start(2000, {timer-> asyncomplete#complete(a:opt['name'], a:ctx, l:startcol, l:matches)})
+ ```
+
+ If you are returning incomplete results and would like to trigger completion on the next keypress pass `1` as the fifth parameter to `asyncomplete#complete`
+ which signifies the result is incomplete.
+
+ ```vim
+ call asyncomplete#complete(a:opt['name'], a:ctx, l:startcol, l:matches, 1)
+ ```
+
+ As a source author you do not have to worry about synchronization issues in case the server returns the async completion after the user has typed more
+ characters. asyncomplete.vim uses partial caching as well as ignores if the context changes when calling `asyncomplete#complete`.
+ This is one of the core reason why the original context must be passed when calling `asyncomplete#complete`.
+
+ ### Credits
+ All the credit goes to the following projects
+ * [https://github.com/roxma/nvim-complete-manager](https://github.com/roxma/nvim-complete-manager)
+ * [https://github.com/maralla/completor.vim](https://github.com/maralla/completor.vim)
+
+ ## Contributors
+
+ ### Code Contributors
+
+ This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
+ <a href="https://github.com/prabirshrestha/asyncomplete.vim/graphs/contributors"><img src="https://opencollective.com/asyncomplete/contributors.svg?width=890&button=false" /></a>
+
+ ### Financial Contributors
+
+ Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/asyncomplete/contribute)]
+
+ #### Individuals
+
+ <a href="https://opencollective.com/asyncomplete"><img src="https://opencollective.com/asyncomplete/individuals.svg?width=890"></a>
+
+ #### Organizations
+
+ Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/asyncomplete/contribute)]
+
+ <a href="https://opencollective.com/asyncomplete/organization/0/website"><img src="https://opencollective.com/asyncomplete/organization/0/avatar.svg"></a>
+ <a href="https://opencollective.com/asyncomplete/organization/1/website"><img src="https://opencollective.com/asyncomplete/organization/1/avatar.svg"></a>
+ <a href="https://opencollective.com/asyncomplete/organization/2/website"><img src="https://opencollective.com/asyncomplete/organization/2/avatar.svg"></a>
+ <a href="https://opencollective.com/asyncomplete/organization/3/website"><img src="https://opencollective.com/asyncomplete/organization/3/avatar.svg"></a>
+ <a href="https://opencollective.com/asyncomplete/organization/4/website"><img src="https://opencollective.com/asyncomplete/organization/4/avatar.svg"></a>
+ <a href="https://opencollective.com/asyncomplete/organization/5/website"><img src="https://opencollective.com/asyncomplete/organization/5/avatar.svg"></a>
+ <a href="https://opencollective.com/asyncomplete/organization/6/website"><img src="https://opencollective.com/asyncomplete/organization/6/avatar.svg"></a>
+ <a href="https://opencollective.com/asyncomplete/organization/7/website"><img src="https://opencollective.com/asyncomplete/organization/7/avatar.svg"></a>
+ <a href="https://opencollective.com/asyncomplete/organization/8/website"><img src="https://opencollective.com/asyncomplete/organization/8/avatar.svg"></a>
+ <a href="https://opencollective.com/asyncomplete/organization/9/website"><img src="https://opencollective.com/asyncomplete/organization/9/avatar.svg"></a>
--- /dev/null
+ function! asyncomplete#log(...) abort
+ if !empty(g:asyncomplete_log_file)
+ call writefile([json_encode(a:000)], g:asyncomplete_log_file, 'a')
+ endif
+ endfunction
+
+ " do nothing, place it here only to avoid the message
+ augroup asyncomplete_silence_messages
+ au!
+ autocmd User asyncomplete_setup silent
+ augroup END
+
+ if !has('timers')
+ echohl ErrorMsg
+ echomsg 'Vim/Neovim compiled with timers required for asyncomplete.vim.'
+ echohl NONE
+ if has('nvim')
+ call asyncomplete#log('neovim compiled with timers required.')
+ else
+ call asyncomplete#log('vim compiled with timers required.')
+ endif
+ " Clear augroup so this message is only displayed once.
+ au! asyncomplete_enable *
+ finish
+ endif
+
+ let s:already_setup = 0
+ let s:sources = {}
+ let s:matches = {} " { server_name: { incomplete: 1, startcol: 0, items: [], refresh: 0, status: 'idle|pending|success|failure', ctx: ctx } }
+ let s:has_complete_info = exists('*complete_info')
+ let s:has_matchfuzzypos = exists('*matchfuzzypos')
+
+ function! s:setup_if_required() abort
+ if !s:already_setup
+ " register asyncomplete change manager
+ for l:change_manager in g:asyncomplete_change_manager
+ call asyncomplete#log('core', 'initializing asyncomplete change manager', l:change_manager)
+ if type(l:change_manager) == type('')
+ execute 'let s:on_change_manager = function("'. l:change_manager .'")()'
+ else
+ let s:on_change_manager = l:change_manager()
+ endif
+ if has_key(s:on_change_manager, 'error')
+ call asyncomplete#log('core', 'initializing asyncomplete change manager failed', s:on_change_manager['name'], s:on_change_manager['error'])
+ else
+ call s:on_change_manager.register(function('s:on_change'))
+ call asyncomplete#log('core', 'initializing asyncomplete change manager complete', s:on_change_manager['name'])
+ break
+ endif
+ endfor
+
+ augroup asyncomplete
+ autocmd!
+ autocmd InsertEnter * call s:on_insert_enter()
+ autocmd InsertLeave * call s:on_insert_leave()
+ augroup END
+
+ doautocmd <nomodeline> User asyncomplete_setup
+ let s:already_setup = 1
+ endif
+ endfunction
+
+ function! asyncomplete#enable_for_buffer() abort
+ call s:setup_if_required()
+ let b:asyncomplete_enable = 1
+ endfunction
+
+ function! asyncomplete#disable_for_buffer() abort
+ let b:asyncomplete_enable = 0
+ endfunction
+
+ function! asyncomplete#get_source_names() abort
+ return keys(s:sources)
+ endfunction
+
+ function! asyncomplete#get_source_info(source_name) abort
+ return s:sources[a:source_name]
+ endfunction
+
+ function! asyncomplete#register_source(info) abort
+ if has_key(s:sources, a:info['name'])
+ call asyncomplete#log('core', 'duplicate asyncomplete#register_source', a:info['name'])
+ return -1
+ else
+ let s:sources[a:info['name']] = a:info
+ if has_key(a:info, 'events') && has_key(a:info, 'on_event')
+ execute 'augroup asyncomplete_source_event_' . a:info['name']
+ for l:event in a:info['events']
+ let l:exec = 'if get(b:,"asyncomplete_enable",0) | call s:notify_event_to_source("' . a:info['name'] . '", "'.l:event.'",asyncomplete#context()) | endif'
+ if type(l:event) == type('')
+ execute 'au ' . l:event . ' * ' . l:exec
+ elseif type(l:event) == type([])
+ execute 'au ' . join(l:event,' ') .' ' . l:exec
+ endif
+ endfor
+ execute 'augroup end'
+ endif
+
+ if exists('b:asyncomplete_active_sources')
+ unlet b:asyncomplete_active_sources
+ call s:get_active_sources_for_buffer()
+ endif
+
+ if exists('b:asyncomplete_triggers')
+ unlet b:asyncomplete_triggers
+ call s:update_trigger_characters()
+ endif
+
+ return 1
+ endif
+ endfunction
+
+ function! asyncomplete#unregister_source(info_or_server_name) abort
+ if type(a:info_or_server_name) == type({})
+ let l:server_name = a:info_or_server_name['name']
+ else
+ let l:server_name = a:info_or_server_name
+ endif
+ if has_key(s:sources, l:server_name)
+ let l:server = s:sources[l:server_name]
+ if has_key(l:server, 'unregister')
+ call l:server.unregister()
+ endif
+ unlet s:sources[l:server_name]
+ return 1
+ else
+ return -1
+ endif
+ endfunction
+
+ function! asyncomplete#context() abort
+ let l:ret = {'bufnr':bufnr('%'), 'curpos':getcurpos(), 'changedtick':b:changedtick}
+ let l:ret['lnum'] = l:ret['curpos'][1]
+ let l:ret['col'] = l:ret['curpos'][2]
+ let l:ret['filetype'] = &filetype
+ let l:ret['filepath'] = expand('%:p')
+ let l:ret['typed'] = strpart(getline(l:ret['lnum']),0,l:ret['col']-1)
+ return l:ret
+ endfunction
+
+ function! s:on_insert_enter() abort
+ call s:get_active_sources_for_buffer() " call to cache
+ call s:update_trigger_characters()
+ endfunction
+
+ function! s:on_insert_leave() abort
+ let s:matches = {}
+ if exists('s:update_pum_timer')
+ call timer_stop(s:update_pum_timer)
+ unlet s:update_pum_timer
+ endif
+ endfunction
+
+ function! s:get_active_sources_for_buffer() abort
+ if exists('b:asyncomplete_active_sources')
+ " active sources were cached for buffer
+ return b:asyncomplete_active_sources
+ endif
+
+ call asyncomplete#log('core', 'computing active sources for buffer', bufnr('%'))
+ let b:asyncomplete_active_sources = []
+ for [l:name, l:info] in items(s:sources)
+ let l:blocked = 0
+
+ if has_key(l:info, 'blocklist')
+ let l:blocklistkey = 'blocklist'
+ else
+ let l:blocklistkey = 'blacklist'
+ endif
+ if has_key(l:info, l:blocklistkey)
+ for l:filetype in l:info[l:blocklistkey]
+ if l:filetype == &filetype || l:filetype is# '*'
+ let l:blocked = 1
+ break
+ endif
+ endfor
+ endif
+
+ if l:blocked
+ continue
+ endif
+
+ if has_key(l:info, 'allowlist')
+ let l:allowlistkey = 'allowlist'
+ else
+ let l:allowlistkey = 'whitelist'
+ endif
+ if has_key(l:info, l:allowlistkey)
+ for l:filetype in l:info[l:allowlistkey]
+ if l:filetype == &filetype || l:filetype is# '*'
+ let b:asyncomplete_active_sources += [l:name]
+ break
+ endif
+ endfor
+ endif
+ endfor
+
+ call asyncomplete#log('core', 'active source for buffer', bufnr('%'), b:asyncomplete_active_sources)
+
+ return b:asyncomplete_active_sources
+ endfunction
+
+ function! s:update_trigger_characters() abort
+ if exists('b:asyncomplete_triggers')
+ " triggers were cached for buffer
+ return b:asyncomplete_triggers
+ endif
+ let b:asyncomplete_triggers = {} " { char: { 'sourcea': 1, 'sourceb': 2 } }
+
+ for l:source_name in s:get_active_sources_for_buffer()
+ let l:source_info = s:sources[l:source_name]
+ if has_key(l:source_info, 'triggers') && has_key(l:source_info['triggers'], &filetype)
+ let l:triggers = l:source_info['triggers'][&filetype]
+ elseif has_key(l:source_info, 'triggers') && has_key(l:source_info['triggers'], '*')
+ let l:triggers = l:source_info['triggers']['*']
+ elseif has_key(g:asyncomplete_triggers, &filetype)
+ let l:triggers = g:asyncomplete_triggers[&filetype]
+ elseif has_key(g:asyncomplete_triggers, '*')
+ let l:triggers = g:asyncomplete_triggers['*']
+ else
+ let l:triggers = []
+ endif
+
+ for l:trigger in l:triggers
+ let l:last_char = l:trigger[len(l:trigger) -1]
+ if !has_key(b:asyncomplete_triggers, l:last_char)
+ let b:asyncomplete_triggers[l:last_char] = {}
+ endif
+ if !has_key(b:asyncomplete_triggers[l:last_char], l:source_name)
+ let b:asyncomplete_triggers[l:last_char][l:source_name] = []
+ endif
+ call add(b:asyncomplete_triggers[l:last_char][l:source_name], l:trigger)
+ endfor
+ endfor
+ call asyncomplete#log('core', 'trigger characters for buffer', bufnr('%'), b:asyncomplete_triggers)
+ endfunction
+
+ function! s:should_skip() abort
+ if mode() isnot# 'i' || !get(b:, 'asyncomplete_enable', 0)
+ return 1
+ else
+ return 0
+ endif
+ endfunction
+
+ function! asyncomplete#close_popup() abort
+ return pumvisible() ? "\<C-y>" : ''
+ endfunction
+
+ function! asyncomplete#cancel_popup() abort
+ return pumvisible() ? "\<C-e>" : ''
+ endfunction
+
+ function! s:get_min_chars(source_name) abort
+ if exists('b:asyncomplete_min_chars')
+ return b:asyncomplete_min_chars
+ elseif has_key(s:sources, a:source_name)
+ return get(s:sources[a:source_name], 'min_chars', g:asyncomplete_min_chars)
+ endif
+ return g:asyncomplete_min_chars
+ endfunction
+
+ function! s:on_change() abort
+ if s:should_skip() | return | endif
+
+ if !g:asyncomplete_auto_popup
+ return
+ endif
+
+ let l:ctx = asyncomplete#context()
+ let l:last_char = l:ctx['typed'][l:ctx['col'] - 2] " col is 1-indexed, but str 0-indexed
+ if exists('b:asyncomplete_triggers')
+ let l:triggered_sources = get(b:asyncomplete_triggers, l:last_char, {})
+ else
+ let l:triggered_sources = {}
+ endif
+ let l:refresh_pattern = get(b:, 'asyncomplete_refresh_pattern', '\(\k\+$\)')
+ let [l:_, l:startidx, l:endidx] = asyncomplete#utils#matchstrpos(l:ctx['typed'], l:refresh_pattern)
+
+ for l:source_name in get(b:, 'asyncomplete_active_sources', [])
+ " match sources based on the last character if it is a trigger character
+ " TODO: also check for multiple chars instead of just last chars for
+ " languages such as cpp which uses -> and ::
+ if has_key(l:triggered_sources, l:source_name)
+ let l:startcol = l:ctx['col']
+ elseif l:startidx > -1
+ let l:startcol = l:startidx + 1 " col is 1-indexed, but str 0-indexed
+ endif
+ " here we use the existence of `l:startcol` to determine whether to
+ " use this completion source. If `l:startcol` exists, we use the
+ " source. If it does not exist, it means that we cannot get a
+ " meaningful starting point for the current source, and this implies
+ " that we cannot use this source for completion. Therefore, we remove
+ " the matches from the source.
+ if exists('l:startcol') && l:endidx - l:startidx >= s:get_min_chars(l:source_name)
+ if !has_key(s:matches, l:source_name) || s:matches[l:source_name]['ctx']['lnum'] !=# l:ctx['lnum'] || s:matches[l:source_name]['startcol'] !=# l:startcol
+ let s:matches[l:source_name] = { 'startcol': l:startcol, 'status': 'idle', 'items': [], 'refresh': 0, 'ctx': l:ctx }
+ endif
+ else
+ if has_key(s:matches, l:source_name)
+ unlet s:matches[l:source_name]
+ endif
+ endif
+ endfor
+
+ call s:trigger(l:ctx)
+ call s:update_pum()
+ endfunction
+
+ function! s:trigger(ctx) abort
+ " send cancellation request if supported
+ for [l:source_name, l:matches] in items(s:matches)
+ call asyncomplete#log('core', 's:trigger', l:matches)
+ if l:matches['refresh'] || l:matches['status'] ==# 'idle' || l:matches['status'] ==# 'failure'
+ let l:matches['status'] = 'pending'
+ try
+ " TODO: check for min chars
+ call asyncomplete#log('core', 's:trigger.completor()', l:source_name, s:matches[l:source_name], a:ctx)
+ call s:sources[l:source_name].completor(s:sources[l:source_name], a:ctx)
+ catch
+ let l:matches['status'] = 'failure'
+ call asyncomplete#log('core', 's:trigger', 'error', v:exception)
+ continue
+ endtry
+ endif
+ endfor
+ endfunction
+
+ function! asyncomplete#complete(name, ctx, startcol, items, ...) abort
+ let l:refresh = a:0 > 0 ? a:1 : 0
+ let l:ctx = asyncomplete#context()
+ if !has_key(s:matches, a:name) || l:ctx['lnum'] != a:ctx['lnum'] " TODO: handle more context changes
+ call asyncomplete#log('core', 'asyncomplete#log', 'ignoring due to context chnages', a:name, a:ctx, a:startcol, l:refresh, a:items)
+ call s:update_pum()
+ return
+ endif
+
+ call asyncomplete#log('asyncomplete#complete', a:name, a:ctx, a:startcol, l:refresh, a:items)
+
+ let l:matches = s:matches[a:name]
+ let l:matches['items'] = s:normalize_items(a:items)
+ let l:matches['refresh'] = l:refresh
+ let l:matches['startcol'] = a:startcol
+ let l:matches['status'] = 'success'
+
+ call s:update_pum()
+ endfunction
+
+ function! s:normalize_items(items) abort
+ if len(a:items) > 0 && type(a:items[0]) ==# type('')
+ let l:items = []
+ for l:item in a:items
+ let l:items += [{'word': l:item }]
+ endfor
+ return l:items
+ else
+ return a:items
+ endif
+ endfunction
+
+ function! asyncomplete#force_refresh() abort
+ return asyncomplete#menu_selected() ? "\<c-y>\<c-r>=asyncomplete#_force_refresh()\<CR>" : "\<c-r>=asyncomplete#_force_refresh()\<CR>"
+ endfunction
+
+ function! asyncomplete#_force_refresh() abort
+ if s:should_skip() | return | endif
+
+ let l:ctx = asyncomplete#context()
+ let l:startcol = l:ctx['col']
+ let l:last_char = l:ctx['typed'][l:startcol - 2]
+
+ " loop left and find the start of the word or trigger chars and set it as the startcol for the source instead of refresh_pattern
+ let l:refresh_pattern = get(b:, 'asyncomplete_refresh_pattern', '\(\k\+$\)')
+ let [l:_, l:startidx, l:endidx] = asyncomplete#utils#matchstrpos(l:ctx['typed'], l:refresh_pattern)
+ " When no word here, startcol is current col
+ let l:startcol = l:startidx == -1 ? col('.') : l:startidx + 1
+
+ let s:matches = {}
+
+ for l:source_name in get(b:, 'asyncomplete_active_sources', [])
+ let s:matches[l:source_name] = { 'startcol': l:startcol, 'status': 'idle', 'items': [], 'refresh': 0, 'ctx': l:ctx }
+ endfor
+
+ call s:trigger(l:ctx)
+ call s:update_pum()
+ return ''
+ endfunction
+
+ function! s:update_pum() abort
+ if exists('s:update_pum_timer')
+ call timer_stop(s:update_pum_timer)
+ unlet s:update_pum_timer
+ endif
+ call asyncomplete#log('core', 's:update_pum')
+ let s:update_pum_timer = timer_start(g:asyncomplete_popup_delay, function('s:recompute_pum'))
+ endfunction
+
+ function! s:recompute_pum(...) abort
+ if s:should_skip() | return | endif
+
+ " TODO: add support for remote recomputation of complete items,
+ " Ex: heavy computation such as fuzzy search can happen in a python thread
+
+ call asyncomplete#log('core', 's:recompute_pum')
+
+ if asyncomplete#menu_selected()
+ call asyncomplete#log('core', 's:recomputed_pum', 'ignorning refresh pum due to menu selection')
+ return
+ endif
+
+ let l:ctx = asyncomplete#context()
+
+ let l:startcols = []
+ let l:matches_to_filter = {}
+
+ for [l:source_name, l:match] in items(s:matches)
+ " ignore sources that have been unregistered
+ if !has_key(s:sources, l:source_name) | continue | endif
+ let l:startcol = l:match['startcol']
+ let l:startcols += [l:startcol]
+ let l:curitems = l:match['items']
+
+ if l:startcol > l:ctx['col']
+ call asyncomplete#log('core', 's:recompute_pum', 'ignoring due to wrong start col', l:startcol, l:ctx['col'])
+ continue
+ else
+ let l:matches_to_filter[l:source_name] = l:match
+ endif
+ endfor
+
+ let l:startcol = min(l:startcols)
+ let l:base = l:ctx['typed'][l:startcol - 1:] " col is 1-indexed, but str 0-indexed
+
+ let l:filter_ctx = extend({
+ \ 'base': l:base,
+ \ 'startcol': l:startcol,
+ \ }, l:ctx)
+
+ let l:mode = s:has_complete_info ? complete_info(['mode'])['mode'] : 'unknown'
+ if l:mode ==# '' || l:mode ==# 'eval' || l:mode ==# 'unknown'
+ let l:Preprocessor = empty(g:asyncomplete_preprocessor) ? function('s:default_preprocessor') : g:asyncomplete_preprocessor[0]
+ call l:Preprocessor(l:filter_ctx, l:matches_to_filter)
+ endif
+ endfunction
+
+ let s:pair = {
+ \ '"': '"',
+ \ '''': '''',
+ \}
+
+ function! s:default_preprocessor(options, matches) abort
+ let l:items = []
+ let l:startcols = []
+ for [l:source_name, l:matches] in items(a:matches)
+ let l:startcol = l:matches['startcol']
+ let l:base = a:options['typed'][l:startcol - 1:]
+ if has_key(s:sources[l:source_name], 'filter')
+ let l:result = s:sources[l:source_name].filter(l:matches, l:startcol, l:base)
+ let l:items += l:result[0]
+ let l:startcols += l:result[1]
+ else
+ if empty(l:base)
+ for l:item in l:matches['items']
+ call add(l:items, s:strip_pair_characters(l:base, l:item))
+ let l:startcols += [l:startcol]
+ endfor
+ elseif s:has_matchfuzzypos && g:asyncomplete_matchfuzzy
+ for l:item in matchfuzzypos(l:matches['items'], l:base, {'key':'word'})[0]
+ call add(l:items, s:strip_pair_characters(l:base, l:item))
+ let l:startcols += [l:startcol]
+ endfor
+ else
+ for l:item in l:matches['items']
+ if stridx(l:item['word'], l:base) == 0
+ call add(l:items, s:strip_pair_characters(l:base, l:item))
+ let l:startcols += [l:startcol]
+ endif
+ endfor
+ endif
+ endif
+ endfor
+
+ let a:options['startcol'] = min(l:startcols)
+
+ call asyncomplete#preprocess_complete(a:options, l:items)
+ endfunction
+
+ function! s:strip_pair_characters(base, item) abort
+ " Strip pair characters. If pre-typed text is '"', candidates
+ " should have '"' suffix.
+ let l:item = a:item
+ if has_key(s:pair, a:base[0])
+ let [l:lhs, l:rhs, l:str] = [a:base[0], s:pair[a:base[0]], l:item['word']]
+ if len(l:str) > 1 && l:str[0] ==# l:lhs && l:str[-1:] ==# l:rhs
+ let l:item = extend({}, l:item)
+ let l:item['word'] = l:str[:-2]
+ endif
+ endif
+ return l:item
+ endfunction
+
+ function! asyncomplete#preprocess_complete(ctx, items) abort
+ " TODO: handle cases where this is called asynchronsouly. Currently not supported
+ if s:should_skip() | return | endif
+
+ call asyncomplete#log('core', 'asyncomplete#preprocess_complete')
+
+ if asyncomplete#menu_selected()
+ call asyncomplete#log('core', 'asyncomplete#preprocess_complete', 'ignorning pum update due to menu selection')
+ return
+ endif
+
+ if (g:asyncomplete_auto_completeopt == 1)
+ setl completeopt=menuone,noinsert,noselect
+ endif
+
+ let l:startcol = a:ctx['startcol']
+ call asyncomplete#log('core', 'asyncomplete#preprocess_complete calling complete()', l:startcol, a:items)
+ if l:startcol > 0 " Prevent E578: Not allowed to change text here
+ call complete(l:startcol, a:items)
+ endif
+ endfunction
+
+ function! asyncomplete#menu_selected() abort
+ " when the popup menu is visible, v:completed_item will be the
+ " current_selected item
+ " if v:completed_item is empty, no item is selected
+ return pumvisible() && !empty(v:completed_item)
+ endfunction
+
+ function! s:notify_event_to_source(name, event, ctx) abort
+ try
+ if has_key(s:sources, a:name)
+ call s:sources[a:name].on_event(s:sources[a:name], a:ctx, a:event)
+ endif
+ catch
+ call asyncomplete#log('core', 's:notify_event_to_source', 'error', v:exception)
+ return
+ endtry
+ endfunction
--- /dev/null
+ " Find a nearest to a `path` parent directory `directoryname` by traversing the
+ " filesystem upwards
+ function! asyncomplete#utils#find_nearest_parent_directory(path, directoryname) abort
+ let l:relative_path = finddir(a:directoryname, a:path . ';')
+
+ if !empty(l:relative_path)
+ return fnamemodify(l:relative_path, ':p')
+ else
+ return ''
+ endif
+ endfunction
+
+ if exists('*matchstrpos')
+ function! asyncomplete#utils#matchstrpos(expr, pattern) abort
+ return matchstrpos(a:expr, a:pattern)
+ endfunction
+ else
+ function! asyncomplete#utils#matchstrpos(expr, pattern) abort
+ return [matchstr(a:expr, a:pattern), match(a:expr, a:pattern), matchend(a:expr, a:pattern)]
+ endfunction
+ endif
--- /dev/null
+ let s:callbacks = []
+
+ function! asyncomplete#utils#_on_change#textchangedp#init() abort
+ if exists('##TextChangedP')
+ call s:setup_if_required()
+ return {
+ \ 'name': 'TextChangedP',
+ \ 'register': function('s:register'),
+ \ 'unregister': function('s:unregister'),
+ \ }
+ else
+ return { 'name': 'TextChangedP', 'error': 'Requires vim with TextChangedP support' }
+ endif
+ endfunction
+
+ function! s:setup_if_required() abort
+ augroup asyncomplete_utils_on_change_text_changed_p
+ autocmd!
+ autocmd InsertEnter * call s:on_insert_enter()
+ autocmd InsertLeave * call s:on_insert_leave()
+ autocmd TextChangedI * call s:on_text_changed_i()
+ autocmd TextChangedP * call s:on_text_changed_p()
+ augroup END
+ endfunction
+
+ function! s:register(cb) abort
+ call add(s:callbacks , a:cb)
+ endfunction
+
+ function! s:unregister(obj, cb) abort
+ " TODO: remove from s:callbacks
+ endfunction
+
+ function! s:on_insert_enter() abort
+ let l:context = asyncomplete#context()
+ let s:previous_context = {
+ \ 'lnum': l:context['lnum'],
+ \ 'col': l:context['col'],
+ \ 'typed': l:context['typed'],
+ \ }
+ endfunction
+
+ function! s:on_insert_leave() abort
+ unlet! s:previous_context
+ endfunction
+
+ function! s:on_text_changed_i() abort
+ call s:maybe_notify_on_change()
+ endfunction
+
+ function! s:on_text_changed_p() abort
+ call s:maybe_notify_on_change()
+ endfunction
+
+ function! s:maybe_notify_on_change() abort
+ if !exists('s:previous_context')
+ return
+ endif
+ " We notify on_change callbacks only when the cursor position
+ " has changed.
+ " Unfortunatelly we need this check because in insert mode it
+ " is possible to have TextChangedI triggered when the completion
+ " context is not changed at all: When we close the completion
+ " popup menu via <C-e> or <C-y>. If we still let on_change
+ " do the completion in this case we never close the menu.
+ " Vim doesn't allow programmatically changing buffer content
+ " in insert mode, so by comparing the cursor's position and the
+ " completion base we know whether the context has changed.
+ let l:context = asyncomplete#context()
+ let l:previous_context = s:previous_context
+ let s:previous_context = {
+ \ 'lnum': l:context['lnum'],
+ \ 'col': l:context['col'],
+ \ 'typed': l:context['typed'],
+ \ }
+ if l:previous_context !=# s:previous_context
+ for l:Cb in s:callbacks
+ call l:Cb()
+ endfor
+ endif
+ endfunction
--- /dev/null
+ let s:callbacks = []
+
+ let s:change_timer = -1
+ let s:last_tick = []
+
+ function! asyncomplete#utils#_on_change#timer#init() abort
+ call s:setup_if_required()
+ return {
+ \ 'name': 'timer',
+ \ 'register': function('s:register'),
+ \ 'unregister': function('s:unregister'),
+ \ }
+ endfunction
+
+ function! s:setup_if_required() abort
+ augroup asyncomplete_utils_on_change_timer
+ autocmd!
+ autocmd InsertEnter * call s:on_insert_enter()
+ autocmd InsertLeave * call s:on_insert_leave()
+ autocmd TextChangedI * call s:on_text_changed_i()
+ augroup END
+ endfunction
+
+ function! s:register(cb) abort
+ call add(s:callbacks , a:cb)
+ endfunction
+
+ function! s:unregister(obj, cb) abort
+ " TODO: remove from s:callbacks
+ endfunction
+
+ function! s:on_insert_enter() abort
+ let s:previous_position = getcurpos()
+ call s:change_tick_start()
+ endfunction
+
+ function! s:on_insert_leave() abort
+ unlet s:previous_position
+ call s:change_tick_stop()
+ endfunction
+
+ function! s:on_text_changed_i() abort
+ call s:check_changes()
+ endfunction
+
+ function! s:change_tick_start() abort
+ if !exists('s:change_timer')
+ let s:last_tick = s:change_tick()
+ " changes every 30ms, which is 0.03s, it should be fast enough
+ let s:change_timer = timer_start(30, function('s:check_changes'), { 'repeat': -1 })
+ endif
+ endfunction
+
+ function! s:change_tick_stop() abort
+ if exists('s:change_timer')
+ call timer_stop(s:change_timer)
+ unlet s:change_timer
+ let s:last_tick = []
+ endif
+ endfunction
+
+ function! s:check_changes(...) abort
+ let l:tick = s:change_tick()
+ if l:tick != s:last_tick
+ let s:last_tick = l:tick
+ call s:maybe_notify_on_change()
+ endif
+ endfunction
+
+ function! s:maybe_notify_on_change() abort
+ " enter to new line or backspace to previous line shouldn't cause change trigger
+ let l:previous_position = s:previous_position
+ let s:previous_position = getcurpos()
+ if l:previous_position[1] ==# getcurpos()[1]
+ for l:Cb in s:callbacks
+ call l:Cb()
+ endfor
+ endif
+ endfunction
+
+ function! s:change_tick() abort
+ return [b:changedtick, getcurpos()]
+ endfunction
--- /dev/null
+ *asyncomplete.vim.txt* Async autocompletion for Vim 8 and Neovim.
+ *asyncomplete*
+
+
+ ===============================================================================
+ CONTENTS *asyncomplete-contents*
+
+ 1. Introduction |asyncomplete-introduction|
+ 2. Options |asyncomplete-options|
+ 3. Functions |asyncomplete-functions|
+ 4. Global vim configuration |asyncomplete-global-config|
+ 5. Known Issues |asyncomplete-known-issues|
+
+ ===============================================================================
+ 1. Introduction *asyncomplete-introduction*
+
+ Async autocompletion for Vim 8 and Neovim with |timers|.
+
+ This is inspired by https://github.com/roxma/nvim-complete-manager but written
+ in pure Vim Script.
+
+ ===============================================================================
+ 2. Options *asyncomplete-options*
+
+
+ g:asyncomplete_enable_for_all *g:asyncomplete_enable_for_all*
+
+ Type |Number|
+ Default: 1
+
+ Enable asyncomplete for all buffers. Can be overriden with
+ `b:asyncomplete_enable` on a per-buffer basis. Setting this to 0 prevents
+ asyncomplete from loading upon entering a buffer.
+
+ b:asyncomplete_enable *b:asyncomplete_enable*
+
+ Type |Number|
+ Default: 1
+
+ Setting this variable to 0 disables asyncomplete for the current buffer
+ and overrides `g:asyncomplete_enable_for_all` .
+
+ g:asyncomplete_auto_popup *g:asyncomplete_auto_popup*
+
+ Type: |Number|
+ Default: `1`
+
+ Automatically show the autocomplete popup menu as you start typing.
+
+ g:asyncomplete_log_file *g:asyncomplete_log_file*
+
+ Type: |String|
+ Default: null
+
+ Path to log file.
+
+ g:asyncomplete_popup_delay *g:asyncomplete_popup_delay*
+
+ Type: |Number|
+ Default: 30
+
+ Milliseconds to wait before opening the popup menu
+
+ g:asyncomplete_auto_completeopt *g:asyncomplete_auto_completeopt*
+
+ Type: |Number|
+ Default: 1
+
+ Set default `completeopt` options. These are `menuone,noinsert,noselect`.
+ This effectively overwrites what ever the user has in their config file.
+
+ Set to 0 to disable.
+
+ g:asyncomplete_preprocessor *g:asyncomplete_preprocessor*
+
+ Type: |Array| for zero or one |Function|
+ Default: []
+
+ Set a function to allow custom filtering or sorting.
+ Below example implements removing duplicates.
+
+ function! s:my_asyncomplete_preprocessor(options, matches) abort
+ let l:visited = {}
+ let l:items = []
+ for [l:source_name, l:matches] in items(a:matches)
+ for l:item in l:matches['items']
+ if stridx(l:item['word'], a:options['base']) == 0
+ if !has_key(l:visited, l:item['word'])
+ call add(l:items, l:item)
+ let l:visited[l:item['word']] = 1
+ endif
+ endif
+ endfor
+ endfor
+
+ call asyncomplete#preprocess_complete(a:options, l:items)
+ endfunction
+
+ let g:asyncomplete_preprocessor = [function('s:my_asyncomplete_preprocessor')]
+
+ Note:
+ asyncomplete#preprocess_complete() must be called synchronously.
+ Plans to support async preprocessing will be supported in the future.
+
+ context and matches in arguments in preprecessor function should be treated
+ as immutable.
+
+ g:asyncomplete_min_chars *g:asyncomplete_min_chars*
+
+ Type: |Number|
+ Default: 0
+
+ Minimum consecutive characters to trigger auto-popup. Overridden by buffer
+ variable if set (`b:asyncomplete_min_chars`)
+
+ g:asyncomplete_matchfuzzy *g:asyncomplete_matchfuzzy*
+
+ Type: |Number|
+ Default: `exists('*matchfuzzypos')`
+
+ Use |matchfuzzypos| to support fuzzy matching of 'word' when completing
+ items. Requires vim with `matchfuzzypos()` function to exists.
+
+ Set to `0` to disable fuzzy matching.
+
+ ===============================================================================
+ 3. Functions *asyncomplete-functions*
+
+ asyncomplete#close_popup() *asyncomplete#close_popup()*
+
+ Insert selected candidate and close popup menu.
+ Following example prevents popup menu from re-opening after insertion.
+ >
+ inoremap <expr> <C-y> pumvisible() ? asyncomplete#close_popup() : "\<C-y>"
+ <
+ asyncomplete#cancel_popup() *asyncomplete#cancel_popup()*
+
+ Cancel completion and close popup menu.
+ Following example prevents popup menu from re-opening after cancellation.
+ >
+ inoremap <expr> <C-e> pumvisible() ? asyncomplete#cancel_popup() : "\<C-e>"
+ <
+ asyncomplete#get_source_info({source-name}) *asyncomplete#get_source_info()*
+
+ Get the source configuration info as dict.
+ Below example implements a priority sort function.
+ >
+ function! s:sort_by_priority_preprocessor(options, matches) abort
+ let l:items = []
+ for [l:source_name, l:matches] in items(a:matches)
+ for l:item in l:matches['items']
+ if stridx(l:item['word'], a:options['base']) == 0
+ let l:item['priority'] =
+ \ get(asyncomplete#get_source_info(l:source_name),'priority',0)
+ call add(l:items, l:item)
+ endif
+ endfor
+ endfor
+
+ let l:items = sort(l:items, {a, b -> b['priority'] - a['priority']})
+
+ call asyncomplete#preprocess_complete(a:options, l:items)
+ endfunction
+
+ let g:asyncomplete_preprocessor = [function('s:sort_by_priority_preprocessor')]
+ <
+ asyncomplete#get_source_names() *asyncomplete#get_source_names()*
+
+ Get the registered source names list.
+
+ ===============================================================================
+ 4. Global vim configuration *asyncomplete-global-config*
+
+ If you notice messages like 'Pattern not found' or 'Match 1 of <N>' printed in
+ red colour in vim command line and in `:messages` history and you are annoyed
+ with them, try setting `shortmess` vim option in your `.vimrc` like so:
+ >
+ set shortmess+=c
+ <
+ See `:help shortmess` for details and description.
+
+ ===============================================================================
+ 5. Known Issues *asyncomplete-known-issues*
+
+ Builtin complete such as omni func, file func flickers and closes.
+ You need vim with patch v8.1.1068.
+ https://github.com/vim/vim/commit/fd133323d4e1cc9c0e61c0ce357df4d36ea148e3
+
+ ===============================================================================
+
+ vim:tw=78:ts=4:sts=4:sw=4:ft=help:norl:
--- /dev/null
+ if exists('g:asyncomplete_loaded')
+ finish
+ endif
+ let g:asyncomplete_loaded = 1
+
+ if get(g:, 'asyncomplete_enable_for_all', 1)
+ augroup asyncomplete_enable
+ au!
+ au BufEnter * if exists('b:asyncomplete_enable') == 0 | call asyncomplete#enable_for_buffer() | endif
+ augroup END
+ endif
+
+ let g:asyncomplete_manager = get(g:, 'asyncomplete_manager', 'asyncomplete#managers#vim#init')
+ let g:asyncomplete_change_manager = get(g:, 'asyncomplete_change_manager', ['asyncomplete#utils#_on_change#textchangedp#init', 'asyncomplete#utils#_on_change#timer#init'])
+ let g:asyncomplete_triggers = get(g:, 'asyncomplete_triggers', {'*': ['.', '>', ':'] })
+ let g:asyncomplete_min_chars = get(g:, 'asyncomplete_min_chars', 0)
+
+ let g:asyncomplete_auto_completeopt = get(g:, 'asyncomplete_auto_completeopt', 1)
+ let g:asyncomplete_auto_popup = get(g:, 'asyncomplete_auto_popup', 1)
+ let g:asyncomplete_popup_delay = get(g:, 'asyncomplete_popup_delay', 30)
+ let g:asyncomplete_log_file = get(g:, 'asyncomplete_log_file', '')
+ let g:asyncomplete_preprocessor = get(g:, 'asyncomplete_preprocessor', [])
+ let g:asyncomplete_matchfuzzy = get(g:, 'asyncomplete_matchfuzzy', exists('*matchfuzzypos'))
+
+ inoremap <silent> <expr> <Plug>(asyncomplete_force_refresh) asyncomplete#force_refresh()
--- /dev/null
+ {
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+ "extends": [
+ "config:base"
+ ]
+ }
--- /dev/null
+ set encoding=utf-8
+ call themis#option('recursive', 1)
+ call themis#helper('command').with(themis#helper('assert'))
--- /dev/null
+ Describe asyncomplete
+ End