diff --git a/bundle/README.md b/bundle/README.md index 771d5c8e3..e2095212e 100644 --- a/bundle/README.md +++ b/bundle/README.md @@ -22,3 +22,4 @@ In `bundle/` directory, there are two kinds of plugins: forked plugins without c - [deoplete-lsp](https://github.com/deoplete-plugins/deoplete-lsp/tree/6299a22bedfb4f814d95cb0010291501472f8fd0) - [nvim-cmp](https://github.com/hrsh7th/nvim-cmp/tree/3192a0c57837c1ec5bf298e4f3ec984c7d2d60c0) - [coc-neosnippet](https://github.com/notomo/cmp-neosnippet/tree/2d14526af3f02dcea738b4cea520e6ce55c09979) +- [deoplete](https://github.com/Shougo/deoplete.nvim/tree/1c40f648d2b00e70beb4c473b7c0e32b633bd9ae) diff --git a/bundle/deoplete.nvim/.github/FUNDING.yml b/bundle/deoplete.nvim/.github/FUNDING.yml new file mode 100644 index 000000000..b42f4afb1 --- /dev/null +++ b/bundle/deoplete.nvim/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: Shougo # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +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'] diff --git a/bundle/deoplete.nvim/.github/workflows/workflow.yaml b/bundle/deoplete.nvim/.github/workflows/workflow.yaml new file mode 100644 index 000000000..03b00ad67 --- /dev/null +++ b/bundle/deoplete.nvim/.github/workflows/workflow.yaml @@ -0,0 +1,64 @@ +name: Lint, Test & Coverage + +on: [push, pull_request] + +jobs: + test: + name: Python ${{ matrix.python-version }} - Neovim (${{ matrix.build }}) + + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: + - 3.7 + - 3.8 + - 3.9 + build: + - nightly + - stable + + steps: + - uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f test/requirements.txt ]; then pip install -r test/requirements.txt; fi + - name: Initialize Neovim + uses: rhysd/action-setup-vim@v1 + id: vim + with: + neovim: true + version: ${{ matrix.build }} + - name: Clone vim-themis + uses: actions/checkout@v2 + with: + repository: thinca/vim-themis + path: vim-themis + - name: Run Lint & Test + run: make --keep-going THEMIS_VIM=${{ steps.vim.outputs.executable }} test lint + + coverage: + name: Generate Codecav Report + + needs: test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f test/requirements.txt ]; then pip install -r test/requirements.txt; fi + - name: Generate Report + run: pytest --cov=./rplugin/python3/deoplete --cov=./test --cov-report=xml + - uses: codecov/codecov-action@v1 + with: + files: ./coverage.xml + functionalities: coveragepy, fix, gcov, search, xcode diff --git a/bundle/deoplete.nvim/.travis.yml b/bundle/deoplete.nvim/.travis.yml deleted file mode 100644 index a7e17d267..000000000 --- a/bundle/deoplete.nvim/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -dist: xenial - -language: python - -python: - - 3.6 - - 3.7 - -install: - - eval "$(curl -Ss https://raw.githubusercontent.com/neovim/bot-ci/master/scripts/travis-setup.sh) nightly-x64" - - make install - -env: - global: - - PATH=$HOME/neovim/bin:$PATH - - PYTEST_ADDOPTS=--cov rplugin/python3/deoplete - -script: - - make --keep-going test lint - - coverage report -m --skip-covered - - coverage xml - - bash <(curl -s https://codecov.io/bash) -X gcov -X coveragepy -X fix -X search -X xcode -f coverage.xml diff --git a/bundle/deoplete.nvim/README.md b/bundle/deoplete.nvim/README.md index d4aaae153..ef27bc3e3 100644 --- a/bundle/deoplete.nvim/README.md +++ b/bundle/deoplete.nvim/README.md @@ -2,10 +2,19 @@ > Dark powered asynchronous completion framework for neovim/Vim8 -[![Build Status](https://travis-ci.org/Shougo/deoplete.nvim.svg?branch=master)](https://travis-ci.org/Shougo/deoplete.nvim) +**Note**: The development of this plugin is finished. Accepts minor patches and +issues but no new features. +[ddc.vim](https://github.com/Shougo/ddc.vim) is the next generation auto +completion plugin. Consider migrating to it. + [![Join the chat at https://gitter.im/Shougo/deoplete.nvim](https://badges.gitter.im/Shougo/deoplete.nvim.svg)](https://gitter.im/Shougo/deoplete.nvim?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Doc](https://img.shields.io/badge/doc-%3Ah%20deoplete-orange.svg)](doc/deoplete.txt) +Please read [help](doc/deoplete.txt) for details. + +Note: If you need to understand what's different between deoplete and other +similar plugins, please read "deoplete-faq" section in the documentation. + Deoplete is the abbreviation of "dark powered neo-completion". It provides an extensible and asynchronous completion framework for neovim/Vim8. @@ -26,7 +35,7 @@ Here are some [completion sources](https://github.com/Shougo/deoplete.nvim/wiki/ ## Install **Note:** deoplete requires Neovim (0.3.0+ and of course, **latest** is -recommended) or Vim8 with Python 3.6.1+ and timers enabled. See +recommended) or Vim8.1 with Python 3.6.1+ and timers enabled. See [requirements](#requirements) if you aren't sure whether you have this. Note: deoplete requires msgpack package 1.0.0+. @@ -75,7 +84,8 @@ For manual installation(not recommended) deoplete requires Neovim or Vim8 with `if_python3`. -If `:echo has("python3")` returns `1`, then you have python 3 support; otherwise, see below. +If `:echo has("python3")` returns `1`, then you have python 3 support; +otherwise, see below. You can enable Python3 interface with pip: @@ -157,3 +167,5 @@ Deoplete for JavaScript ![Rust using rls](https://user-images.githubusercontent.com/1750795/38780764-8524b0b8-40a9-11e8-91bc-6e4148c398a3.png) ![Ruby dictionary completion](https://user-images.githubusercontent.com/1314340/44786516-5bb57a00-abcf-11e8-8687-492fa5f9f905.gif) + +![LanguageClient-neovim integration](https://user-images.githubusercontent.com/4245199/87716288-efd25f80-c7ae-11ea-8080-334d155b3155.png) diff --git a/bundle/deoplete.nvim/autoload/deoplete.vim b/bundle/deoplete.nvim/autoload/deoplete.vim index 7da471ad4..dfb65f632 100644 --- a/bundle/deoplete.nvim/autoload/deoplete.vim +++ b/bundle/deoplete.nvim/autoload/deoplete.vim @@ -72,7 +72,6 @@ function! deoplete#close_popup() abort return pumvisible() ? "\" : '' endfunction function! deoplete#smart_close_popup() abort - call deoplete#handler#_skip_next_completion() return pumvisible() ? "\" : '' endfunction function! deoplete#cancel_popup() abort @@ -88,3 +87,7 @@ endfunction function! deoplete#complete_common_string() abort return deoplete#mapping#_complete_common_string() endfunction +function! deoplete#can_complete() abort + return !empty(get(get(g:, 'deoplete#_context', {}), 'candidates', [])) + \ && deoplete#mapping#_can_complete() +endfunction diff --git a/bundle/deoplete.nvim/autoload/deoplete/custom.vim b/bundle/deoplete.nvim/autoload/deoplete/custom.vim index adb8bfe2d..dda0171e4 100644 --- a/bundle/deoplete.nvim/autoload/deoplete/custom.vim +++ b/bundle/deoplete.nvim/autoload/deoplete/custom.vim @@ -18,10 +18,10 @@ function! deoplete#custom#_init() abort let s:cached.source_vars = {} endfunction function! deoplete#custom#_init_buffer() abort - let b:custom = {} - let b:custom.option = {} - let b:custom.source_vars = {} - let b:custom.filter = {} + let b:deoplete_custom = {} + let b:deoplete_custom.option = {} + let b:deoplete_custom.source_vars = {} + let b:deoplete_custom.filter = {} endfunction function! deoplete#custom#_update_cache() abort @@ -65,11 +65,11 @@ function! deoplete#custom#_get() abort return s:custom endfunction function! deoplete#custom#_get_buffer() abort - if !exists('b:custom') + if !exists('b:deoplete_custom') call deoplete#custom#_init_buffer() endif - return b:custom + return b:deoplete_custom endfunction function! deoplete#custom#_get_source(source_name) abort @@ -92,8 +92,14 @@ function! deoplete#custom#_get_filetype_option(name, filetype, default) abort endif let option = s:cached.option[a:name] - let filetype = has_key(option, a:filetype) ? a:filetype : '_' - return get(option, filetype, a:default) + " Check filetype -> a.b filetype -> '_' + for filetype in [a:filetype] + split(a:filetype, '\.') + ['_'] + if has_key(option, filetype) + return option[filetype] + endif + endfor + + return a:default endfunction function! deoplete#custom#_get_source_vars(name) abort return get(s:cached.source_vars, a:name, {}) diff --git a/bundle/deoplete.nvim/autoload/deoplete/handler.vim b/bundle/deoplete.nvim/autoload/deoplete/handler.vim index 18e663388..db5e26116 100644 --- a/bundle/deoplete.nvim/autoload/deoplete/handler.vim +++ b/bundle/deoplete.nvim/autoload/deoplete/handler.vim @@ -14,7 +14,7 @@ function! deoplete#handler#_init() abort for event in [ \ 'InsertEnter', 'InsertLeave', \ 'BufReadPost', 'BufWritePost', - \ 'VimLeavePre', + \ 'VimLeavePre', 'FileType', \ ] call s:define_on_event(event) endfor @@ -26,11 +26,7 @@ function! deoplete#handler#_init() abort call s:define_completion_via_timer('InsertEnter') endif if deoplete#custom#_get_option('refresh_always') - if exists('##TextChangedP') - call s:define_completion_via_timer('TextChangedP') - else - call s:define_completion_via_timer('InsertCharPre') - endif + call s:define_completion_via_timer('TextChangedP') endif " Note: Vim 8 GUI(MacVim and Win32) is broken @@ -51,11 +47,7 @@ function! deoplete#handler#_do_complete() abort let context = g:deoplete#_context let event = get(context, 'event', '') if s:is_exiting() || v:insertmode !=# 'i' || s:check_input_method() - return - endif - - if !has_key(context, 'candidates') - \ || deoplete#util#get_input(context.event) !=# context.input + \ || !has_key(context, 'candidates') return endif @@ -65,6 +57,11 @@ function! deoplete#handler#_do_complete() abort let prev.candidates = context.candidates let prev.complete_position = context.complete_position let prev.linenr = line('.') + let prev.time = context.time + + if context.event ==# 'Manual' + let context.event = '' + endif let auto_popup = deoplete#custom#_get_option( \ 'auto_complete_popup') !=# 'manual' @@ -74,13 +71,10 @@ function! deoplete#handler#_do_complete() abort let auto_popup = v:true endif - if context.event ==# 'Manual' - let context.event = '' - elseif !exists('g:deoplete#_saved_completeopt') && auto_popup - call deoplete#mapping#_set_completeopt() - endif - if auto_popup + " Note: completeopt must be changed before complete() and feedkeys() + call deoplete#mapping#_set_completeopt(g:deoplete#_context.is_async) + call feedkeys("\_", 'i') endif endfunction @@ -108,16 +102,8 @@ function! deoplete#handler#_check_omnifunc(context) abort let prev.input = a:context.input let prev.candidates = [] - if &completeopt =~# 'noselect' - call deoplete#mapping#_set_completeopt() - call feedkeys("\\", 'in') - else - call deoplete#util#print_error( - \ 'omni_patterns feature is disabled.') - call deoplete#util#print_error( - \ 'You need to set "noselect" in completeopt option.') - endif - return 1 + call deoplete#mapping#_set_completeopt(v:true) + call feedkeys("\\", 'in') endif endfor endfor @@ -145,7 +131,7 @@ function! s:completion_timer_stop() abort unlet s:completion_timer endfunction -function! s:check_prev_completion(event) abort +function! deoplete#handler#_check_prev_completion(event) abort let prev = g:deoplete#_prev_completion if a:event ==# 'Async' || a:event ==# 'Update' || mode() !=# 'i' \ || empty(get(prev, 'candidates', [])) @@ -160,8 +146,6 @@ function! s:check_prev_completion(event) abort return endif - call deoplete#mapping#_set_completeopt() - let mode = deoplete#custom#_get_option('prev_completion_mode') let candidates = copy(prev.candidates) @@ -169,9 +153,9 @@ function! s:check_prev_completion(event) abort let input = input[prev.complete_position :] let escaped_input = escape(input, '~\.^$[]*') let pattern = substitute(escaped_input, '\w', '\\w*\0', 'g') - call filter(candidates, 'v:val.word =~? pattern') + call filter(candidates, { _, val -> val.word =~? pattern }) if mode ==# 'length' - call filter(candidates, 'len(v:val.word) > len(input)') + call filter(candidates, { _, val -> len(val.word) > len(input) }) endif elseif mode ==# 'mirror' " pass @@ -183,7 +167,7 @@ function! s:check_prev_completion(event) abort \ 'complete_position': prev.complete_position, \ 'candidates': candidates, \ } - call feedkeys("\+", 'i') + return 1 endfunction function! deoplete#handler#_async_timer_start() abort @@ -198,12 +182,26 @@ endfunction function! deoplete#handler#_completion_begin(event) abort call deoplete#custom#_update_cache() - if s:is_skip(a:event) + let auto_popup = deoplete#custom#_get_option( + \ 'auto_complete_popup') !=# 'manual' + let prev_input = get(g:deoplete#_context, 'input', '') + let cur_input = deoplete#util#get_input(a:event) + + let check_back_space = auto_popup + \ && cur_input !=# prev_input + \ && len(cur_input) + 1 ==# len(prev_input) + \ && stridx(prev_input, cur_input) == 0 + let refresh_backspace = deoplete#custom#_get_option('refresh_backspace') + + if s:is_skip(a:event) || (check_back_space && !refresh_backspace) let g:deoplete#_context.candidates = [] + let g:deoplete#_context.input = cur_input return endif - call s:check_prev_completion(a:event) + if auto_popup && deoplete#handler#_check_prev_completion(a:event) + call feedkeys("\+", 'i') + endif if a:event !=# 'Update' && a:event !=# 'Async' call deoplete#init#_prev_completion() @@ -211,6 +209,11 @@ function! deoplete#handler#_completion_begin(event) abort call deoplete#util#rpcnotify( \ 'deoplete_auto_completion_begin', {'event': a:event}) + + " For popup flicker + if check_back_space && empty(v:completed_item) + call feedkeys("\_", 'i') + endif endfunction function! s:is_skip(event) abort if a:event ==# 'TextChangedP' && !empty(v:completed_item) @@ -231,11 +234,19 @@ function! s:is_skip(event) abort return 1 endif + " Check nofile buffers + if &l:buftype =~# 'nofile' && bufname('%') !=# '[Command Line]' + let nofile_complete_filetypes = deoplete#custom#_get_option( + \ 'nofile_complete_filetypes') + if index(nofile_complete_filetypes, &l:filetype) < 0 + return 1 + endif + endif + let auto_complete = deoplete#custom#_get_option('auto_complete') if &paste \ || (a:event !=# 'Manual' && a:event !=# 'Update' && !auto_complete) - \ || (&l:completefunc !=# '' && &l:buftype =~# 'nofile') \ || v:insertmode !=# 'i' return 1 endif @@ -266,6 +277,11 @@ function! s:is_skip_prev_text(event) abort endfunction function! s:is_skip_text(event) abort let input = deoplete#util#get_input(a:event) + if !has('nvim') && iconv(iconv(input, 'utf-8', 'utf-16'), + \ 'utf-16', 'utf-8') !=# input + " In Vim8, invalid bytes brokes nvim-yarp. + return 1 + endif let lastchar = matchstr(input, '.$') let skip_multibyte = deoplete#custom#_get_option('skip_multibyte') @@ -280,12 +296,17 @@ function! s:is_skip_text(event) abort \ && displaywidth >= &l:textwidth if &l:formatoptions =~# '[ta]' \ || !empty(filter(deoplete#util#get_syn_names(), - \ "v:val ==# 'Comment'")) + \ { _, val -> val ==# 'Comment' })) \ || is_virtual return 1 endif endif + if a:event =~# '^TextChanged' && s:matched_indentkeys(input) !=# '' + call deoplete#util#indent_current_line() + return 1 + endif + let skip_chars = deoplete#custom#_get_option('skip_chars') return (a:event !=# 'Manual' && input !=# '' @@ -294,6 +315,32 @@ endfunction function! s:check_input_method() abort return exists('*getimstatus') && getimstatus() endfunction +function! s:matched_indentkeys(input) abort + if &l:indentexpr ==# '' + " Disable auto indent + return '' + endif + + " Note: check the last word + let checkstr = matchstr(a:input, '\w\+$') + + for word in filter(map(split(&l:indentkeys, ','), + \ { _, val -> matchstr(val, 'e\|=\zs.*') }), + \ { _, val -> val !=# '' && val =~# '\h\w*' }) + + if word ==# 'e' + let word = 'else' + endif + + let lastpos = len(a:input) - len(word) + if checkstr ==# word || (word =~# '^\W\+$' && + \ lastpos >= 0 && strridx(a:input, word) == lastpos) + return word + endif + endfor + + return '' +endfunction function! s:define_on_event(event) abort if !exists('##' . a:event) @@ -325,11 +372,27 @@ endfunction function! s:on_complete_done() abort if get(v:completed_item, 'word', '') ==# '' + \ || !has_key(g:deoplete#_context, 'complete_str') return endif call deoplete#handler#_skip_next_completion() + let max_used = 100 + let g:deoplete#_recently_used = insert( + \ g:deoplete#_recently_used, + \ tolower(v:completed_item.word), + \ ) + let min_pattern_length = deoplete#custom#_get_option('min_pattern_length') + if len(g:deoplete#_context['complete_str']) > min_pattern_length + let g:deoplete#_recently_used = insert( + \ g:deoplete#_recently_used, + \ tolower(g:deoplete#_context['complete_str']), + \ ) + endif + let g:deoplete#_recently_used = deoplete#util#uniq( + \ g:deoplete#_recently_used)[: max_used] + let user_data = get(v:completed_item, 'user_data', '') if type(user_data) !=# v:t_string || user_data ==# '' return diff --git a/bundle/deoplete.nvim/autoload/deoplete/init.vim b/bundle/deoplete.nvim/autoload/deoplete/init.vim index 95bbed5ef..611503df0 100644 --- a/bundle/deoplete.nvim/autoload/deoplete/init.vim +++ b/bundle/deoplete.nvim/autoload/deoplete/init.vim @@ -38,7 +38,7 @@ function! deoplete#init#_channel() abort return 1 endif - let python3 = get(g:, 'python3_host_prog', 'python3') + let python3 = expand(get(g:, 'python3_host_prog', 'python3'), 1) if !executable(python3) call deoplete#util#print_error( \ string(python3) . ' is not executable.') @@ -49,8 +49,8 @@ function! deoplete#init#_channel() abort call deoplete#util#print_error('deoplete requires nvim 0.3.0+.') return 1 endif - if !has('nvim') && v:version < 800 - call deoplete#util#print_error('deoplete requires Vim 8.0+.') + if !has('nvim') && v:version < 801 + call deoplete#util#print_error('deoplete requires Vim 8.1+.') return 1 endif @@ -112,6 +112,7 @@ function! s:init_internal_variables() abort call deoplete#init#_prev_completion() let g:deoplete#_context = {} + let g:deoplete#_recently_used = [] if !exists('g:deoplete#_logging') let g:deoplete#_logging = {} @@ -136,7 +137,7 @@ function! s:init_internal_variables() abort if deoplete#util#has_yarp() " Dummy call is needed to check exists() - call neovim_rpc#serveraddr() + silent! call neovim_rpc#serveraddr() if !exists('*neovim_rpc#serveraddr') call deoplete#util#print_error( \ 'deoplete requires vim-hug-neovim-rpc plugin in Vim.') @@ -220,7 +221,8 @@ function! s:check_custom_var(source_name, old_var, new_var) abort call deoplete#util#print_error( \ printf('%s is deprecated variable. '. - \ 'Please use deoplete#custom#var() instead.', a:old_var)) + \ 'Please use deoplete#custom#var("%s", "%s", {value}) instead.', + \ a:old_var, a:source_name, a:new_var)) call deoplete#custom#var(a:source_name, a:new_var, eval(a:old_var)) endfunction function! s:check_custom_option(old_var, new_var) abort @@ -230,36 +232,36 @@ function! s:check_custom_option(old_var, new_var) abort call deoplete#util#print_error( \ printf('%s is deprecated variable. '. - \ 'Please use deoplete#custom#option() instead.', a:old_var)) + \ 'Please use deoplete#custom#option("%s", {value}) instead.', + \ a:old_var, a:new_var)) call deoplete#custom#option(a:new_var, eval(a:old_var)) endfunction function! deoplete#init#_option() abort - " Note: HTML omni func use search(). return { \ 'auto_complete': v:true, \ 'auto_complete_delay': 0, \ 'auto_complete_popup': 'auto', - \ 'auto_refresh_delay': 100, - \ 'camel_case': v:false, + \ 'auto_refresh_delay': 20, \ 'candidate_marks': [], + \ 'overwrite_completeopt': v:true, \ 'check_stderr': v:true, \ 'complete_suffix': v:true, - \ 'ignore_case': &ignorecase, \ 'ignore_sources': {}, \ 'keyword_patterns': {'_': '[a-zA-Z_]\k*'}, \ 'max_list': 500, \ 'min_pattern_length': 2, - \ 'num_processes': 4, + \ 'num_processes': 1, + \ 'nofile_complete_filetypes': ['denite-filter'], \ 'omni_patterns': {}, \ 'on_insert_enter': v:true, \ 'on_text_changed_i': v:true, - \ 'prev_completion_mode': '', + \ 'prev_completion_mode': 'filter', \ 'profile': v:false, \ 'refresh_always': v:true, + \ 'refresh_backspace': v:true, \ 'skip_chars': ['(', ')'], \ 'skip_multibyte': v:false, - \ 'smart_case': &smartcase, \ 'sources': {}, \ 'trigger_key': v:char, \ 'yarp': v:false, @@ -272,6 +274,7 @@ function! deoplete#init#_prev_completion() abort \ 'linenr': -1, \ 'candidates': [], \ 'complete_position': -1, + \ 'time': reltime(), \ } endfunction diff --git a/bundle/deoplete.nvim/autoload/deoplete/mapping.vim b/bundle/deoplete.nvim/autoload/deoplete/mapping.vim index 84d4917a6..1ed2efca5 100644 --- a/bundle/deoplete.nvim/autoload/deoplete/mapping.vim +++ b/bundle/deoplete.nvim/autoload/deoplete/mapping.vim @@ -10,6 +10,12 @@ function! deoplete#mapping#_init() abort \ deoplete#mapping#_dummy('deoplete#mapping#_complete') inoremap + \ deoplete#mapping#_dummy('deoplete#mapping#_prev_complete') + + " Note: The dummy mappings may be inserted on other modes. + cnoremap _ + cnoremap + + noremap _ + noremap + endfunction function! deoplete#mapping#_dummy(func) abort return "\=".a:func."()\" @@ -35,16 +41,41 @@ function! s:check_completion_info(candidates) abort endif return 0 - let old_candidates = sort(map(copy(info.items), 'v:val.word')) - return sort(map(copy(a:candidates), 'v:val.word')) ==# old_candidates + let old_candidates = sort(map(copy(info.items), { _, val -> val.word })) + return sort(map(copy(a:candidates), + \ { _, val -> val.word })) ==# old_candidates +endfunction +function! deoplete#mapping#_can_complete() abort + let context = get(g:, 'deoplete#_context', {}) + return has_key(context, 'candidates') && has_key(context, 'event') + \ && has_key(context, 'input') + \ && !s:check_completion_info(context.candidates) + \ && &modifiable endfunction function! deoplete#mapping#_complete() abort - if !has_key(g:deoplete#_context, 'candidates') - \ || s:check_completion_info(g:deoplete#_context.candidates) - \ || !&modifiable + if !deoplete#mapping#_can_complete() + let g:deoplete#_context.candidates = [] return '' endif + if deoplete#util#get_input(g:deoplete#_context.event) + \ !=# g:deoplete#_context.input + " Use prev completion instead + if deoplete#handler#_check_prev_completion(g:deoplete#_context.event) + call feedkeys("\+", 'i') + endif + + return '' + endif + + let auto_popup = deoplete#custom#_get_option( + \ 'auto_complete_popup') !=# 'manual' + + if auto_popup + " Note: completeopt must be changed before complete() + call deoplete#mapping#_set_completeopt(g:deoplete#_context.is_async) + endif + " echomsg string(g:deoplete#_context) if empty(g:deoplete#_context.candidates) && deoplete#util#check_popup() " Note: call complete() to close the popup @@ -62,20 +93,34 @@ function! deoplete#mapping#_prev_complete() abort return '' endif + let auto_popup = deoplete#custom#_get_option( + \ 'auto_complete_popup') !=# 'manual' + + if auto_popup + " Note: completeopt must be changed before complete() + call deoplete#mapping#_set_completeopt(v:false) + endif + call complete(g:deoplete#_filtered_prev.complete_position + 1, \ g:deoplete#_filtered_prev.candidates) return '' endfunction -function! deoplete#mapping#_set_completeopt() abort - if exists('g:deoplete#_saved_completeopt') +function! deoplete#mapping#_set_completeopt(is_async) abort + if !deoplete#custom#_get_option('overwrite_completeopt') return endif - let g:deoplete#_saved_completeopt = &completeopt + + if !exists('g:deoplete#_saved_completeopt') + let g:deoplete#_saved_completeopt = &completeopt + endif set completeopt-=longest set completeopt+=menuone set completeopt-=menu - if &completeopt !~# 'noinsert\|noselect' + if &completeopt !~# 'noinsert\|noselect' || a:is_async + " Note: If is_async, noselect is needed to prevent without confirmation + " problem + set completeopt-=noinsert set completeopt+=noselect endif endfunction @@ -117,16 +162,9 @@ function! deoplete#mapping#_complete_common_string() abort return '' endif - let complete_str = prev.input[prev.complete_position :] - let candidates = filter(copy(prev.candidates), - \ 'stridx(tolower(v:val.word), tolower(complete_str)) == 0') - - if empty(candidates) || complete_str ==# '' - return '' - endif - - let common_str = candidates[0].word - for candidate in candidates[1:] + let complete_str = deoplete#util#get_input('')[prev.complete_position :] + let common_str = prev.candidates[0].word + for candidate in prev.candidates[1:] while stridx(tolower(candidate.word), tolower(common_str)) != 0 let common_str = common_str[: -2] endwhile diff --git a/bundle/deoplete.nvim/autoload/deoplete/util.vim b/bundle/deoplete.nvim/autoload/deoplete/util.vim index 51292df3e..fd292167e 100644 --- a/bundle/deoplete.nvim/autoload/deoplete/util.vim +++ b/bundle/deoplete.nvim/autoload/deoplete/util.vim @@ -35,10 +35,6 @@ function! deoplete#util#get_input(event) abort \ '^.*\%' . (mode ==# 'i' ? col('.') : col('.') - 1) \ . 'c' . (mode ==# 'i' ? '' : '.')) - if a:event ==# 'InsertCharPre' - let input .= v:char - endif - return input endfunction function! deoplete#util#get_next_input(event) abort @@ -88,7 +84,7 @@ function! s:vimoption2python(option) abort endfunction function! deoplete#util#uniq(list) abort - let list = map(copy(a:list), '[v:val, v:val]') + let list = map(copy(a:list), { _, val -> [val, val] }) let i = 0 let seen = {} while i < len(list) @@ -100,7 +96,7 @@ function! deoplete#util#uniq(list) abort let i += 1 endif endwhile - return map(list, 'v:val[0]') + return map(list, { _, val -> val[0] }) endfunction function! deoplete#util#get_syn_names() abort @@ -183,8 +179,8 @@ endfunction " >0 if a > b " 0 if versions are equal. function! deoplete#util#versioncmp(a, b) abort - let a = map(split(a:a, '\.'), 'str2nr(v:val)') - let b = map(split(a:b, '\.'), 'str2nr(v:val)') + let a = map(split(a:a, '\.'), { _, val -> str2nr(val) }) + let b = map(split(a:b, '\.'), { _, val -> str2nr(val) }) let l = min([len(a), len(b)]) let d = 0 @@ -213,3 +209,17 @@ endfunction function! deoplete#util#check_popup() abort return exists('*complete_info') && complete_info().mode ==# 'eval' endfunction + +function! deoplete#util#indent_current_line() abort + let pos = getpos('.') + let len = len(getline('.')) + let equalprg = &l:equalprg + try + setlocal equalprg= + silent normal! == + finally + let &l:equalprg = equalprg + let pos[2] += len(getline('.')) - len + call setpos('.', pos) + endtry +endfunction diff --git a/bundle/deoplete.nvim/autoload/health/deoplete.vim b/bundle/deoplete.nvim/autoload/health/deoplete.vim index 973657054..c5100ecf6 100644 --- a/bundle/deoplete.nvim/autoload/health/deoplete.vim +++ b/bundle/deoplete.nvim/autoload/health/deoplete.vim @@ -59,11 +59,11 @@ function! s:still_have_issues() abort let indentation = ' ' call health#report_info("If you're still having problems, " . \ "try the following commands:\n" . - \ indentation . "$ export NVIM_PYTHON_LOG_FILE=/tmp/log\n" . - \ indentation . "$ export NVIM_PYTHON_LOG_LEVEL=DEBUG\n" . - \ indentation . "$ nvim\n" . - \ indentation . "$ cat /tmp/log_{PID}\n" . - \ indentation . ' and then create an issue on github' + \ indentation . "- $ export NVIM_PYTHON_LOG_FILE=/tmp/log\n" . + \ indentation . "- $ export NVIM_PYTHON_LOG_LEVEL=DEBUG\n" . + \ indentation . "- $ nvim\n" . + \ indentation . "- $ cat /tmp/log_{PID}\n" . + \ indentation . '- and then create an issue on github' \ ) endfunction diff --git a/bundle/deoplete.nvim/codecov.yml b/bundle/deoplete.nvim/codecov.yml deleted file mode 100644 index edccd5c35..000000000 --- a/bundle/deoplete.nvim/codecov.yml +++ /dev/null @@ -1,8 +0,0 @@ -coverage: - status: - project: false - patch: true - changes: true - -comment: - layout: "diff" diff --git a/bundle/deoplete.nvim/doc/deoplete.txt b/bundle/deoplete.nvim/doc/deoplete.txt index 1d5d3f9a6..d7a8ccaee 100644 --- a/bundle/deoplete.nvim/doc/deoplete.txt +++ b/bundle/deoplete.nvim/doc/deoplete.txt @@ -1,6 +1,6 @@ *deoplete.txt* Dark powered asynchronous completion framework for Neovim/Vim8 -Version: 6.0 +Version: 6.2 Author: Shougo License: MIT license @@ -30,14 +30,14 @@ Compatibility |deoplete-compatibility| INTRODUCTION *deoplete-introduction* *deoplete* is the abbreviation of "dark powered neo-completion". It -provides asynchronous keyword completion system in the +provides an asynchronous keyword completion system in the current buffer. Note: deoplete may consume more memory than other plugins do. -Improvements in deoplete in comparison to |neocomplete|: +Improvements in deoplete in comparison to neocomplete: -1. Real asynchronous completion behavior like |YouCompleteMe| by default. +1. Real asynchronous completion behavior like YouCompleteMe by default. 2. Uses Python3 to implement sources. 3. Removes legacy interface. 4. Requires |+python3|. @@ -45,8 +45,8 @@ Improvements in deoplete in comparison to |neocomplete|: ============================================================================== INSTALL *deoplete-install* -Note: deoplete requires Neovim (0.3.0+) or Vim8 (latest is recommended) with -Python 3.6.1+ and |+timers| enabled. +Note: deoplete requires Neovim (0.3.0+) or Vim8.1+ (latest is recommended) +with Python 3.6.1+ and |+timers| enabled. Please install/upgrade msgpack package (1.0.0+). https://github.com/msgpack/msgpack-python @@ -113,7 +113,7 @@ auto_complete auto_complete_delay Delay the completion after input in milliseconds. - Default value: 0 (milliseconds) + Default value: 20 (milliseconds) *deoplete-options-auto_complete_popup* auto_complete_popup @@ -131,21 +131,19 @@ auto_refresh_delay Default value: 100 (milliseconds) - *deoplete-options-camel_case* -camel_case - If it is True, lowercase letters are also matched with the - corresponding uppercase ones. - Ex: "foB" is matched with "FooBar" but not with "foobar". - Note: This feature is only available in - |deoplete-filter-matcher_fuzzy| or - |deoplete-filter-matcher_full_fuzzy|. - - Default value: v:false - *deoplete-options-candidate_marks* candidate_marks The candidate additional marks. + If set, this will be used to display an additional mark next + to the the first {N} candidates. The first mark is used for + the top ranked candidate, the second for the second candidate + and so forth. + + In the example below, the first 5 candidates are marked A..G, + which serve as mnemonics for commands to insert that + candidate. + Default value: [] > call deoplete#custom#option('candidate_marks', @@ -174,16 +172,11 @@ complete_suffix Default value: v:true - *deoplete-options-ignore_case* -ignore_case - If it is True, deoplete ignores case. - - Default value: same with your 'ignorecase' value - *deoplete-options-ignore_sources* ignore_sources It is a dictionary to decide ignore source names. The key is filetype and the value is source names list. + Note: It is disabled in |deoplete#manual_complete()|. Default value: {} @@ -209,15 +202,25 @@ max_list Default value: 500 + *deoplete-options-nofile_complete_filetypes* +nofile_complete_filetypes + If 'buftype' is "nofile", deoplete completion is disabled + automatically except the filetype list. + + Default value: ["denite-filter"] + *deoplete-options-num_processes* num_processes The number of processes used for the deoplete parallel - feature. + completion feature. + The parallel completion increases the completion speed, but it + increases the screen flicker. + If it is 1, this feature is disabled. If it is less than or equal to 0, the number of processes is equal to that of sources. - Default value: 4 + Default value: 1 *deoplete-options-omni_patterns* omni_patterns @@ -260,6 +263,14 @@ on_text_changed_i Deoplete enables the auto completion on |TextChangedI| autocmd if this value is True. + Default value: v:true + + *deoplete-options-overwrite_completeopt* +overwrite_completeopt + Deoplete overwrites 'completeopt' option. + You can disable the feature but if you change it, deoplete may + not work. + Default value: v:true *deoplete-options-profile* @@ -289,13 +300,22 @@ prev_completion_mode refresh_always Deoplete refreshes the candidates automatically if this value is True. - Note: It increases the screen flicker. + + Note: It increases the screen flicker when + |deoplete-options-num_processes| != 0. + + Default value: v:true + + *deoplete-options-refresh_backspace* +refresh_backspace + Deoplete refreshes the candidates automatically when you + press or key. Default value: v:true *deoplete-options-skip_multibyte* skip_multibyte - Deoplete skip multibyte text completion automatically if this + Deoplete skips multibyte text completion automatically if this value is True. Default value: v:false @@ -306,20 +326,13 @@ skip_chars Default value: ['(', ')'] - *deoplete-options-smart_case* -smart_case - When a capital letter is included in input, deoplete does - not ignore the upper- and lowercase. - - Default value: same with your 'smartcase' value - *deoplete-options-sources* sources It is a dictionary to specify source names. The key is - filetype and the value is source names list. If the key is - "_", the value will be used for default filetypes. For - example, you can load some sources in C++ filetype. - If the value is [], it will load all sources. + filetype and the value is a list of source names. If the key + is "_", the value will be used for default filetypes. For + example, you can load some sources in C++ filetype. If the + value is [], it will load all sources. Default value: {} > @@ -339,7 +352,7 @@ min_pattern_length Default: 2 - *deoplete-options-yarp* + *deoplete-options-yarp* yarp Use nvim-yarp library instead of neovim remote plugin feature. Note: nvim-yarp plugin is needed. @@ -470,7 +483,7 @@ deoplete#custom#source({source-name}, {dict}) " Disable the candidates in Comment/String syntaxes. call deoplete#custom#source('_', - \ 'disabled_syntaxes', ['Comment', 'String']) + \ 'disabled_syntaxes', ['Comment', 'String', 'Constant']) " Change the truncate width. call deoplete#custom#source('javacomplete2', @@ -492,7 +505,8 @@ deoplete#custom#source({source-name}, {dict}) " Enable jedi source debug messages " call deoplete#custom#option('profile', v:true) " call deoplete#enable_logging('DEBUG', 'deoplete.log') - " call deoplete#custom#source('jedi', 'is_debug_enabled', 1) + " call deoplete#custom#source('jedi', + \ 'is_debug_enabled', v:true) < *deoplete#custom#var()* deoplete#custom#var({source-name}, {var-name}, {value}) @@ -510,9 +524,14 @@ KEY MAPPINGS *deoplete-key-mappings* deoplete#auto_complete([{event}]) It calls the auto completion of deoplete. You can use it to call auto completion again. - {event} is autocmd event name. If it is omit, "Async" is + {event} is autocmd event name. If it is omitted, "Async" is used. + *deoplete#can_complete()* +deoplete#can_complete() + Return v:true if current word completion is available. + Note: |deoplete-options-auto_complete_popup| must be "manual". + *deoplete#close_popup()* deoplete#close_popup() Insert candidate and close popup menu for deoplete. @@ -525,18 +544,18 @@ deoplete#complete() *deoplete#complete_common_string()* deoplete#complete_common_string() - complete common string in candidates. It will be convenient - when candidates have long common string. + complete common string in candidates. + This can be useful when candidates have a long common prefix. Note: It must be in |:map-|. *deoplete#insert_candidate()* -deoplete#insert_candidate({number}) - Insert {number}th candidate. +deoplete#insert_candidate({index}) + Insert the candidate at index {index}. Indices start at 0. Note: It must be in |:map-|. *deoplete#manual_complete()* deoplete#manual_complete([{sources}]) - It calls the completion of deoplete. You can use it with + Trigger deoplete completion. You can use it with custom completion setups. You can provide a list of {sources}: It can be the name of a source or a list of sources name. @@ -544,16 +563,16 @@ deoplete#manual_complete([{sources}]) Note: It must be in |:map-|. If you want to trigger deoplete manually, see also - |deoplete-options-auto_complete|, which should be 1 then - typically. + |deoplete-options-auto_complete|, which should typically then + be 1. > inoremap \ pumvisible() ? "\" : \ check_back_space() ? "\" : \ deoplete#manual_complete() function! s:check_back_space() abort "{{{ - let col = col('.') - 1 - return !col || getline('.')[col - 1] =~ '\s' + let col = col('.') - 1 + return !col || getline('.')[col - 1] =~ '\s' endfunction"}}} < *deoplete#smart_close_popup()* @@ -566,13 +585,12 @@ deoplete#smart_close_popup() inoremap \ deoplete#smart_close_popup()."\" < - Note: This mapping conflicts with |SuperTab| or |endwise| - plugins. + Note: This mapping conflicts with SuperTab or endwise plugins. Note: This key mapping is for or keymappings. *deoplete#undo_completion()* deoplete#undo_completion() - Undo inputted candidate. + Undo inserted candidate. Note: It must be in |:map-|. > inoremap deoplete#undo_completion() @@ -583,7 +601,7 @@ EXAMPLES *deoplete-examples* " Use deoplete. let g:deoplete#enable_at_startup = 1 " Use smartcase. - call deoplete#custom#option('smart_case', v:true) + call deoplete#custom#source('_', 'smart_case', v:true) " : close popup and save indent. inoremap =my_cr_function() @@ -656,15 +674,21 @@ file *deoplete-source-file* Source custom variables: enable_buffer_path If it is True, file source completes the files - from the buffer path instead of the current - directory. + from the buffer directory instead of the + current directory. (default: v:true) + enable_slash_completion + If it is True, file source completes the files + when user input "/". + (default: v:false) + force_completion_length The completion length if the input does not contain "/". If it is less than 0, it is disabled. (default: -1) + member *deoplete-source-member* This source collects members from current buffer. @@ -706,7 +730,7 @@ omni *deoplete-source-omni* setting. (default: {}) > - call deoplete#custom#source('omni', 'functions', { + call deoplete#custom#var('omni', 'functions', { \ 'javascript': ['tern#Complete', 'jspc#omni'] \}) < @@ -730,6 +754,10 @@ omni *deoplete-source-omni* ============================================================================== FILTERS *deoplete-filters* +Once candidates have been supplied by one or more sources, they are passed +through the filters, which are matchers, converters or sorters. Sources can +have specific filters. + *deoplete-filter-matcher_default* Default matchers: ['matcher_fuzzy'] @@ -778,9 +806,13 @@ matcher_length Length matching matcher. It removes candidates shorter than or equal to the user input. + *deoplete-filter-matcher_matchfuzzy* +matcher_matchfuzzy + |matchfuzzy()| matcher. + *deoplete-filter-sorter_rank* sorter_rank Matched rank order sorter. The higher the head matched word - or already typed word. + or already typed or inserted word. The locality bonus feature is implemented like VSCode. *deoplete-filter-sorter_word* @@ -889,8 +921,7 @@ converter_reorder_attr *deoplete-filter-converter_auto_paren* converter_auto_paren It adds parentheses character in a candidate's word. - It is useful if you use |neopairs| or |neosnippet| - plugins. + It is useful if you use neopairs or neosnippet plugins. *deoplete-filter-converter_case* converter_case @@ -920,7 +951,10 @@ converter_truncate_info converter_truncate_menu It truncates a candidate's menu by the current window width. -============================================================================== + *deoplete-filter-converter_word_abbr* +converter_word_abbr + Convert candidate word to abbr. + CREATE SOURCE *deoplete-create-source* To create source, you should read default sources implementation in @@ -928,7 +962,7 @@ rplugin/python3/deoplete/source/*.py. The files are automatically loaded and deoplete creates new Source class object. -Source class must extend Base class in ".base". +Source class must extend the Base class in ".base". Note: The sources must be written in Python3 language. @@ -946,9 +980,20 @@ __init__ (Function) *deoplete-source-attribute-__* __{name} (Unspecified) (Optional) Additional source information. - Note: Recommend sources save variables instead of + Note: Sources should save variables instead of global variables. + *deoplete-source-attribute-camel_case* +camel_case + If it is True, lowercase letters are also matched with the + corresponding uppercase ones. + Ex: "foB" is matched with "FooBar" but not with "foobar". + Note: This feature is only available in + |deoplete-filter-matcher_fuzzy| or + |deoplete-filter-matcher_full_fuzzy|. + + Default value: v:false + *deoplete-source-attribute-converters* converters (List[str]) (Optional) Source default converters list. @@ -989,40 +1034,58 @@ filetypes (List[str]) (Optional) Available filetype list. Default: [] - Note: It means this source available in all filetypes. + Note: It means this source is available for all filetypes. *deoplete-source-attribute-gather_candidates* gather_candidates (Function) (Required) It is called to gather candidates. - It takes {self} and {context} as its parameter and returns a + It takes {self} and {context} as parameters and returns a list of {candidate}. - If the error is occurred, it must return None. + If an error occurrs, it must return None. {candidate} must be String or Dictionary contains |deoplete-candidate-attributes|. Here, {context} is the context information when the source is called (|deoplete-notation-{context}|). + Note: The source must not filter the candidates by user input. - It is |deoplete-filters| work. If the source filter the - candidates, user cannot filter the candidates by fuzzy match. + Instead, let the |deoplete-filters| match and sort the + results. If the source filters the candidates, the user + cannot then filter the candidates by fuzzy match. + Instead, let the |deoplete-filters| match and sort the + results. If the source filters the candidates, the user + cannot then filter the candidates by fuzzy match. + candidates, the user cannot then filter the candidates by + fuzzy match. *deoplete-source-attribute-get_complete_position* get_complete_position (Function) (Optional) - It is called to get complete position. + It is called to get the position of the current completion. It takes {self} and {context} as its parameter and returns - complete position in current line. - Here, {context} is the context information when the source is + a number representing the starting position of the completion + in the current line. + + Here, {context} is the context information from when the + source is called (|deoplete-notation-{context}|). called (|deoplete-notation-{context}|). Default: position using |deoplete-options-keyword_patterns|. Note: If |deoplete-source-attribute-is_bytepos| is True, it must return byte position. + This is useful if you want to complete terms that are more. + + *deoplete-source-attribute-ignore_case* +ignore_case + If it is True, deoplete ignores case. + + Default value: v:false + *deoplete-source-attribute-input_pattern* input_pattern (String) (Optional) - If it is matched with input, deoplete ignores + If it matches the input, deoplete ignores |deoplete-source-attribute-min_pattern_length|. It is useful for omni function sources. Note: It is Python3 regexp. @@ -1036,7 +1099,7 @@ input_patterns The dictionary version of |deoplete-source-attribute-input_pattern|. A key is filetype and a value is input pattern. - Note: It is Python3 regexp. + Note: Values are Python3 regexps. *deoplete-source-attribute-is_bytepos* is_bytepos @@ -1045,7 +1108,7 @@ is_bytepos |deoplete-source-attribute-get_complete_position| returns byteposition instead of character position. It is useful for Vim script to create sources because Vim - script string uses byte position. Python string uses + script string uses byte position. Python strings use character position. Default: False @@ -1067,7 +1130,7 @@ is_silent *deoplete-source-attribute-is_skip_langmap* is_skip_langmap (Bool) (Optional) - If it is True, the source skips the |langmap| completion. + If it is True, the source skips the 'langmap' completion. Default: True @@ -1087,6 +1150,11 @@ is_volatile *deoplete-source-attribute-mark* mark (String) (Optional) The mark of a source. + Note: If the source set candidate menu, the source must set + it. If the attribute is empty string, the candidate menu will + be disabled. + + Default: '' *deoplete-source-attribute-matchers* matchers (List[str]) (Optional) @@ -1104,44 +1172,43 @@ matcher_key (String) (Optional) *deoplete-source-attribute-max_abbr_width* max_abbr_width (Integer) (Optional) - If the candidate abbr length exceeds the length it will be cut + If the candidate abbr length exceeds this value, it will be + cut down. down. If it is less than or equal to 0, it will be disabled. Default: 80 - *deoplete-source-attribute-max_candidates* max_candidates (Integer) (Optional) - If the candidates are more than it, deoplete will ignore the - filtering. + If there are more candidates than this value, deoplete will + ignore the filtering. Default: 500 *deoplete-source-attribute-max_kind_width* max_kind_width (Integer) (Optional) - If the candidate kind length exceeds the length it will be cut - down. - If it is less than or equal to 0, it will be disabled. + If the candidate kind length exceeds this value it will be + trimmed. If this value is less than or equal to 0, it will be + disabled. Default: 40 *deoplete-source-attribute-max_info_width* max_info_width (Integer) (Optional) - If the candidate info length exceeds the length it will be cut - down. - If it is less than or equal to 0, it will be disabled. + If the candidate info length exceeds this value it is trimmed. + If this value is less than or equal to 0, it will be disabled. Default: 200 *deoplete-source-attribute-max_menu_width* max_menu_width (Integer) (Optional) - If the candidate menu length exceeds the length it will be cut + If the candidate menu length exceeds this value it is trimmed. down. - If it is less than or equal to 0, it will be disabled. + If this value is less than or equal to 0, it will be disabled. Default: 40 @@ -1199,6 +1266,13 @@ rank (Integer) (Optional) Default: 100 + *deoplete-source-attribute-smart_case* +smart_case + When a capital letter is included in input, deoplete does + not ignore the upper- and lowercase. + + Default value: v:false + *deoplete-source-attribute-sorters* sorters (List[str]) (Optional) Source default sorters list. @@ -1249,6 +1323,9 @@ vars (Dictionary) (Optional) The input string of the current line, namely the part before the cursor. + is_refresh (Bool) + If the input is changed, it will be "True". + is_async (Bool) If the gather is asynchronous, the source must set it to "True". A typical strategy for an asynchronous @@ -1287,10 +1364,6 @@ vars (Dictionary) (Optional) context['is_async'] = self._count < 10 return [context['input'].split()[-1] + str(self._count)] < - is_refresh (Bool) - If the input is changed, it will be "True". - - ------------------------------------------------------------------------------ CANDIDATE ATTRIBUTES *deoplete-candidate-attributes* @@ -1333,6 +1406,9 @@ The files are automatically loaded and deoplete creates new Filter class object. Filter class must extend Base class in ".base". +Matchers, sorters and converters are all kinds of filters, and should all +be placed in the same filter directory. + Note: The filters must be written in Python3 language. ------------------------------------------------------------------------------ @@ -1362,6 +1438,8 @@ filter ============================================================================== EXTERNAL SOURCES *deoplete-external-sources* +Please see https://github.com/Shougo/deoplete.nvim/wiki/Completion-Sources + neco-vim: "vim" source for Vim script https://github.com/Shougo/neco-vim @@ -1374,124 +1452,11 @@ https://github.com/Shougo/neoinclude.vim neco-syntax: "syntax" source https://github.com/Shougo/neco-syntax -vimshell: "vimshell" source for vimshell -https://github.com/Shougo/vimshell.vim - -neco-ghc: "ghc" source for Haskell -https://github.com/eagletmt/neco-ghc - -neco-look: "look" source to suggest words from sorted dictionary -https://github.com/ujihisa/neco-look - -vim-racer: "racer" source for Rust -https://github.com/racer-rust/vim-racer - -UltiSnips source: "ultisnips" source for UltiSnips -https://github.com/SirVer/ultisnips - -clang-complete: "clang_complete" source for C/C++/Objective-C -https://github.com/Rip-Rip/clang_complete - -deoplete-go: "go" source for Go -https://github.com/deoplete-plugins/deoplete-go - -elixir.nvim: "elixir" source for Elixir -https://github.com/awetzel/elixir.nvim - -deoplete-jedi: "jedi" source for Python -https://github.com/deoplete-plugins/deoplete-jedi - -perlomni.vim: "PerlOmni" source for Perl -https://github.com/c9s/perlomni.vim - -nvim-typescript: "typescript" source for typescript -https://github.com/mhartington/nvim-typescript - -async-clj-omni: "async_clj" source for Clojure -https://github.com/SevereOverfl0w/async-clj-omni - -deoplete-ternjs: "ternjs" source for JavaScript -https://github.com/carlitux/deoplete-ternjs - -deoplete-swift: "swift" source for Swift -https://github.com/landaire/deoplete-swift - -neovim-intellij-complete-deoplete: "intellij" source for Intellij IDE -https://github.com/vhakulinen/neovim-intellij-complete-deoplete - -tmux-complete: "tmuxcomplete" source for tmux panes -https://github.com/wellle/tmux-complete.vim - -deoplete-github: "github" source for "gitcommit" filetype -https://github.com/SevereOverfl0w/deoplete-github - -deoplete-flow: "flow" source for JavaScript -https://github.com/steelsojka/deoplete-flow - -deoplete-d: "d" source for D language -https://github.com/landaire/deoplete-d - -deoplete-rtags: "rtags" source for "c", "cpp", "objc" and "objcpp" filetypes -https://github.com/LuXuryPro/deoplete-rtags - -deoplete-solargraph "solargraph" source for Ruby language -https://github.com/uplus/deoplete-solargraph - -deoplete-padawan: "padawan" source for padawan.php -https://github.com/pbogut/deoplete-padawan - -webcomplete.vim: "webcomplete" source for browser opened pages -https://github.com/thalesmello/webcomplete.vim - -deoplete-julia: "julia" source for Julia -https://github.com/JuliaEditorSupport/deoplete-julia - -acid.nvim: "acid" source for Clojure -https://github.com/hkupty/acid.nvim - -deoplete-omnisharp: "cs" source for C# -https://github.com/Robzz/deoplete-omnisharp/ - -deoplete-omnisharp: Improved version of deoplete-omnisharp -https://github.com/dimixar/deoplete-omnisharp/ - -deoplete-omnisharp: Embedded omnisharp server version of deoplete-omnisharp -https://github.com/cyansprite/deoplete-omnisharp - -deoplete-hack: "hack" source for Hack and PHP -https://github.com/zefei/deoplete-hack - -deoplete-laravel-plugin: "laravel-plugin" source for "php" and "blade" -filetypes -https://github.com/rafaelndev/deoplete-laravel-plugin - deoplete-zsh: "zsh" source for Zsh https://github.com/deoplete-plugins/deoplete-zsh -deoplete-fish: "fish" source for fish shell -https://github.com/ponko2/deoplete-fish - -deoplete-fsharp: "fsharp" source for F# -https://github.com/callmekohei/deoplete-fsharp - -autocomplete-flow: "flow" source for JavaScript -https://github.com/wokalski/autocomplete-flow - -deoplete-asm: "asm" source for Assembly Language -https://github.com/deoplete-plugins/deoplete-asm - -deoplete-abook: "abook" source for abook contacts -https://github.com/fszymanski/deoplete-abook - -deoplete-emoji: "emoji" source for emoji codes -https://github.com/fszymanski/deoplete-emoji - -LanguageClient-neovim: "LanguageClient" source for Language Server -Protocol(LSP) -https://github.com/autozimu/LanguageClient-neovim - -deoplete-vim-lsp: "lsp" source for vim-lsp -https://github.com/lighttiger2505/deoplete-vim-lsp +deoplete-lsp source for neovim builtin LSP features +https://github.com/deoplete-plugins/deoplete-lsp deoplete-tags: "tag" source for tag files https://github.com/deoplete-plugins/deoplete-tag @@ -1617,7 +1582,7 @@ A: Please enable logging feature like this. > call deoplete#custom#option('profile', v:true) call deoplete#enable_logging('DEBUG', 'deoplete.log') - call deoplete#custom#source('jedi', 'is_debug_enabled', 1) + call deoplete#custom#source('jedi', 'is_debug_enabled', v:true) Q: "Channel id must be a positive integer" error. https://github.com/Shougo/deoplete.nvim/issues/406 @@ -1666,6 +1631,13 @@ Q: deoplete conflicts with lexima.vim A: > https://github.com/cohama/lexima.vim/issues/65#issuecomment-339338677 +Q: Vim's build-in completion behavior(|i_CTRL-P| etc) is changed after +deoplete is enabled. The first entry is not inserted. + +A: It is the feature of deoplete. The default insertion is disabled to enable +completion asynchronously. If you don't like the behavior, you should use +other auto completion plugin. + *deoplete-faq-config* 2. Configuration~ @@ -1675,11 +1647,9 @@ Q: I want to silence the |ins-completion-menu| messages in the command line A: You can disable the messages through the 'shortmess' option. > - if has("patch-7.4.314") - set shortmess+=c - endif + set shortmess+=c -Q: I want to use the auto select feature like |neocomplete|. +Q: I want to use the auto select feature like neocomplete. A: You can use it by the 'completeopt' option. > @@ -1747,6 +1717,11 @@ Q: How can I sort all entries alphabetically? A: > call deoplete#custom#source('_', 'sorters', ['sorter_word']) +Q: I want to disable all sources marks. + +A: > + call deoplete#custom#source('_', 'mark', '') + Q: I want to use head matcher instead of fuzzy matcher. A: You can achieve this by following > @@ -1848,10 +1823,10 @@ A: > inoremap \ pumvisible() ? "\" : \ check_back_space() ? "\" : - \ deoplete#complete() + \ deoplete#can_complete() ? deoplete#complete() : '' function! s:check_back_space() abort - let col = col('.') - 1 - return !col || getline('.')[col - 1] =~# '\s' + let col = col('.') - 1 + return !col || getline('.')[col - 1] =~# '\s' endfunction Q: Why I have to trigger |deoplete#complete()| at least twice for the popup to @@ -1876,6 +1851,15 @@ If you really need the feature, it works for me. > inoremap pumvisible() ? \ "\" . deoplete#complete() : "\" < + +Q: deoplete has the flicker issue when parallel completion feature is enabled. + +A: I recommend for you to disable |deoplete-options-refresh_always|option when +you enable deoplete parallel completion. > + + call deoplete#custom#option('num_processes', 4) + call deoplete#custom#option('refresh_always', v:false) + *deoplete-faq-ft-specific* 3. Filetype Specific Questions~ @@ -1888,7 +1872,7 @@ A: Please use |deoplete-options-auto_complete|. > Q: I want to use C/C++ omni completion with deoplete. -A: You should use |deoplete-clangx|. +A: You should use deoplete-clangx. https://github.com/Shougo/deoplete-clangx @@ -1963,10 +1947,10 @@ A: > Q: How to donate money to you? -A: I don't get the donation, but if you want to donate, please support neovim -project. My plugins depends on neovim development. +A: I have started github sponsorship to spend more time for Vim/neovim +plugins. You can donate money to help me! -https://salt.bountysource.com/teams/neovim +https://github.com/sponsors/Shougo Q: What means "dark powered"? @@ -2005,6 +1989,11 @@ A: deoplete is: If you don't like node.js based plugin or huge plugin base system, you should not choose coc.nvim. + completion-nvim is Lua based plugin for neovim. + You cannot use it in Vim8. + If you like Lua, you don't use Vim8, and you don't like external + dependency, it is a better choice. + And the important view is the author. If you like the author or the author created plugins, you should choose the auto completion plugin. @@ -2021,9 +2010,26 @@ A: deoplete is: I don't think deoplete is the best for everyone. Please choose auto completion plugin. +Q: deoplete with coc.nvim does not work. +https://github.com/Shougo/deoplete.nvim/issues/1192 + +A: Unfortunately, both deoplete and coc.nvim provide auto completion feature. + So deoplete conflicts with coc.nvim. + You cannot use both. You need to choose coc.nvim or deoplete. + Note: But to change |deoplete-options-overwrite_completeopt| may work for + you. + ============================================================================== COMPATIBILITY *deoplete-compatibility* +2021.05.30 +* "smart_case" and "ignore_case" and "camel_case" are source specific options + instead. + +2020.11.07 +* Disable nofile buffers completion except + deoplete-options-nofile_complete_filetypes. + 2020.04.26 * Add deprecated variables warnings. diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/__init__.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/__init__.py index 3c01644e2..6ee58f5fc 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/__init__.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/__init__.py @@ -4,19 +4,19 @@ # License: MIT license # ============================================================================ +from importlib.util import find_spec +from pynvim import Nvim import typing -from importlib.util import find_spec from deoplete.deoplete import Deoplete -from deoplete.util import Nvim - -if find_spec('yarp'): +try: + # For Vim8 import vim -elif find_spec('pynvim'): +except ModuleNotFoundError: + # For neovim + # Note: neovim cannot import vim module import pynvim as vim -else: - import neovim as vim Context = typing.Dict[str, typing.Any] @@ -26,8 +26,8 @@ if hasattr(vim, 'plugin'): @vim.plugin class DeopleteHandlers(object): - def __init__(self, vim: Nvim): - self._vim = vim + def __init__(self, _vim: Nvim): + self._vim = _vim @vim.function('_deoplete_init', sync=False) # type: ignore def init_channel(self, diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/base/filter.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/base/filter.py index 992777899..71eb8670f 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/base/filter.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/base/filter.py @@ -4,11 +4,12 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim import typing from abc import abstractmethod from deoplete.logger import LoggingMixin -from deoplete.util import error_vim, Nvim, UserContext, Candidates +from deoplete.util import error_vim, UserContext, Candidates class Base(LoggingMixin): @@ -22,14 +23,14 @@ class Base(LoggingMixin): def on_event(self, context: UserContext) -> None: pass - def get_var(self, var_name: str) -> typing.Optional[typing.Any]: + def get_var(self, var_name: str) -> typing.Any: custom_vars = self.vim.call( 'deoplete#custom#_get_filter', self.name) if var_name in custom_vars: return custom_vars[var_name] if var_name in self.vars: return self.vars[var_name] - return None + return '' @abstractmethod def filter(self, context: UserContext) -> Candidates: diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/base/source.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/base/source.py index 73be2ca54..09ae413fa 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/base/source.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/base/source.py @@ -4,12 +4,13 @@ # License: MIT license # ============================================================================ +from abc import abstractmethod +from pynvim import Nvim import re import typing -from abc import abstractmethod from deoplete.logger import LoggingMixin -from deoplete.util import debug, error_vim, Nvim, UserContext, Candidates +from deoplete.util import debug, error_vim, UserContext, Candidates class Base(LoggingMixin): @@ -51,6 +52,9 @@ class Base(LoggingMixin): self.max_candidates = 500 self.matcher_key = '' self.dup = False + self.ignore_case = False + self.smart_case = False + self.camel_case = False def get_complete_position(self, context: UserContext) -> int: m = re.search('(?:' + context['keyword_pattern'] + ')$|$', @@ -72,14 +76,14 @@ class Base(LoggingMixin): def on_event(self, context: UserContext) -> None: pass - def get_var(self, var_name: str) -> typing.Optional[typing.Any]: + def get_var(self, var_name: str) -> typing.Any: custom_vars = self.vim.call( 'deoplete#custom#_get_source_vars', self.name) if var_name in custom_vars: return custom_vars[var_name] if var_name in self.vars: return self.vars[var_name] - return None + return '' def get_filetype_var(self, filetype: str, var_name: str) -> typing.Optional[typing.Any]: diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/child.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/child.py index 9d3821b01..79ee53fb3 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/child.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/child.py @@ -4,24 +4,24 @@ # License: MIT license # ============================================================================ +from collections import defaultdict +from pathlib import Path +from pynvim import Nvim import copy -import os.path +import msgpack import re import sys import time -import msgpack import typing -from collections import defaultdict - from deoplete import logger from deoplete.exceptions import SourceInitError from deoplete.util import (bytepos2charpos, charpos2bytepos, error, error_tb, import_plugin, get_custom, get_syn_names, - convert2candidates, uniq_list_dict, Nvim) + convert2candidates, uniq_list_dict) UserContext = typing.Dict[str, typing.Any] -Candidates = typing.Dict[str, typing.Any] +Candidates = typing.List[typing.Dict[str, typing.Any]] Result = typing.Dict[str, typing.Any] @@ -75,7 +75,8 @@ class Child(logger.LoggingMixin): self._vim.call('deoplete#auto_complete', 'Update') def main(self, name: str, args: typing.List[typing.Any], - queue_id: typing.Optional[int]) -> typing.Optional[Candidates]: + queue_id: typing.Optional[int]) -> typing.Optional[ + typing.Dict[str, typing.Any]]: ret = None if name == 'enable_logging': self._enable_logging() @@ -103,6 +104,9 @@ class Child(logger.LoggingMixin): self.is_debug_enabled = True def _add_source(self, path: str) -> None: + # Resolve symbolic link + path = str(Path(path).resolve()) + source = None try: Source = import_plugin(path, 'source', 'Source') @@ -110,14 +114,15 @@ class Child(logger.LoggingMixin): return source = Source(self._vim) - name = os.path.splitext(os.path.basename(path))[0] + name = Path(path).stem source.name = getattr(source, 'name', name) source.path = path - if source.name in self._loaded_sources: + loaded_path = self._loaded_sources.get(source.name, '') + if source.name in self._loaded_sources and path != loaded_path: # Duplicated name error_tb(self._vim, 'Duplicated source: %s' % source.name) error_tb(self._vim, 'path: "%s" "%s"' % - (path, self._loaded_sources[source.name])) + (path, loaded_path)) source = None except Exception: error_tb(self._vim, 'Could not load source: %s' % path) @@ -129,6 +134,9 @@ class Child(logger.LoggingMixin): f'Loaded Source: {source.name} ({path})') def _add_filter(self, path: str) -> None: + # Resolve symbolic link + path = str(Path(path).resolve()) + f = None try: Filter = import_plugin(path, 'filter', 'Filter') @@ -136,14 +144,15 @@ class Child(logger.LoggingMixin): return f = Filter(self._vim) - name = os.path.splitext(os.path.basename(path))[0] + name = Path(path).stem f.name = getattr(f, 'name', name) f.path = path - if f.name in self._loaded_filters: + loaded_path = self._loaded_filters.get(f.name, '') + if f.name in self._loaded_filters and path != loaded_path: # Duplicated name error_tb(self._vim, 'Duplicated filter: %s' % f.name) error_tb(self._vim, 'path: "%s" "%s"' % - (path, self._loaded_filters[f.name])) + (path, loaded_path)) f = None except Exception: # Exception occurred when loading a filter. Log stack trace. @@ -230,6 +239,8 @@ class Child(logger.LoggingMixin): source.is_volatile, source.is_async)): return self._prev_results[source.name] + ctx['bufpath'] = context['bufpath'] + ctx['cwd'] = context['cwd'] ctx['is_async'] = False ctx['is_refresh'] = True ctx['max_abbr_width'] = min(source.max_abbr_width, @@ -248,6 +259,8 @@ class Child(logger.LoggingMixin): if ctx['max_menu_width'] > 0: ctx['max_menu_width'] = max(10, ctx['max_menu_width']) + self._set_context_case(source, ctx) + # Gathering self._profile_start(ctx, source.name) ctx['vars'] = self._vim.vars @@ -289,6 +302,19 @@ class Child(logger.LoggingMixin): except Exception as exc: self._handle_source_exception(source, exc) + def _set_context_case(self, source: typing.Any, + context: UserContext) -> None: + case = source.smart_case or source.camel_case + ignorecase = source.ignore_case + if case: + if re.search(r'[A-Z]', context['complete_str']): + ignorecase = False + else: + ignorecase = True + context['camelcase'] = source.camel_case + context['ignorecase'] = ignorecase + context['smartcase'] = source.smart_case + def _handle_source_exception(self, source: typing.Any, exc: Exception) -> None: if isinstance(exc, SourceInitError): @@ -330,8 +356,7 @@ class Child(logger.LoggingMixin): error_tb(self._vim, 'Errors from: %s' % f) def _get_candidates(self, result: Result, - context_input: str, next_input: str - ) -> typing.Optional[Candidates]: + context_input: str, next_input: str) -> Candidates: source = result['source'] # Gather async results @@ -339,7 +364,7 @@ class Child(logger.LoggingMixin): self._gather_async_results(result, source) if not result['candidates']: - return None + return [] # Source context ctx = copy.copy(result['context']) @@ -349,14 +374,7 @@ class Child(logger.LoggingMixin): ctx['complete_str'] = context_input[ctx['char_position']:] ctx['is_sorted'] = False - # Set ignorecase - case = ctx['smartcase'] or ctx['camelcase'] - if case: - if re.search(r'[A-Z]', ctx['complete_str']): - ctx['ignorecase'] = False - else: - ctx['ignorecase'] = True - ignorecase = ctx['ignorecase'] + self._set_context_case(source, ctx) # Match matchers = [self._filters[x] for x @@ -397,14 +415,19 @@ class Child(logger.LoggingMixin): for candidates in sorted_candidates: ctx['candidates'] += candidates - ctx['ignorecase'] = ignorecase - # On post filter if hasattr(source, 'on_post_filter'): ctx['candidates'] = source.on_post_filter(ctx) mark = source.mark + ' ' + # Check user mark set + user_mark = self._vim.call( + 'deoplete#custom#_get_source', source.name).get('mark', '') + if user_mark == '': + user_mark = self._vim.call( + 'deoplete#custom#_get_source', '_').get('mark', mark) + refresh = False refresh_always = self._vim.call( 'deoplete#custom#_get_option', 'refresh_always') @@ -418,10 +441,13 @@ class Child(logger.LoggingMixin): for candidate in ctx['candidates']: candidate['icase'] = 1 candidate['equal'] = refresh + candidate['source'] = source.name # Set default menu - if (mark != ' ' and - candidate.get('menu', '').find(mark) != 0): + if user_mark == '': + # Disable menu + candidate['menu'] = '' + elif mark != ' ' and candidate.get('menu', '').find(mark) != 0: candidate['menu'] = mark + candidate.get('menu', '') if source.dup: @@ -431,7 +457,7 @@ class Child(logger.LoggingMixin): # Remove duplicates ctx['candidates'] = uniq_list_dict(ctx['candidates']) - return ctx['candidates'] # type: ignore + return list(ctx['candidates']) def _itersource(self, context: UserContext ) -> typing.Generator[typing.Any, None, None]: @@ -443,7 +469,9 @@ class Child(logger.LoggingMixin): 'ignore_sources', ft, [])) for source_name, source in self._get_sources().items(): - if source.filetypes is None or source_name in ignore_sources: + if source.filetypes is None or ( + source_name in ignore_sources and + context['event'] != 'Manual'): continue if context['sources'] and source_name not in context['sources']: continue @@ -509,10 +537,10 @@ class Child(logger.LoggingMixin): context['input'].find(result['prev_input']) == 0) def _is_skip(self, context: UserContext, source: typing.Any) -> bool: - if 'syntax_names' in context and source.disabled_syntaxes: - p = re.compile('(' + '|'.join(source.disabled_syntaxes) + ')$') - if next(filter(p.search, context['syntax_names']), None): - return True + if (context.get('syntax_names', []) and source.disabled_syntaxes + and len(set(context['syntax_names']) & + set(source.disabled_syntaxes)) > 0): + return True iminsert = self._vim.call('getbufvar', '%', '&iminsert') if iminsert == 1 and source.is_skip_langmap: @@ -536,11 +564,14 @@ class Child(logger.LoggingMixin): Each item in `attrs` is the attribute name. """ attrs = ( + 'camel_case', 'converters', 'disabled_syntaxes', 'dup', 'filetypes', + 'ignore_case', 'input_pattern', + 'input_patterns', 'is_debug_enabled', 'is_silent', 'is_volatile', @@ -553,6 +584,7 @@ class Child(logger.LoggingMixin): 'max_menu_width', 'max_pattern_length', 'min_pattern_length', + 'smart_case', 'sorters', ) diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/context.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/context.py index 0983a402e..6706afd04 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/context.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/context.py @@ -4,11 +4,12 @@ # License: MIT license # ============================================================================ -import os +from pathlib import Path +from pynvim import Nvim import re import typing -from deoplete.util import Nvim +from deoplete.util import exists_path UserContext = typing.Dict[str, typing.Any] @@ -48,6 +49,7 @@ class Context(object): 'deoplete#util#get_next_input', event), 'position': self._vim.call('getpos', '.'), 'same_filetypes': same_filetypes, + 'time': self._vim.call('reltime'), } context.update(self._cached) # type: ignore @@ -79,26 +81,20 @@ class Context(object): bufname = self._vim.call('bufname', bufnr) cwd = self._vim.call('getcwd') buftype = self._vim.call('getbufvar', '%', '&buftype') - bufpath = (bufname if os.path.isabs(bufname) - else os.path.join(cwd, bufname)) - if not os.path.exists(bufpath) or 'nofile' in buftype: + bufpath = (bufname if Path(bufname).is_absolute() + else str(Path(cwd).joinpath(bufname))) + if not exists_path(bufpath) or 'nofile' in buftype: bufpath = '' self._cached = { 'bufnr': bufnr, 'bufname': bufname, 'bufpath': bufpath, - 'camelcase': self._vim.call( - 'deoplete#custom#_get_option', 'camel_case'), 'complete_str': '', 'custom': self._vim.call('deoplete#custom#_get'), 'cwd': cwd, 'encoding': self._vim.options['encoding'], - 'ignorecase': self._vim.call( - 'deoplete#custom#_get_option', 'ignore_case'), 'is_windows': self._vim.call('has', 'win32'), - 'smartcase': self._vim.call( - 'deoplete#custom#_get_option', 'smart_case'), } def _get_context_filetype(self, diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/deoplete.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/deoplete.py index 309d7eb46..65721d518 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/deoplete.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/deoplete.py @@ -4,15 +4,15 @@ # License: MIT license # ============================================================================ +from pathlib import Path +from pynvim import Nvim import copy -import glob -import os import typing import deoplete.parent from deoplete import logger from deoplete.context import Context -from deoplete.util import error, error_tb, Nvim +from deoplete.util import error, error_tb UserContext = typing.Dict[str, typing.Any] Candidates = typing.Dict[str, typing.Any] @@ -26,7 +26,6 @@ class Deoplete(logger.LoggingMixin): self._vim = vim self._runtimepath = '' - self._runtimepath_list: typing.List[str] = [] self._custom: typing.Dict[str, typing.Dict[str, typing.Any]] = {} self._loaded_paths: typing.Set[str] = set() self._prev_results: typing.Dict[int, Candidates] = {} @@ -70,6 +69,8 @@ class Deoplete(logger.LoggingMixin): def completion_begin(self, user_context: UserContext) -> None: if not self._context: self.init_context() + else: + self._context._init_cached() context = self._context.get(user_context['event']) # type: ignore context.update(user_context) @@ -96,24 +97,30 @@ class Deoplete(logger.LoggingMixin): if needs_poll: self._vim.call('deoplete#handler#_async_timer_start') - if not candidates: - self._vim.call('deoplete#mapping#_restore_completeopt') - - # Async update is skipped if same. prev_completion = self._vim.vars['deoplete#_prev_completion'] + + # Skip if async update is same. + # Note: If needs_poll, it cannot be skipped. prev_candidates = prev_completion['candidates'] event = context['event'] - if (event == 'Async' or event == 'Update' and - prev_candidates and candidates == prev_candidates): + same_candidates = prev_candidates and candidates == prev_candidates + if not needs_poll and same_candidates and ( + event == 'Async' or event == 'Update'): + return + + # Skip if old completion. + if context['time'] < prev_completion['time']: return # error(self._vim, candidates) self._vim.vars['deoplete#_context'] = { 'complete_position': position, + 'complete_str': context['input'][position:], 'candidates': candidates, 'event': context['event'], 'input': context['input'], - 'is_async': is_async, + 'time': context['time'], + 'is_async': needs_poll, } if candidates or self._vim.call('deoplete#util#check_popup'): @@ -165,9 +172,15 @@ class Deoplete(logger.LoggingMixin): def _merge_results(self, context: UserContext) -> typing.Tuple[ bool, bool, int, typing.List[typing.Any]]: + # If parallel feature is enabled, it is updated frequently. + # But if it is single process, it cannot be updated. + # So it must be updated. + async_check = len(self._parents) > 1 or ( + context['event'] != 'Async' and context['event'] != 'Update') use_prev = (context['input'] == self._prev_input and context['next_input'] == self._prev_next_input - and context['event'] != 'Manual') + and context['event'] != 'Manual' + and async_check) if not use_prev: self._prev_results = {} @@ -182,7 +195,8 @@ class Deoplete(logger.LoggingMixin): complete_position = min(x['complete_position'] for x in results) all_candidates: typing.List[Candidates] = [] - for result in sorted(results, key=lambda x: x['rank'], reverse=True): + for result in sorted(results, + key=lambda x: int(x['rank']), reverse=True): candidates = result['candidates'] prefix = context['input'][ complete_position:result['complete_position']] @@ -218,43 +232,38 @@ class Deoplete(logger.LoggingMixin): parent.enable_logging() self._parents.append(parent) - def _find_rplugins(self, - source: str) -> typing.Generator[str, None, None]: + def _find_rplugins(self, source: str) -> typing.List[Path]: """Search for base.py or *.py Searches $VIMRUNTIME/*/rplugin/python3/deoplete/$source[s]/ """ - if not self._runtimepath_list: - return - sources = ( - os.path.join('rplugin', 'python3', 'deoplete', - source, '*.py'), - os.path.join('rplugin', 'python3', 'deoplete', - source + 's', '*.py'), - os.path.join('rplugin', 'python3', 'deoplete', - source, '*', '*.py'), - ) - - for src in sources: - for path in self._runtimepath_list: - yield from glob.iglob(os.path.join(path, src)) + result = [] + result += self._vim.call( + 'globpath', self._vim.options['runtimepath'], + f'rplugin/python3/deoplete/{source}/*.py', 1, 1) + result += self._vim.call( + 'globpath', self._vim.options['runtimepath'], + f'rplugin/python3/deoplete/{source}s/*.py', 1, 1) + result += self._vim.call( + 'globpath', self._vim.options['runtimepath'], + f'rplugin/python3/deoplete/{source}/*/*.py', 1, 1) + return [Path(x) for x in result] def _load_sources(self, context: UserContext) -> None: if not self._parents and self._max_parents == 1: self._add_parent(deoplete.parent.SyncParent) for path in self._find_rplugins('source'): - if (path in self._loaded_paths - or os.path.basename(path) == 'base.py'): + if str(path) in self._loaded_paths or path.name == 'base.py': continue - self._loaded_paths.add(path) + self._loaded_paths.add(str(path)) if len(self._parents) <= self._parent_count: # Add parent automatically self._add_parent(deoplete.parent.AsyncParent) - self._parents[self._parent_count].add_source(path) + self._parents[self._parent_count].add_source(str(path)) self.debug( # type: ignore f'Process {self._parent_count}: {path}') @@ -267,7 +276,7 @@ class Deoplete(logger.LoggingMixin): def _load_filters(self, context: UserContext) -> None: for path in self._find_rplugins('filter'): for parent in self._parents: - parent.add_filter(path) + parent.add_filter(str(path)) def _set_source_attributes(self, context: UserContext) -> None: for parent in self._parents: @@ -277,7 +286,6 @@ class Deoplete(logger.LoggingMixin): runtimepath = self._vim.options['runtimepath'] if runtimepath != self._runtimepath: self._runtimepath = runtimepath - self._runtimepath_list = runtimepath.split(',') self._load_sources(context) self._load_filters(context) diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/base.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/base.py index 7b899604e..3ff2a2053 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/base.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/base.py @@ -4,9 +4,9 @@ # License: MIT license # ============================================================================ -# For backward compatibility +from pynvim import Nvim + from deoplete.base.filter import Base as _Base -from deoplete.util import Nvim class Base(_Base): diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_auto_delimiter.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_auto_delimiter.py index 5fdc3e3e5..4d45055bf 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_auto_delimiter.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_auto_delimiter.py @@ -4,10 +4,11 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim import typing from deoplete.base.filter import Base -from deoplete.util import Nvim, UserContext, Candidates +from deoplete.util import UserContext, Candidates class Filter(Base): @@ -21,8 +22,7 @@ class Filter(Base): } def filter(self, context: UserContext) -> Candidates: - delimiters: typing.List[str] = self.get_var( # type: ignore - 'delimiters') + delimiters: typing.List[str] = self.get_var('delimiters') for candidate, delimiter in [ [x, last_find(x['abbr'], delimiters)] for x in context['candidates'] @@ -30,7 +30,7 @@ class Filter(Base): not last_find(x['word'], delimiters) and last_find(x['abbr'], delimiters)]: candidate['word'] += delimiter - return context['candidates'] # type: ignore + return list(context['candidates']) def last_find(s: str, needles: typing.List[str]) -> typing.Optional[str]: diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_auto_paren.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_auto_paren.py index 047370f94..0e697fee4 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_auto_paren.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_auto_paren.py @@ -4,10 +4,11 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim import re from deoplete.base.filter import Base -from deoplete.util import Nvim, UserContext, Candidates +from deoplete.util import UserContext, Candidates class Filter(Base): @@ -26,4 +27,4 @@ class Filter(Base): (('abbr' in x and p2.search(x['abbr'])) or ('info' in x and p2.search(x['info'])))]: candidate['word'] += '(' - return context['candidates'] # type: ignore + return list(context['candidates']) diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_case.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_case.py index 4f36e8d6e..b6e60e725 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_case.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_case.py @@ -4,10 +4,11 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim import re from deoplete.base.filter import Base -from deoplete.util import Nvim, UserContext, Candidates +from deoplete.util import UserContext, Candidates class Filter(Base): @@ -20,7 +21,7 @@ class Filter(Base): def filter(self, context: UserContext) -> Candidates: complete_str = context['complete_str'] if not re.search(r'[A-Z]', complete_str): - return context['candidates'] # type: ignore + return list(context['candidates']) complete_lower = complete_str.lower() complete_len = len(complete_str) @@ -29,4 +30,4 @@ class Filter(Base): if x['word'].lower().startswith(complete_lower)]: candidate['word'] = complete_str + candidate[ 'word'][complete_len:] - return context['candidates'] # type: ignore + return list(context['candidates']) diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_remove_overlap.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_remove_overlap.py index 0146bbc1e..8b9fe7f9d 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_remove_overlap.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_remove_overlap.py @@ -4,11 +4,12 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim import re import typing from deoplete.base.filter import Base -from deoplete.util import Nvim, UserContext, Candidates +from deoplete.util import UserContext, Candidates class Filter(Base): @@ -20,26 +21,36 @@ class Filter(Base): def filter(self, context: UserContext) -> Candidates: if not context['next_input']: - return context['candidates'] # type: ignore + return list(context['candidates']) next_input_words = [x for x in re.split( r'([a-zA-Z_]+|\W)', context['next_input']) if x] + # Skip parentheses if close parentheses is found after cursor + cur_pos = self.vim.call('getcurpos')[1:3] check_pairs = [] - if self.vim.call('searchpair', '(', '', ')', 'bnw'): - check_pairs.append(['(', ')']) - if self.vim.call('searchpair', '[', '', ']', 'bnw'): - check_pairs.append(['[', ']']) + pair_pos = self.vim.call('searchpairpos', '(', '', ')', 'nW') + if ('(' in context['input'] and cur_pos < pair_pos and + cur_pos[0] == pair_pos[0]): + check_pairs.append(['(', ')', pair_pos]) + pair_pos = self.vim.call('searchpairpos', '[', '', ']', 'nW') + if ('[' in context['input'] and cur_pos < pair_pos and + cur_pos[0] == pair_pos[0]): + check_pairs.append(['[', ']', pair_pos]) for [overlap, candidate, word] in [ [x, y, y['word']] for x, y in [[overlap_length(x['word'], next_input_words), x] for x in context['candidates']] if x > 0]: - if [x for x in check_pairs if x[0] in word and x[1] in word]: + word_end_pos = (context['complete_position'] + + self.vim.call('len', word)) + if [x for x in check_pairs + if x[0] in word and x[1] in word[-overlap:] and + word_end_pos >= x[2][1]]: continue if 'abbr' not in candidate: candidate['abbr'] = word candidate['word'] = word[: -overlap] - return context['candidates'] # type: ignore + return list(context['candidates']) def overlap_length(left: str, next_input_words: typing.List[str]) -> int: diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_remove_paren.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_remove_paren.py index e62a7c279..3d56e16c1 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_remove_paren.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_remove_paren.py @@ -4,10 +4,11 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim import re from deoplete.base.filter import Base -from deoplete.util import Nvim, UserContext, Candidates +from deoplete.util import UserContext, Candidates class Filter(Base): @@ -22,4 +23,4 @@ class Filter(Base): if '(' in x['word']]: candidate['word'] = re.sub(r'\(.*\)(\$\d+)?', '', candidate['word']) - return context['candidates'] # type: ignore + return list(context['candidates']) diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_reorder_attr.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_reorder_attr.py index 308c75400..4271c811b 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_reorder_attr.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_reorder_attr.py @@ -4,11 +4,12 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim import re import typing from deoplete.base.filter import Base -from deoplete.util import Nvim, UserContext, Candidates +from deoplete.util import UserContext, Candidates class Filter(Base): @@ -63,10 +64,10 @@ class Filter(Base): return new_candidates def filter(self, context: UserContext) -> Candidates: - preferred_order_attrs = self.get_var( # type: ignore + preferred_order_attrs = self.get_var( 'attrs_order').get(context['filetype'], []) if not context['candidates'] or not preferred_order_attrs: - return context['candidates'] # type: ignore + return list(context['candidates']) max_list_size = self.vim.call( 'deoplete#custom#_get_option', 'max_list' diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_truncate_abbr.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_truncate_abbr.py index b25a1ec10..08d21b9d7 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_truncate_abbr.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_truncate_abbr.py @@ -4,8 +4,10 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim + from deoplete.base.filter import Base -from deoplete.util import truncate_skipping, Nvim, UserContext, Candidates +from deoplete.util import truncate_skipping, UserContext, Candidates class Filter(Base): @@ -18,11 +20,11 @@ class Filter(Base): def filter(self, context: UserContext) -> Candidates: max_width = context['max_abbr_width'] if max_width <= 0: - return context['candidates'] # type: ignore + return list(context['candidates']) footer_width = max_width / 3 for candidate in context['candidates']: candidate['abbr'] = truncate_skipping( candidate.get('abbr', candidate['word']), max_width, '..', footer_width) - return context['candidates'] # type: ignore + return list(context['candidates']) diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_truncate_info.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_truncate_info.py index fb85f5cfe..fb9964748 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_truncate_info.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_truncate_info.py @@ -4,8 +4,10 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim + from deoplete.base.filter import Base -from deoplete.util import truncate_skipping, Nvim, UserContext, Candidates +from deoplete.util import truncate_skipping, UserContext, Candidates class Filter(Base): @@ -18,11 +20,11 @@ class Filter(Base): def filter(self, context: UserContext) -> Candidates: max_width = context['max_info_width'] if not context['candidates'] or max_width <= 0: - return context['candidates'] # type: ignore + return list(context['candidates']) footer_width = 1 for candidate in context['candidates']: candidate['info'] = truncate_skipping( candidate.get('info', ''), max_width, '..', footer_width) - return context['candidates'] # type: ignore + return list(context['candidates']) diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_truncate_kind.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_truncate_kind.py index 2c7c5962b..a93c4bae8 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_truncate_kind.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_truncate_kind.py @@ -4,8 +4,10 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim + from deoplete.base.filter import Base -from deoplete.util import truncate_skipping, Nvim, UserContext, Candidates +from deoplete.util import truncate_skipping, UserContext, Candidates class Filter(Base): @@ -19,11 +21,11 @@ class Filter(Base): max_width = context['max_kind_width'] if not context['candidates'] or 'kind' not in context[ 'candidates'][0] or max_width <= 0: - return context['candidates'] # type: ignore + return list(context['candidates']) footer_width = max_width / 3 for candidate in context['candidates']: candidate['kind'] = truncate_skipping( candidate.get('kind', ''), max_width, '..', footer_width) - return context['candidates'] # type: ignore + return list(context['candidates']) diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_truncate_menu.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_truncate_menu.py index 164592a7c..0fb3ce908 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_truncate_menu.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_truncate_menu.py @@ -4,8 +4,10 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim + from deoplete.base.filter import Base -from deoplete.util import truncate_skipping, Nvim, UserContext, Candidates +from deoplete.util import truncate_skipping, UserContext, Candidates class Filter(Base): @@ -19,11 +21,11 @@ class Filter(Base): max_width = context['max_menu_width'] if not context['candidates'] or 'menu' not in context[ 'candidates'][0] or max_width <= 0: - return context['candidates'] # type: ignore + return list(context['candidates']) footer_width = max_width / 3 for candidate in context['candidates']: candidate['menu'] = truncate_skipping( candidate.get('menu', ''), max_width, '..', footer_width) - return context['candidates'] # type: ignore + return list(context['candidates']) diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_word_abbr.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_word_abbr.py new file mode 100644 index 000000000..69d05a672 --- /dev/null +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/converter_word_abbr.py @@ -0,0 +1,23 @@ +# ============================================================================ +# FILE: converter_word_abbr.py +# AUTHOR: Shougo Matsushita +# License: MIT license +# ============================================================================ + +from pynvim import Nvim + +from deoplete.base.filter import Base +from deoplete.util import UserContext, Candidates + + +class Filter(Base): + def __init__(self, vim: Nvim) -> None: + super().__init__(vim) + + self.name = 'converter_word_abbr' + self.description = 'word abbr converter' + + def filter(self, context: UserContext) -> Candidates: + for candidate in context['candidates']: + candidate['abbr'] = candidate['word'] + return list(context['candidates']) diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_cpsm.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_cpsm.py index 96e41adc7..b28ddfd45 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_cpsm.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_cpsm.py @@ -4,13 +4,14 @@ # License: MIT license # ============================================================================ -import os +from pathlib import Path +from pynvim import Nvim import sys import typing from deoplete.base.filter import Base from deoplete.util import error, globruntime -from deoplete.util import Nvim, UserContext, Candidates +from deoplete.util import UserContext, Candidates class Filter(Base): @@ -26,7 +27,7 @@ class Filter(Base): def filter(self, context: UserContext) -> Candidates: if (not context['candidates'] or not context['input'] or self._cpsm is False): - return context['candidates'] # type: ignore + return list(context['candidates']) if self._cpsm is None: errmsg = self._init_cpsm(context) @@ -49,7 +50,7 @@ class Filter(Base): found = globruntime(self.vim.options['runtimepath'], fname) errmsg = '' if found: - sys.path.insert(0, os.path.dirname(found[0])) + sys.path.insert(0, str(Path(found[0]).parent)) try: import cpsm_py except ImportError as exc: diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_full_fuzzy.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_full_fuzzy.py index f7fec4ce0..176d275a2 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_full_fuzzy.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_full_fuzzy.py @@ -4,9 +4,11 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim import re + from deoplete.base.filter import Base -from deoplete.util import fuzzy_escape, Nvim, UserContext, Candidates +from deoplete.util import fuzzy_escape, UserContext, Candidates class Filter(Base): diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_fuzzy.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_fuzzy.py index 9f0092eaa..73f208e5c 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_fuzzy.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_fuzzy.py @@ -4,12 +4,12 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim import re from deoplete.base.filter import Base -from deoplete.util import ( - fuzzy_escape, binary_search_begin, binary_search_end) -from deoplete.util import Nvim, UserContext, Candidates +from deoplete.util import binary_search_begin, binary_search_end +from deoplete.util import fuzzy_escape, UserContext, Candidates class Filter(Base): @@ -25,7 +25,7 @@ class Filter(Base): if context['ignorecase']: complete_str = complete_str.lower() if not complete_str: - return context['candidates'] # type: ignore + return list(context['candidates']) if context['is_sorted']: begin = binary_search_begin( diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_head.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_head.py index de672de27..e63bfd842 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_head.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_head.py @@ -4,9 +4,11 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim + from deoplete.base.filter import Base from deoplete.util import binary_search_begin, binary_search_end -from deoplete.util import Nvim, UserContext, Candidates +from deoplete.util import UserContext, Candidates class Filter(Base): @@ -32,7 +34,7 @@ class Filter(Base): candidates = context['candidates'][begin:end+1] if context['ignorecase']: - return candidates # type: ignore + return list(candidates) else: candidates = context['candidates'] diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_length.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_length.py index 9804e676c..27b7de2c1 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_length.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_length.py @@ -4,8 +4,10 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim + from deoplete.base.filter import Base -from deoplete.util import Nvim, UserContext, Candidates +from deoplete.util import UserContext, Candidates class Filter(Base): diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_matchfuzzy.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_matchfuzzy.py new file mode 100644 index 000000000..e782c268e --- /dev/null +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/matcher_matchfuzzy.py @@ -0,0 +1,28 @@ +# ============================================================================ +# FILE: matcher_matchfuzzy.py +# AUTHOR: Shougo Matsushita +# License: MIT license +# ============================================================================ + +from pynvim import Nvim + +from deoplete.base.filter import Base +from deoplete.util import UserContext, Candidates + + +class Filter(Base): + + def __init__(self, vim: Nvim) -> None: + super().__init__(vim) + + self.name = 'matcher_matchfuzzy' + self.description = 'matchfuzzy() matcher' + + def filter(self, context: UserContext) -> Candidates: + if not self.vim.call('exists', '*matchfuzzy'): + return [] + + return list(self.vim.call( + 'matchfuzzy', context['candidates'], + context['complete_str'], {'key': 'word'} + )) diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/sorter_rank.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/sorter_rank.py index f002aabf2..d6623085b 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/sorter_rank.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/sorter_rank.py @@ -4,12 +4,13 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim import re import typing from deoplete.base.filter import Base from deoplete.util import getlines -from deoplete.util import Nvim, UserContext, Candidates, Candidate +from deoplete.util import UserContext, Candidates, Candidate LINES_MAX = 150 @@ -39,11 +40,15 @@ class Filter(Base): def filter(self, context: UserContext) -> Candidates: complete_str = context['complete_str'].lower() linenr = context['position'][1] + recently_used = self.vim.vars['deoplete#_recently_used'] def compare(x: Candidate) -> int: word = x['word'] - matched = int(complete_str in word.lower()) + lower = x['word'].lower() + matched = int(complete_str in lower) score = -matched * 40 + if [x for x in recently_used if lower.startswith(x)]: + score -= 1000 if word in self._cache: mru = min([abs(x - linenr) for x in self._cache[word]]) mru -= LINES_MAX diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/sorter_word.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/sorter_word.py index 555810675..cc4a12429 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/sorter_word.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/filter/sorter_word.py @@ -4,8 +4,10 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim + from deoplete.base.filter import Base -from deoplete.util import Nvim, UserContext, Candidates +from deoplete.util import UserContext, Candidates class Filter(Base): @@ -18,4 +20,4 @@ class Filter(Base): def filter(self, context: UserContext) -> Candidates: return sorted(context['candidates'], - key=lambda x: x['word'].swapcase()) + key=lambda x: str(x['word'].swapcase())) diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/logger.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/logger.py index cb7e16d55..2d95e6774 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/logger.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/logger.py @@ -8,10 +8,9 @@ import time import logging import typing -from deoplete.util import Nvim - -from functools import wraps from collections import defaultdict +from functools import wraps +from pynvim import Nvim log_format = '%(asctime)s %(levelname)-8s [%(process)d] (%(name)s) %(message)s' log_message_cooldown = 0.5 @@ -50,19 +49,11 @@ def setup(vim: Nvim, level: str, output_file: str = '') -> None: level = 'DEBUG' root.setLevel(getattr(logging, level)) - try: - import pkg_resources - - pynvim_version = pkg_resources.get_distribution('pynvim').version - except Exception: - pynvim_version = 'unknown' - log = getLogger('logging') log.info('--- Deoplete Log Start ---') - log.info('%s, Python %s, pynvim %s', + log.info('%s, Python %s', vim.call('deoplete#util#neovim_version'), - '.'.join(map(str, sys.version_info[:3])), - pynvim_version) + '.'.join(map(str, sys.version_info[:3]))) if 'deoplete#_logging_notified' not in vim.vars: vim.vars['deoplete#_logging_notified'] = 1 diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/parent.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/parent.py index e79f50990..0dfb7f38f 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/parent.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/parent.py @@ -4,20 +4,20 @@ # License: MIT license # ============================================================================ -import time -import os -import msgpack -import subprocess -import sys -import typing from abc import abstractmethod from functools import partial from pathlib import Path +from pynvim import Nvim from queue import Queue +import msgpack +import subprocess +import sys +import time +import typing from deoplete import logger from deoplete.process import Process -from deoplete.util import error_tb, error, Nvim +from deoplete.util import error_tb, error UserContext = typing.Dict[str, typing.Any] @@ -91,7 +91,7 @@ class AsyncParent(_Parent): Taken from jedi.api.environment._try_get_same_env. """ exe = sys.executable - if not os.path.basename(exe).lower().startswith('python'): + if not Path(exe).name.lower().startswith('python'): checks: typing.Tuple[typing.Any, ...] if sys.platform == 'win32': checks = (r'Scripts\python.exe', 'python.exe') @@ -103,8 +103,8 @@ class AsyncParent(_Parent): 'bin/python', ) for check in checks: - guess = os.path.join(sys.exec_prefix, check) - if os.path.isfile(str(guess)): + guess = Path(sys.exec_prefix).joinpath(check) + if guess.is_file(): return str(guess) if 'python3_host_prog' not in self._vim.vars: return 'python3' diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/source/around.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/source/around.py index dd52ecc32..e9a114cae 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/source/around.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/source/around.py @@ -4,11 +4,12 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim import re from deoplete.base.source import Base from deoplete.util import parse_buffer_pattern, getlines -from deoplete.util import Nvim, UserContext, Candidates +from deoplete.util import UserContext, Candidates class Source(Base): diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/source/base.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/source/base.py index 0c59b5aa6..b1345231b 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/source/base.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/source/base.py @@ -4,9 +4,9 @@ # License: MIT license # ============================================================================ -# For backward compatibility +from pynvim import Nvim + from deoplete.base.source import Base as _Base -from deoplete.util import Nvim class Base(_Base): diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/source/buffer.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/source/buffer.py index 45347b94b..bd0cc9357 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/source/buffer.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/source/buffer.py @@ -4,11 +4,12 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim import typing from deoplete.base.source import Base from deoplete.util import parse_buffer_pattern, getlines -from deoplete.util import Nvim, UserContext, Candidates +from deoplete.util import UserContext, Candidates class Source(Base): diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/source/file.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/source/file.py index 2aa1dffaa..96b0c7815 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/source/file.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/source/file.py @@ -5,13 +5,13 @@ # License: MIT license # ============================================================================ -import os +from pathlib import Path +from pynvim import Nvim import re import typing -from os.path import exists, dirname from deoplete.base.source import Base -from deoplete.util import expand, Nvim, UserContext, Candidates +from deoplete.util import expand, exists_path, UserContext, Candidates class Source(Base): @@ -26,6 +26,7 @@ class Source(Base): self.events: typing.List[str] = ['InsertEnter'] self.vars = { 'enable_buffer_path': True, + 'enable_slash_completion': False, 'force_completion_length': -1, } @@ -39,7 +40,7 @@ class Source(Base): def get_complete_position(self, context: UserContext) -> int: pos = int(context['input'].rfind('/')) force_completion_length = int( - self.get_var('force_completion_length')) # type: ignore + self.get_var('force_completion_length')) if pos < 0 and force_completion_length >= 0: fmt = '[a-zA-Z0-9.-]{{{}}}$'.format(force_completion_length) m = re.search(fmt, context['input']) @@ -55,20 +56,38 @@ class Source(Base): if context['input'].rfind('/') >= 0 else './') - p = self._longest_path_that_exists(context, input_str) - if not p or p == '/' or re.search('//+$', p): + # Note: context['bufpath'] will be empty if not exists file + bufname = context['bufname'] + bufpath = (bufname if Path(bufname).is_absolute() + else str(Path(context['cwd']).joinpath(bufname))) + buftype = self.vim.call('getbufvar', '%', '&buftype') + if not bufname or 'nofile' in buftype or not self.get_var( + 'enable_buffer_path'): + bufpath = '' + + p = self._longest_path_that_exists(context, input_str, bufpath) + slash_completion = bool(self.get_var('enable_slash_completion')) + if not p or re.search('//+$', p) or ( + p == '/' and not slash_completion): return [] - complete_str = self._substitute_path(context, dirname(p) + '/') - if not os.path.isdir(complete_str): + + p = expand(p) + if p[-1] != '/': + p += '/' + complete_str = self._substitute_path(context, p, bufpath) + if not Path(complete_str).is_dir(): return [] hidden = context['complete_str'].find('.') == 0 contents: typing.List[typing.Any] = [[], []] try: - for item in sorted(os.listdir(complete_str), key=str.lower): + for item in sorted([str(x.name) for x + in Path(complete_str).iterdir()], + key=str.lower): if not hidden and item[0] == '.': continue - contents[not os.path.isdir(complete_str + item)].append(item) - except PermissionError: + is_dir = not Path(complete_str + '/' + item).is_dir() + contents[is_dir].append(item) + except (PermissionError, FileNotFoundError): pass dirs, files = contents @@ -76,25 +95,32 @@ class Source(Base): ] + [{'word': x} for x in files] def _longest_path_that_exists(self, context: UserContext, - input_str: str) -> str: + input_str: str, bufpath: str) -> str: input_str = re.sub(r'[^/]*$', '', input_str) - data = re.split(r'((?:%s+|(?:(? str: + def _substitute_path(self, context: UserContext, + path: str, bufpath: str) -> str: m = re.match(r'(\.{1,2})/+', path) - if m: - if self.get_var('enable_buffer_path') and context['bufpath']: - base = context['bufpath'] - else: - base = os.path.join(context['cwd'], 'x') + if not m: + return expand(path) - for _ in m.group(1): - base = dirname(base) - return os.path.abspath(os.path.join( - base, path[len(m.group(0)):])) + '/' - return expand(path) + if bufpath: + base = str(Path(bufpath).parent) + else: + base = context['cwd'] + + if m.group(1) == '..': + base = str(Path(base).parent) + rest = path[len(m.group(0)):] + if rest: + return str(Path(base).joinpath(rest)) + '/' + else: + return base diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/source/member.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/source/member.py index 6cca1fbb6..9f51fdd9a 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/source/member.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/source/member.py @@ -4,13 +4,14 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim import re import typing from deoplete.base.source import Base from deoplete.util import ( convert2list, parse_buffer_pattern, set_pattern, getlines) -from deoplete.util import Nvim, UserContext, Candidates +from deoplete.util import UserContext, Candidates class Source(Base): diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/source/omni.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/source/omni.py index 2eb84acec..fcc8d18cd 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/source/omni.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/source/omni.py @@ -4,13 +4,14 @@ # License: MIT license # ============================================================================ +from pynvim import Nvim import re import typing from deoplete.base.source import Base from deoplete.util import ( convert2list, set_pattern, convert2candidates) -from deoplete.util import Nvim, UserContext, Candidates +from deoplete.util import UserContext, Candidates class Source(Base): @@ -44,6 +45,8 @@ class Source(Base): def _get_complete_position(self, context: UserContext, current_ft: str, filetype: str) -> int: + complete_pos = -1 + for omnifunc in convert2list( self.get_filetype_var(filetype, 'functions')): if omnifunc == '' and (filetype == current_ft or @@ -69,15 +72,16 @@ class Source(Base): 'rubycomplete#Complete', 'phpcomplete#CompletePHP']: # In the blacklist - return -1 + continue try: complete_pos = int(self.vim.call(self._omnifunc, 1, '')) except Exception: self.print_error('Error occurred calling omnifunction: ' + self._omnifunc) return -1 - return complete_pos - return -1 + if complete_pos >= 0: + break + return complete_pos def gather_candidates(self, context: UserContext) -> Candidates: try: @@ -95,4 +99,4 @@ class Source(Base): candidate['dup'] = 1 candidate['equal'] = 1 - return candidates # type: ignore + return list(candidates) diff --git a/bundle/deoplete.nvim/rplugin/python3/deoplete/util.py b/bundle/deoplete.nvim/rplugin/python3/deoplete/util.py index 8492cf83d..6116c8203 100644 --- a/bundle/deoplete.nvim/rplugin/python3/deoplete/util.py +++ b/bundle/deoplete.nvim/rplugin/python3/deoplete/util.py @@ -4,22 +4,18 @@ # License: MIT license # ============================================================================ -import os -import re -import sys +from os.path import expandvars +from pathlib import Path +from pynvim import Nvim +from pynvim.api import Buffer import glob import importlib.util +import re +import sys import traceback import typing import unicodedata -if importlib.util.find_spec('pynvim'): - from pynvim import Nvim - from pynvim.api import Buffer -else: - from neovim import Nvim - from neovim.api import Buffer - UserContext = typing.Dict[str, typing.Any] Candidate = typing.Dict[str, typing.Any] Candidates = typing.List[Candidate] @@ -61,17 +57,19 @@ def import_plugin(path: str, source: str, If the class exists, add its directory to sys.path. """ - name = os.path.splitext(os.path.basename(path))[0] + name = str(Path(path).name)[: -3] module_name = 'deoplete.%s.%s' % (source, name) spec = importlib.util.spec_from_file_location(module_name, path) + if not spec: + return None module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) # type: ignore cls = getattr(module, classname, None) if not cls: return None - dirname = os.path.dirname(path) + dirname = str(Path(path).parent) if dirname not in sys.path: sys.path.insert(0, dirname) return cls @@ -96,7 +94,7 @@ def error_tb(vim: Nvim, msg: str) -> None: t, v, tb = sys.exc_info() if t and v and tb: lines += traceback.format_exc().splitlines() - lines += ['%s. Use :messages / see above for error details.' % msg] + lines += ['%s Use :messages / see above for error details.' % msg] if hasattr(vim, 'err_write'): vim.err_write('[deoplete] %s\n' % '\n'.join(lines)) else: @@ -141,8 +139,8 @@ def get_custom(custom: typing.Dict[str, typing.Any], return default -def get_syn_names(vim: Nvim) -> str: - return str(vim.call('deoplete#util#get_syn_names')) +def get_syn_names(vim: Nvim) -> typing.List[str]: + return list(vim.call('deoplete#util#get_syn_names')) def parse_file_pattern(f: typing.Iterable[str], @@ -169,8 +167,8 @@ def fuzzy_escape(string: str, camelcase: bool) -> str: def load_external_module(base: str, module: str) -> None: - current = os.path.dirname(os.path.abspath(base)) - module_dir = os.path.join(os.path.dirname(current), module) + current = Path(base).parent.resolve() + module_dir = str(current.parent.joinpath(module)) if module_dir not in sys.path: sys.path.insert(0, module_dir) @@ -219,7 +217,20 @@ def charwidth(c: str) -> int: def expand(path: str) -> str: - return os.path.expanduser(os.path.expandvars(path)) + if path.startswith('~'): + try: + path = str(Path(path).expanduser()) + except Exception: + pass + return expandvars(path) + + +def exists_path(path: str) -> bool: + try: + return Path(path).exists() + except Exception: + pass + return False def getlines(vim: Nvim, start: int = 1, @@ -230,7 +241,9 @@ def getlines(vim: Nvim, start: int = 1, lines: typing.List[str] = [] current = start while current <= int(end): - lines += vim.call('getline', current, current + max_len) + # Skip very long lines + lines += [x for x in vim.call('getline', current, current + max_len) + if len(x) < 300] current += max_len + 1 return lines diff --git a/bundle/deoplete.nvim/test/conftest.py b/bundle/deoplete.nvim/test/conftest.py index 83e216306..6a3589226 100644 --- a/bundle/deoplete.nvim/test/conftest.py +++ b/bundle/deoplete.nvim/test/conftest.py @@ -1,5 +1,5 @@ -import os +from pathlib import Path import sys -BASE_DIR = os.path.dirname(os.path.dirname(__file__)) -sys.path.insert(0, os.path.join(BASE_DIR, 'rplugin/python3')) +BASE_DIR = Path(__file__).parent.parent +sys.path.insert(0, str(Path(BASE_DIR).joinpath('rplugin/python3')))