mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-04-13 22:39:10 +08:00
Add deoplete into bundle dir
This commit is contained in:
parent
a466c0d700
commit
003ba8e5ff
@ -68,13 +68,13 @@ function! SpaceVim#layers#autocomplete#plugins() abort
|
||||
call add(plugins, ['neoclide/coc.nvim', {'merged': 0, 'rev': 'release'}])
|
||||
endif
|
||||
elseif g:spacevim_autocomplete_method ==# 'deoplete'
|
||||
call add(plugins, ['Shougo/deoplete.nvim', {
|
||||
call add(plugins, [g:_spacevim_root_dir . 'bundle/deoplete.nvim', {
|
||||
\ 'on_event' : 'InsertEnter',
|
||||
\ 'loadconf' : 1,
|
||||
\ }])
|
||||
if !has('nvim')
|
||||
call add(plugins, ['SpaceVim/nvim-yarp', {'merged': 0}])
|
||||
call add(plugins, ['SpaceVim/vim-hug-neovim-rpc', {'merged': 0}])
|
||||
call add(plugins, [g:_spacevim_root_dir . 'bundle/nvim-yarp', {'merged': 0}])
|
||||
call add(plugins, [g:_spacevim_root_dir . 'bundle/vim-hug-neovim-rpc', {'merged': 0}])
|
||||
endif
|
||||
elseif g:spacevim_autocomplete_method ==# 'asyncomplete'
|
||||
call add(plugins, ['prabirshrestha/asyncomplete.vim', {
|
||||
|
51
bundle/deoplete.nvim/.github/ISSUE_TEMPLATE.md
vendored
Normal file
51
bundle/deoplete.nvim/.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
**Warning: I will close the issue without the minimal init.vim and the reproduction instructions.**
|
||||
|
||||
# Problems summary
|
||||
|
||||
|
||||
## Expected
|
||||
|
||||
|
||||
## Environment Information
|
||||
|
||||
* deoplete version (SHA1):
|
||||
|
||||
* OS:
|
||||
|
||||
* neovim/Vim `:version` output:
|
||||
|
||||
* `:checkhealth` or `:CheckHealth` result(neovim only):
|
||||
|
||||
## Provide a minimal init.vim/vimrc with less than 50 lines (Required!)
|
||||
|
||||
```vim
|
||||
" Your minimal init.vim/vimrc
|
||||
set runtimepath+=~/path/to/deoplete.nvim/
|
||||
let g:deoplete#enable_at_startup = 1
|
||||
|
||||
" For Vim only
|
||||
"set runtimepath+=~/path/to/nvim-yarp/
|
||||
"set runtimepath+=~/path/to/vim-hug-neovim-rpc/
|
||||
```
|
||||
|
||||
|
||||
## How to reproduce the problem from neovim/Vim startup (Required!)
|
||||
|
||||
1. foo
|
||||
2. bar
|
||||
3. baz
|
||||
|
||||
|
||||
## Generate a logfile if appropriate
|
||||
|
||||
1. export NVIM_PYTHON_LOG_FILE=/tmp/log
|
||||
2. export NVIM_PYTHON_LOG_LEVEL=DEBUG
|
||||
3. nvim -u minimal.vimrc
|
||||
4. some works
|
||||
5. cat /tmp/log_{PID}
|
||||
|
||||
|
||||
## Screenshot (if possible)
|
||||
|
||||
|
||||
## Upload the log file
|
7
bundle/deoplete.nvim/.gitignore
vendored
Normal file
7
bundle/deoplete.nvim/.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
*.py[cod]
|
||||
doc/tags
|
||||
vim-themis
|
||||
.cache
|
||||
.mypy_cache
|
||||
.pytest_cache
|
||||
tags
|
22
bundle/deoplete.nvim/.travis.yml
Normal file
22
bundle/deoplete.nvim/.travis.yml
Normal file
@ -0,0 +1,22 @@
|
||||
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
|
21
bundle/deoplete.nvim/LICENSE
Normal file
21
bundle/deoplete.nvim/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
License: MIT license
|
||||
AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
31
bundle/deoplete.nvim/Makefile
Normal file
31
bundle/deoplete.nvim/Makefile
Normal file
@ -0,0 +1,31 @@
|
||||
PATH := ./vim-themis/bin:$(PATH)
|
||||
export THEMIS_VIM := nvim
|
||||
export THEMIS_ARGS := -e -s --headless
|
||||
export THEMIS_HOME := ./vim-themis
|
||||
|
||||
|
||||
install: vim-themis
|
||||
pip install --upgrade -r test/requirements.txt
|
||||
|
||||
install-user: vim-themis
|
||||
pip install --user --upgrade -r test/requirements.txt
|
||||
|
||||
lint:
|
||||
vint --version
|
||||
vint plugin
|
||||
vint autoload
|
||||
flake8 --version
|
||||
flake8 rplugin
|
||||
mypy --version
|
||||
mypy --ignore-missing-imports --follow-imports=skip --strict rplugin/python3/deoplete
|
||||
|
||||
test:
|
||||
themis --version
|
||||
themis test/autoload/*
|
||||
pytest --version
|
||||
pytest
|
||||
|
||||
vim-themis:
|
||||
git clone --depth 1 https://github.com/thinca/vim-themis $@
|
||||
|
||||
.PHONY: install lint test
|
159
bundle/deoplete.nvim/README.md
Normal file
159
bundle/deoplete.nvim/README.md
Normal file
@ -0,0 +1,159 @@
|
||||
# deoplete.nvim
|
||||
|
||||
> Dark powered asynchronous completion framework for neovim/Vim8
|
||||
|
||||
[](https://travis-ci.org/Shougo/deoplete.nvim)
|
||||
[](https://gitter.im/Shougo/deoplete.nvim?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](doc/deoplete.txt)
|
||||
|
||||
Deoplete is the abbreviation of "dark powered neo-completion". It
|
||||
provides an extensible and asynchronous completion framework for
|
||||
neovim/Vim8.
|
||||
|
||||
deoplete will display completions via `complete()` by default.
|
||||
|
||||
Here are some [completion sources](https://github.com/Shougo/deoplete.nvim/wiki/Completion-Sources) specifically made for deoplete.nvim.
|
||||
|
||||
<!-- vim-markdown-toc GFM -->
|
||||
|
||||
- [Install](#install)
|
||||
- [Requirements](#requirements)
|
||||
- [Configuration](#configuration)
|
||||
- [Screenshots](#screenshots)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
|
||||
## 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
|
||||
[requirements](#requirements) if you aren't sure whether you have this.
|
||||
|
||||
Note: deoplete requires msgpack package 1.0.0+.
|
||||
Please install/upgrade msgpack package by pip.
|
||||
https://github.com/msgpack/msgpack-python
|
||||
|
||||
|
||||
Note: If you really need to use older msgpack, please use deoplete ver.5.2
|
||||
instead.
|
||||
|
||||
https://github.com/Shougo/deoplete.nvim/releases/tag/5.2
|
||||
|
||||
For vim-plug
|
||||
|
||||
```viml
|
||||
if has('nvim')
|
||||
Plug 'Shougo/deoplete.nvim', { 'do': ':UpdateRemotePlugins' }
|
||||
else
|
||||
Plug 'Shougo/deoplete.nvim'
|
||||
Plug 'roxma/nvim-yarp'
|
||||
Plug 'roxma/vim-hug-neovim-rpc'
|
||||
endif
|
||||
let g:deoplete#enable_at_startup = 1
|
||||
```
|
||||
|
||||
For dein.vim
|
||||
|
||||
```viml
|
||||
call dein#add('Shougo/deoplete.nvim')
|
||||
if !has('nvim')
|
||||
call dein#add('roxma/nvim-yarp')
|
||||
call dein#add('roxma/vim-hug-neovim-rpc')
|
||||
endif
|
||||
let g:deoplete#enable_at_startup = 1
|
||||
```
|
||||
|
||||
For manual installation(not recommended)
|
||||
|
||||
1. Extract the files and put them in your Neovim or .vim directory
|
||||
(usually `$XDG_CONFIG_HOME/nvim/`).
|
||||
|
||||
2. Write `call deoplete#enable()` or `let g:deoplete#enable_at_startup = 1` in
|
||||
your `init.vim`
|
||||
|
||||
### Requirements
|
||||
|
||||
deoplete requires Neovim or Vim8 with `if_python3`.
|
||||
|
||||
If `:echo has("python3")` returns `1`, then you have python 3 support; otherwise, see below.
|
||||
|
||||
You can enable Python3 interface with pip:
|
||||
|
||||
pip3 install --user pynvim
|
||||
|
||||
Please install nvim-yarp and vim-hug-neovim-rpc for Vim8.
|
||||
|
||||
- <https://github.com/roxma/nvim-yarp>
|
||||
- <https://github.com/roxma/vim-hug-neovim-rpc>
|
||||
|
||||
**Note: Python3 must be enabled before updating remote plugins**
|
||||
|
||||
If Deoplete was installed prior to Python support being added to Neovim,
|
||||
`:UpdateRemotePlugins` should be executed manually in order to enable
|
||||
auto-completion.
|
||||
|
||||
**Note: deoplete needs pynvim ver.0.3.0+.**
|
||||
|
||||
You need update pynvim module.
|
||||
|
||||
pip3 install --user --upgrade pynvim
|
||||
|
||||
If you want to read the Neovim-python/python3 interface install documentation,
|
||||
you should read `:help provider-python` and the Wiki.
|
||||
<https://github.com/deoplete-plugins/deoplete-jedi/wiki/Setting-up-Python-for-Neovim>
|
||||
|
||||
## Configuration
|
||||
|
||||
```vim
|
||||
" Use deoplete.
|
||||
let g:deoplete#enable_at_startup = 1
|
||||
```
|
||||
|
||||
See `:help deoplete-options` for a complete list of options.
|
||||
|
||||
## Screenshots
|
||||
|
||||
Deoplete for JavaScript
|
||||
<https://www.youtube.com/watch?v=oanoPTpiSF4>
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
90
bundle/deoplete.nvim/autoload/deoplete.vim
Normal file
90
bundle/deoplete.nvim/autoload/deoplete.vim
Normal file
@ -0,0 +1,90 @@
|
||||
"=============================================================================
|
||||
" FILE: deoplete.vim
|
||||
" AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
" License: MIT license
|
||||
"=============================================================================
|
||||
|
||||
function! deoplete#initialize() abort
|
||||
return deoplete#init#_initialize()
|
||||
endfunction
|
||||
function! deoplete#is_enabled() abort
|
||||
return deoplete#init#_is_handler_enabled()
|
||||
endfunction
|
||||
function! deoplete#enable() abort
|
||||
if has('vim_starting')
|
||||
augroup deoplete
|
||||
autocmd!
|
||||
autocmd VimEnter * call deoplete#enable()
|
||||
augroup END
|
||||
return 1
|
||||
endif
|
||||
|
||||
if deoplete#initialize() && deoplete#is_enabled()
|
||||
return 1
|
||||
endif
|
||||
return deoplete#init#_enable_handler()
|
||||
endfunction
|
||||
function! deoplete#disable() abort
|
||||
call deoplete#initialize()
|
||||
return deoplete#init#_disable_handler()
|
||||
endfunction
|
||||
function! deoplete#toggle() abort
|
||||
call deoplete#initialize()
|
||||
return deoplete#is_enabled() ?
|
||||
\ deoplete#init#_disable_handler() :
|
||||
\ deoplete#init#_enable_handler()
|
||||
endfunction
|
||||
|
||||
function! deoplete#enable_logging(level, logfile) abort
|
||||
let g:deoplete#_logging = {'level': a:level, 'logfile': a:logfile}
|
||||
call deoplete#util#rpcnotify('deoplete_enable_logging', {})
|
||||
endfunction
|
||||
|
||||
function! deoplete#send_event(event, ...) abort
|
||||
if &l:previewwindow
|
||||
return
|
||||
endif
|
||||
|
||||
let sources = deoplete#util#convert2list(get(a:000, 0, []))
|
||||
call deoplete#util#rpcnotify('deoplete_on_event',
|
||||
\ {'event': a:event, 'sources': sources})
|
||||
endfunction
|
||||
|
||||
function! deoplete#complete() abort
|
||||
return deoplete#mapping#_dummy('deoplete#mapping#_complete')
|
||||
endfunction
|
||||
function! deoplete#auto_complete(...) abort
|
||||
return deoplete#handler#_completion_begin(get(a:000, 0, 'Async'))
|
||||
endfunction
|
||||
function! deoplete#manual_complete(...) abort
|
||||
if !deoplete#is_enabled()
|
||||
return ''
|
||||
endif
|
||||
|
||||
call deoplete#init#_prev_completion()
|
||||
|
||||
" Start complete.
|
||||
return "\<C-r>=deoplete#mapping#_rpcrequest_wrapper("
|
||||
\ . string(get(a:000, 0, [])) . ")\<CR>"
|
||||
endfunction
|
||||
function! deoplete#close_popup() abort
|
||||
call deoplete#handler#_skip_next_completion()
|
||||
return pumvisible() ? "\<C-y>" : ''
|
||||
endfunction
|
||||
function! deoplete#smart_close_popup() abort
|
||||
call deoplete#handler#_skip_next_completion()
|
||||
return pumvisible() ? "\<C-e>" : ''
|
||||
endfunction
|
||||
function! deoplete#cancel_popup() abort
|
||||
call deoplete#handler#_skip_next_completion()
|
||||
return pumvisible() ? "\<C-e>" : ''
|
||||
endfunction
|
||||
function! deoplete#insert_candidate(number) abort
|
||||
return deoplete#mapping#_insert_candidate(a:number)
|
||||
endfunction
|
||||
function! deoplete#undo_completion() abort
|
||||
return deoplete#mapping#_undo_completion()
|
||||
endfunction
|
||||
function! deoplete#complete_common_string() abort
|
||||
return deoplete#mapping#_complete_common_string()
|
||||
endfunction
|
62
bundle/deoplete.nvim/autoload/deoplete/_main.py
Normal file
62
bundle/deoplete.nvim/autoload/deoplete/_main.py
Normal file
@ -0,0 +1,62 @@
|
||||
# ============================================================================
|
||||
# FILE: _main.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import sys
|
||||
import io
|
||||
|
||||
from importlib.util import find_spec
|
||||
if find_spec('pynvim'):
|
||||
from pynvim import attach
|
||||
else:
|
||||
from neovim import attach
|
||||
|
||||
|
||||
def attach_vim(serveraddr):
|
||||
if len(serveraddr.split(':')) == 2:
|
||||
serveraddr, port = serveraddr.split(':')
|
||||
port = int(port)
|
||||
vim = attach('tcp', address=serveraddr, port=port)
|
||||
else:
|
||||
vim = attach('socket', path=serveraddr)
|
||||
|
||||
# sync path
|
||||
for path in vim.call(
|
||||
'globpath', vim.options['runtimepath'],
|
||||
'rplugin/python3', 1).split('\n'):
|
||||
sys.path.append(path)
|
||||
# Remove current path
|
||||
del sys.path[0]
|
||||
|
||||
return vim
|
||||
|
||||
|
||||
class RedirectStream(io.IOBase):
|
||||
def __init__(self, handler):
|
||||
self.handler = handler
|
||||
|
||||
def write(self, line):
|
||||
self.handler(line)
|
||||
|
||||
def writelines(self, lines):
|
||||
self.handler('\n'.join(lines))
|
||||
|
||||
|
||||
def main(serveraddr):
|
||||
vim = attach_vim(serveraddr)
|
||||
from deoplete.child import Child
|
||||
from deoplete.util import error_tb
|
||||
stdout = sys.stdout
|
||||
sys.stdout = RedirectStream(lambda data: vim.out_write(data))
|
||||
sys.stderr = RedirectStream(lambda data: vim.err_write(data))
|
||||
try:
|
||||
child = Child(vim)
|
||||
child.main_loop(stdout)
|
||||
except Exception as exc:
|
||||
error_tb(vim, 'Error in child: %r' % exc)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1])
|
177
bundle/deoplete.nvim/autoload/deoplete/custom.vim
Normal file
177
bundle/deoplete.nvim/autoload/deoplete/custom.vim
Normal file
@ -0,0 +1,177 @@
|
||||
"=============================================================================
|
||||
" FILE: custom.vim
|
||||
" AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
" License: MIT license
|
||||
"=============================================================================
|
||||
|
||||
function! deoplete#custom#_init() abort
|
||||
let s:custom = {}
|
||||
let s:custom.source = {}
|
||||
let s:custom.source._ = {}
|
||||
let s:custom.option = deoplete#init#_option()
|
||||
let s:custom.filter = {}
|
||||
|
||||
let s:cached = {}
|
||||
let s:cached.option = {}
|
||||
let s:cached.filter = {}
|
||||
let s:cached.buffer_option = {}
|
||||
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 = {}
|
||||
endfunction
|
||||
|
||||
function! deoplete#custom#_update_cache() abort
|
||||
if !exists('s:custom')
|
||||
call deoplete#custom#_init()
|
||||
endif
|
||||
|
||||
let custom_buffer = deoplete#custom#_get_buffer()
|
||||
|
||||
let s:cached.option = copy(s:custom.option)
|
||||
let s:cached.buffer_option = copy(custom_buffer.option)
|
||||
call extend(s:cached.option, s:cached.buffer_option)
|
||||
|
||||
let s:cached.source_vars = {}
|
||||
for [name, source] in items(s:custom.source)
|
||||
let s:cached.source_vars[name] = get(source, 'vars', {})
|
||||
endfor
|
||||
for [name, vars] in items(custom_buffer.source_vars)
|
||||
if !has_key(s:cached.source_vars, name)
|
||||
let s:cached.source_vars[name] = {}
|
||||
endif
|
||||
call extend(s:cached.source_vars[name], vars)
|
||||
endfor
|
||||
let s:cached.filter = {}
|
||||
for [name, vars] in items(s:custom.filter)
|
||||
let s:cached.filter[name] = vars
|
||||
endfor
|
||||
for [name, vars] in items(custom_buffer.filter)
|
||||
if !has_key(s:cached.filter, name)
|
||||
let s:cached.filter[name] = {}
|
||||
endif
|
||||
call extend(s:cached.filter[name], vars)
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! deoplete#custom#_get() abort
|
||||
if !exists('s:custom')
|
||||
call deoplete#custom#_init()
|
||||
endif
|
||||
|
||||
return s:custom
|
||||
endfunction
|
||||
function! deoplete#custom#_get_buffer() abort
|
||||
if !exists('b:custom')
|
||||
call deoplete#custom#_init_buffer()
|
||||
endif
|
||||
|
||||
return b:custom
|
||||
endfunction
|
||||
|
||||
function! deoplete#custom#_get_source(source_name) abort
|
||||
let custom = deoplete#custom#_get().source
|
||||
|
||||
if !has_key(custom, a:source_name)
|
||||
let custom[a:source_name] = {}
|
||||
endif
|
||||
|
||||
return custom[a:source_name]
|
||||
endfunction
|
||||
function! deoplete#custom#_get_option(name) abort
|
||||
return s:cached.option[a:name]
|
||||
endfunction
|
||||
function! deoplete#custom#_get_filetype_option(name, filetype, default) abort
|
||||
let buffer_option = s:cached.buffer_option
|
||||
if has_key(buffer_option, a:name)
|
||||
" Use buffer_option instead
|
||||
return buffer_option[a:name]
|
||||
endif
|
||||
|
||||
let option = s:cached.option[a:name]
|
||||
let filetype = has_key(option, a:filetype) ? a:filetype : '_'
|
||||
return get(option, filetype, a:default)
|
||||
endfunction
|
||||
function! deoplete#custom#_get_source_vars(name) abort
|
||||
return get(s:cached.source_vars, a:name, {})
|
||||
endfunction
|
||||
function! deoplete#custom#_get_filter(name) abort
|
||||
return get(s:cached.filter, a:name, {})
|
||||
endfunction
|
||||
|
||||
function! deoplete#custom#source(source_name, name_or_dict, ...) abort
|
||||
for key in deoplete#util#split(a:source_name)
|
||||
let custom_source = deoplete#custom#_get_source(key)
|
||||
call s:set_custom(custom_source, a:name_or_dict, get(a:000, 0, ''))
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! deoplete#custom#var(source_name, name_or_dict, ...) abort
|
||||
for key in deoplete#util#split(a:source_name)
|
||||
let custom_source = deoplete#custom#_get_source(key)
|
||||
let vars = get(custom_source, 'vars', {})
|
||||
call s:set_custom(vars, a:name_or_dict, get(a:000, 0, ''))
|
||||
call deoplete#custom#source(key, 'vars', vars)
|
||||
endfor
|
||||
endfunction
|
||||
function! deoplete#custom#buffer_var(source_name, name_or_dict, ...) abort
|
||||
let custom = deoplete#custom#_get_buffer().source_vars
|
||||
for key in deoplete#util#split(a:source_name)
|
||||
if !has_key(custom, key)
|
||||
let custom[key] = {}
|
||||
endif
|
||||
let vars = custom[key]
|
||||
call s:set_custom(vars, a:name_or_dict, get(a:000, 0, ''))
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! deoplete#custom#filter(filter_name, name_or_dict, ...) abort
|
||||
let custom = deoplete#custom#_get().filter
|
||||
for key in deoplete#util#split(a:filter_name)
|
||||
if !has_key(custom, key)
|
||||
let custom[key] = {}
|
||||
endif
|
||||
let vars = custom[key]
|
||||
call s:set_custom(vars, a:name_or_dict, get(a:000, 0, ''))
|
||||
endfor
|
||||
endfunction
|
||||
function! deoplete#custom#buffer_filter(filter_name, name_or_dict, ...) abort
|
||||
let custom = deoplete#custom#_get_buffer().filter
|
||||
for key in deoplete#util#split(a:filter_name)
|
||||
if !has_key(custom, key)
|
||||
let custom[key] = {}
|
||||
endif
|
||||
let vars = custom[key]
|
||||
call s:set_custom(vars, a:name_or_dict, get(a:000, 0, ''))
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! deoplete#custom#option(name_or_dict, ...) abort
|
||||
let custom = deoplete#custom#_get().option
|
||||
call s:set_custom(custom, a:name_or_dict, get(a:000, 0, ''))
|
||||
endfunction
|
||||
function! deoplete#custom#buffer_option(name_or_dict, ...) abort
|
||||
let custom = deoplete#custom#_get_buffer().option
|
||||
call s:set_custom(custom, a:name_or_dict, get(a:000, 0, ''))
|
||||
endfunction
|
||||
|
||||
function! s:set_custom(dest, name_or_dict, value) abort
|
||||
if type(a:name_or_dict) == v:t_dict
|
||||
call extend(a:dest, a:name_or_dict)
|
||||
else
|
||||
call s:set_value(a:dest, a:name_or_dict, a:value)
|
||||
endif
|
||||
endfunction
|
||||
function! s:set_value(dest, name, value) abort
|
||||
if type(a:value) == v:t_dict && !empty(a:value)
|
||||
if !has_key(a:dest, a:name)
|
||||
let a:dest[a:name] = {}
|
||||
endif
|
||||
call extend(a:dest[a:name], a:value)
|
||||
else
|
||||
let a:dest[a:name] = a:value
|
||||
endif
|
||||
endfunction
|
400
bundle/deoplete.nvim/autoload/deoplete/handler.vim
Normal file
400
bundle/deoplete.nvim/autoload/deoplete/handler.vim
Normal file
@ -0,0 +1,400 @@
|
||||
"=============================================================================
|
||||
" FILE: handler.vim
|
||||
" AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
" License: MIT license
|
||||
"=============================================================================
|
||||
|
||||
function! deoplete#handler#_init() abort
|
||||
augroup deoplete
|
||||
autocmd!
|
||||
autocmd InsertLeave * call s:on_insert_leave()
|
||||
autocmd CompleteDone * call s:on_complete_done()
|
||||
augroup END
|
||||
|
||||
for event in [
|
||||
\ 'InsertEnter', 'InsertLeave',
|
||||
\ 'BufReadPost', 'BufWritePost',
|
||||
\ 'VimLeavePre',
|
||||
\ ]
|
||||
call s:define_on_event(event)
|
||||
endfor
|
||||
|
||||
if deoplete#custom#_get_option('on_text_changed_i')
|
||||
call s:define_completion_via_timer('TextChangedI')
|
||||
endif
|
||||
if deoplete#custom#_get_option('on_insert_enter')
|
||||
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
|
||||
endif
|
||||
|
||||
" Note: Vim 8 GUI(MacVim and Win32) is broken
|
||||
" dummy timer call is needed before complete()
|
||||
if !has('nvim') && has('gui_running')
|
||||
\ && (has('gui_macvim') || has('win32'))
|
||||
let s:dummy_timer = timer_start(200, {timer -> 0}, {'repeat': -1})
|
||||
endif
|
||||
|
||||
if deoplete#util#has_yarp()
|
||||
" To fix "RuntimeError: Event loop is closed" issue
|
||||
" Note: Workaround
|
||||
autocmd deoplete VimLeavePre * call s:kill_yarp()
|
||||
endif
|
||||
endfunction
|
||||
|
||||
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
|
||||
return
|
||||
endif
|
||||
|
||||
let prev = g:deoplete#_prev_completion
|
||||
let prev.event = context.event
|
||||
let prev.input = context.input
|
||||
let prev.candidates = context.candidates
|
||||
let prev.complete_position = context.complete_position
|
||||
let prev.linenr = line('.')
|
||||
|
||||
let auto_popup = deoplete#custom#_get_option(
|
||||
\ 'auto_complete_popup') !=# 'manual'
|
||||
|
||||
" Enable auto refresh when popup is displayed
|
||||
if deoplete#util#check_popup()
|
||||
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
|
||||
call feedkeys("\<Plug>_", 'i')
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! deoplete#handler#_check_omnifunc(context) abort
|
||||
let prev = g:deoplete#_prev_completion
|
||||
let blacklist = ['LanguageClient#complete']
|
||||
if a:context.event ==# 'Manual'
|
||||
\ || &l:omnifunc ==# ''
|
||||
\ || index(blacklist, &l:omnifunc) >= 0
|
||||
\ || prev.input ==# a:context.input
|
||||
\ || s:check_input_method()
|
||||
\ || deoplete#custom#_get_option('auto_complete_popup') ==# 'manual'
|
||||
return
|
||||
endif
|
||||
|
||||
for filetype in a:context.filetypes
|
||||
for pattern in deoplete#util#convert2list(
|
||||
\ deoplete#custom#_get_filetype_option(
|
||||
\ 'omni_patterns', filetype, ''))
|
||||
if pattern !=# '' && a:context.input =~# '\%('.pattern.'\)$'
|
||||
let g:deoplete#_context.candidates = []
|
||||
|
||||
let prev.event = a:context.event
|
||||
let prev.input = a:context.input
|
||||
let prev.candidates = []
|
||||
|
||||
if &completeopt =~# 'noselect'
|
||||
call deoplete#mapping#_set_completeopt()
|
||||
call feedkeys("\<C-x>\<C-o>", '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
|
||||
endif
|
||||
endfor
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:completion_timer_start(event) abort
|
||||
if exists('s:completion_timer')
|
||||
call s:completion_timer_stop()
|
||||
endif
|
||||
|
||||
let delay = deoplete#custom#_get_option('auto_complete_delay')
|
||||
if delay > 0
|
||||
let s:completion_timer = timer_start(
|
||||
\ delay, {-> deoplete#handler#_completion_begin(a:event)})
|
||||
else
|
||||
call deoplete#handler#_completion_begin(a:event)
|
||||
endif
|
||||
endfunction
|
||||
function! s:completion_timer_stop() abort
|
||||
if !exists('s:completion_timer')
|
||||
return
|
||||
endif
|
||||
|
||||
call timer_stop(s:completion_timer)
|
||||
unlet s:completion_timer
|
||||
endfunction
|
||||
|
||||
function! s:check_prev_completion(event) abort
|
||||
let prev = g:deoplete#_prev_completion
|
||||
if a:event ==# 'Async' || a:event ==# 'Update' || mode() !=# 'i'
|
||||
\ || empty(get(prev, 'candidates', []))
|
||||
\ || s:check_input_method()
|
||||
return
|
||||
endif
|
||||
|
||||
let input = deoplete#util#get_input(a:event)
|
||||
let complete_str = matchstr(input, '\w\+$')
|
||||
let min_pattern_length = deoplete#custom#_get_option('min_pattern_length')
|
||||
if prev.linenr != line('.') || len(complete_str) < min_pattern_length
|
||||
return
|
||||
endif
|
||||
|
||||
call deoplete#mapping#_set_completeopt()
|
||||
|
||||
let mode = deoplete#custom#_get_option('prev_completion_mode')
|
||||
let candidates = copy(prev.candidates)
|
||||
|
||||
if mode ==# 'filter' || mode ==# 'length'
|
||||
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')
|
||||
if mode ==# 'length'
|
||||
call filter(candidates, 'len(v:val.word) > len(input)')
|
||||
endif
|
||||
elseif mode ==# 'mirror'
|
||||
" pass
|
||||
else
|
||||
return
|
||||
endif
|
||||
|
||||
let g:deoplete#_filtered_prev = {
|
||||
\ 'complete_position': prev.complete_position,
|
||||
\ 'candidates': candidates,
|
||||
\ }
|
||||
call feedkeys("\<Plug>+", 'i')
|
||||
endfunction
|
||||
|
||||
function! deoplete#handler#_async_timer_start() abort
|
||||
let delay = deoplete#custom#_get_option('auto_refresh_delay')
|
||||
if delay <= 0
|
||||
return
|
||||
endif
|
||||
|
||||
call timer_start(max([20, delay]), {-> deoplete#auto_complete()})
|
||||
endfunction
|
||||
|
||||
function! deoplete#handler#_completion_begin(event) abort
|
||||
call deoplete#custom#_update_cache()
|
||||
|
||||
if s:is_skip(a:event)
|
||||
let g:deoplete#_context.candidates = []
|
||||
return
|
||||
endif
|
||||
|
||||
call s:check_prev_completion(a:event)
|
||||
|
||||
if a:event !=# 'Update' && a:event !=# 'Async'
|
||||
call deoplete#init#_prev_completion()
|
||||
endif
|
||||
|
||||
call deoplete#util#rpcnotify(
|
||||
\ 'deoplete_auto_completion_begin', {'event': a:event})
|
||||
endfunction
|
||||
function! s:is_skip(event) abort
|
||||
if a:event ==# 'TextChangedP' && !empty(v:completed_item)
|
||||
return 1
|
||||
endif
|
||||
|
||||
" Note: The check is needed for <C-y> mapping
|
||||
if s:is_skip_prev_text(a:event)
|
||||
return 1
|
||||
endif
|
||||
|
||||
if s:is_skip_text(a:event)
|
||||
" Close the popup
|
||||
if deoplete#util#check_popup()
|
||||
call feedkeys("\<Plug>_", 'i')
|
||||
endif
|
||||
|
||||
return 1
|
||||
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
|
||||
|
||||
return 0
|
||||
endfunction
|
||||
function! s:is_skip_prev_text(event) abort
|
||||
let input = deoplete#util#get_input(a:event)
|
||||
|
||||
" Note: Use g:deoplete#_context is needed instead of
|
||||
" g:deoplete#_prev_completion
|
||||
let prev_input = get(g:deoplete#_context, 'input', '')
|
||||
if input ==# prev_input
|
||||
\ && input !=# ''
|
||||
\ && a:event !=# 'Manual'
|
||||
\ && a:event !=# 'Async'
|
||||
\ && a:event !=# 'Update'
|
||||
\ && a:event !=# 'TextChangedP'
|
||||
return 1
|
||||
endif
|
||||
|
||||
" Note: It fixes insert first candidate automatically problem
|
||||
if a:event ==# 'Update' && prev_input !=# '' && input !=# prev_input
|
||||
return 1
|
||||
endif
|
||||
|
||||
return 0
|
||||
endfunction
|
||||
function! s:is_skip_text(event) abort
|
||||
let input = deoplete#util#get_input(a:event)
|
||||
|
||||
let lastchar = matchstr(input, '.$')
|
||||
let skip_multibyte = deoplete#custom#_get_option('skip_multibyte')
|
||||
if skip_multibyte && len(lastchar) != strwidth(lastchar)
|
||||
\ && empty(get(b:, 'eskk', []))
|
||||
return 1
|
||||
endif
|
||||
|
||||
let displaywidth = strdisplaywidth(input) + 1
|
||||
let is_virtual = virtcol('.') >= displaywidth
|
||||
if &l:formatoptions =~# '[tca]' && &l:textwidth > 0
|
||||
\ && displaywidth >= &l:textwidth
|
||||
if &l:formatoptions =~# '[ta]'
|
||||
\ || !empty(filter(deoplete#util#get_syn_names(),
|
||||
\ "v:val ==# 'Comment'"))
|
||||
\ || is_virtual
|
||||
return 1
|
||||
endif
|
||||
endif
|
||||
|
||||
let skip_chars = deoplete#custom#_get_option('skip_chars')
|
||||
|
||||
return (a:event !=# 'Manual' && input !=# ''
|
||||
\ && index(skip_chars, input[-1:]) >= 0)
|
||||
endfunction
|
||||
function! s:check_input_method() abort
|
||||
return exists('*getimstatus') && getimstatus()
|
||||
endfunction
|
||||
|
||||
function! s:define_on_event(event) abort
|
||||
if !exists('##' . a:event)
|
||||
return
|
||||
endif
|
||||
|
||||
execute 'autocmd deoplete' a:event
|
||||
\ '* call deoplete#send_event('.string(a:event).')'
|
||||
endfunction
|
||||
function! s:define_completion_via_timer(event) abort
|
||||
if !exists('##' . a:event)
|
||||
return
|
||||
endif
|
||||
|
||||
execute 'autocmd deoplete' a:event
|
||||
\ '* call s:completion_timer_start('.string(a:event).')'
|
||||
endfunction
|
||||
|
||||
function! s:on_insert_leave() abort
|
||||
call deoplete#mapping#_restore_completeopt()
|
||||
let g:deoplete#_context = {}
|
||||
call deoplete#init#_prev_completion()
|
||||
|
||||
if &cpoptions =~# '$'
|
||||
" If 'cpoptions' includes '$' with popup, redraw problem exists.
|
||||
redraw
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:on_complete_done() abort
|
||||
if get(v:completed_item, 'word', '') ==# ''
|
||||
return
|
||||
endif
|
||||
|
||||
call deoplete#handler#_skip_next_completion()
|
||||
|
||||
let user_data = get(v:completed_item, 'user_data', '')
|
||||
if type(user_data) !=# v:t_string || user_data ==# ''
|
||||
return
|
||||
endif
|
||||
|
||||
try
|
||||
call s:substitute_suffix(json_decode(user_data))
|
||||
catch /.*/
|
||||
endtry
|
||||
endfunction
|
||||
function! s:substitute_suffix(user_data) abort
|
||||
if !deoplete#custom#_get_option('complete_suffix')
|
||||
\ || !has_key(a:user_data, 'old_suffix')
|
||||
\ || !has_key(a:user_data, 'new_suffix')
|
||||
return
|
||||
endif
|
||||
let old_suffix = a:user_data.old_suffix
|
||||
let new_suffix = a:user_data.new_suffix
|
||||
|
||||
let next_text = deoplete#util#get_next_input('CompleteDone')
|
||||
if stridx(next_text, old_suffix) != 0
|
||||
return
|
||||
endif
|
||||
|
||||
let next_text = new_suffix . next_text[len(old_suffix):]
|
||||
call setline('.', deoplete#util#get_input('CompleteDone') . next_text)
|
||||
endfunction
|
||||
|
||||
function! deoplete#handler#_skip_next_completion() abort
|
||||
if !exists('g:deoplete#_context')
|
||||
return
|
||||
endif
|
||||
|
||||
let input = deoplete#util#get_input('CompleteDone')
|
||||
if input !~# '[/.]$'
|
||||
let g:deoplete#_context.input = input
|
||||
endif
|
||||
call deoplete#mapping#_restore_completeopt()
|
||||
call deoplete#init#_prev_completion()
|
||||
endfunction
|
||||
|
||||
function! s:is_exiting() abort
|
||||
return exists('v:exiting') && v:exiting != v:null
|
||||
endfunction
|
||||
|
||||
function! s:kill_yarp() abort
|
||||
if !exists('g:deoplete#_yarp')
|
||||
return
|
||||
endif
|
||||
|
||||
if g:deoplete#_yarp.job_is_dead
|
||||
return
|
||||
endif
|
||||
|
||||
let job = g:deoplete#_yarp.job
|
||||
if !has('nvim') && !exists('g:yarp_jobstart')
|
||||
" Get job object from vim-hug-neovim-rpc
|
||||
let job = g:_neovim_rpc_jobs[job].job
|
||||
endif
|
||||
|
||||
if has('nvim')
|
||||
call jobstop(job)
|
||||
else
|
||||
call job_stop(job, 'kill')
|
||||
endif
|
||||
|
||||
let g:deoplete#_yarp.job_is_dead = 1
|
||||
endfunction
|
298
bundle/deoplete.nvim/autoload/deoplete/init.vim
Normal file
298
bundle/deoplete.nvim/autoload/deoplete/init.vim
Normal file
@ -0,0 +1,298 @@
|
||||
"=============================================================================
|
||||
" FILE: init.vim
|
||||
" AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
" License: MIT license
|
||||
"=============================================================================
|
||||
|
||||
if !exists('s:is_handler_enabled')
|
||||
let s:is_handler_enabled = 0
|
||||
endif
|
||||
|
||||
function! deoplete#init#_is_handler_enabled() abort
|
||||
return s:is_handler_enabled
|
||||
endfunction
|
||||
|
||||
function! deoplete#init#_initialize() abort
|
||||
if exists('g:deoplete#_initialized')
|
||||
return 1
|
||||
endif
|
||||
|
||||
let g:deoplete#_initialized = v:false
|
||||
|
||||
call deoplete#init#_custom_variables()
|
||||
call deoplete#custom#_update_cache()
|
||||
|
||||
call s:init_internal_variables()
|
||||
|
||||
" For context_filetype check
|
||||
silent! call context_filetype#get()
|
||||
|
||||
if deoplete#init#_channel()
|
||||
return 1
|
||||
endif
|
||||
|
||||
call deoplete#mapping#_init()
|
||||
endfunction
|
||||
function! deoplete#init#_channel() abort
|
||||
if !exists('g:deoplete#_serveraddr')
|
||||
return 1
|
||||
endif
|
||||
|
||||
let python3 = get(g:, 'python3_host_prog', 'python3')
|
||||
if !executable(python3)
|
||||
call deoplete#util#print_error(
|
||||
\ string(python3) . ' is not executable.')
|
||||
call deoplete#util#print_error(
|
||||
\ 'You need to set g:python3_host_prog.')
|
||||
endif
|
||||
if has('nvim') && !has('nvim-0.3.0')
|
||||
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+.')
|
||||
return 1
|
||||
endif
|
||||
|
||||
try
|
||||
if deoplete#util#has_yarp()
|
||||
let g:deoplete#_yarp = yarp#py3('deoplete')
|
||||
call g:deoplete#_yarp.notify('deoplete_init')
|
||||
else
|
||||
" rplugin.vim may not be loaded on VimEnter
|
||||
if !exists('g:loaded_remote_plugins')
|
||||
runtime! plugin/rplugin.vim
|
||||
endif
|
||||
|
||||
call _deoplete_init()
|
||||
endif
|
||||
catch
|
||||
call deoplete#util#print_error(v:exception)
|
||||
call deoplete#util#print_error(v:throwpoint)
|
||||
|
||||
if !has('python3')
|
||||
call deoplete#util#print_error(
|
||||
\ 'deoplete requires Python3 support("+python3").')
|
||||
endif
|
||||
|
||||
if deoplete#init#_python_version_check()
|
||||
call deoplete#util#print_error('deoplete requires Python 3.6.1+.')
|
||||
endif
|
||||
|
||||
if deoplete#util#has_yarp()
|
||||
if !exists('*yarp#py3')
|
||||
call deoplete#util#print_error(
|
||||
\ 'deoplete requires nvim-yarp plugin.')
|
||||
endif
|
||||
else
|
||||
call deoplete#util#print_error(
|
||||
\ 'deoplete failed to load. '
|
||||
\ .'Try the :UpdateRemotePlugins command and restart Neovim. '
|
||||
\ .'See also :checkhealth.')
|
||||
endif
|
||||
|
||||
return 1
|
||||
endtry
|
||||
endfunction
|
||||
function! deoplete#init#_channel_initialized() abort
|
||||
return get(g:, 'deoplete#_initialized', v:false)
|
||||
endfunction
|
||||
function! deoplete#init#_enable_handler() abort
|
||||
call deoplete#handler#_init()
|
||||
let s:is_handler_enabled = 1
|
||||
endfunction
|
||||
function! deoplete#init#_disable_handler() abort
|
||||
augroup deoplete
|
||||
autocmd!
|
||||
augroup END
|
||||
let s:is_handler_enabled = 0
|
||||
endfunction
|
||||
|
||||
function! s:init_internal_variables() abort
|
||||
call deoplete#init#_prev_completion()
|
||||
|
||||
let g:deoplete#_context = {}
|
||||
|
||||
if !exists('g:deoplete#_logging')
|
||||
let g:deoplete#_logging = {}
|
||||
endif
|
||||
unlet! g:deoplete#_initialized
|
||||
try
|
||||
let g:deoplete#_serveraddr =
|
||||
\ deoplete#util#has_yarp() ?
|
||||
\ neovim_rpc#serveraddr() : v:servername
|
||||
if g:deoplete#_serveraddr ==# ''
|
||||
" Use NVIM_LISTEN_ADDRESS
|
||||
let g:deoplete#_serveraddr = $NVIM_LISTEN_ADDRESS
|
||||
endif
|
||||
catch
|
||||
call deoplete#util#print_error(v:exception)
|
||||
call deoplete#util#print_error(v:throwpoint)
|
||||
|
||||
if !has('python3')
|
||||
call deoplete#util#print_error(
|
||||
\ 'deoplete requires Python3 support("+python3").')
|
||||
endif
|
||||
|
||||
if deoplete#util#has_yarp()
|
||||
" Dummy call is needed to check exists()
|
||||
call neovim_rpc#serveraddr()
|
||||
if !exists('*neovim_rpc#serveraddr')
|
||||
call deoplete#util#print_error(
|
||||
\ 'deoplete requires vim-hug-neovim-rpc plugin in Vim.')
|
||||
endif
|
||||
endif
|
||||
endtry
|
||||
endfunction
|
||||
function! deoplete#init#_custom_variables() abort
|
||||
if get(g:, 'deoplete#disable_auto_complete', v:false)
|
||||
call deoplete#custom#option('auto_complete', v:false)
|
||||
endif
|
||||
|
||||
call s:check_custom_option(
|
||||
\ 'g:deoplete#auto_complete_delay',
|
||||
\ 'auto_complete_delay')
|
||||
call s:check_custom_option(
|
||||
\ 'g:deoplete#auto_refresh_delay',
|
||||
\ 'auto_refresh_delay')
|
||||
call s:check_custom_option(
|
||||
\ 'g:deoplete#camel_case',
|
||||
\ 'camel_case')
|
||||
call s:check_custom_option(
|
||||
\ 'g:deoplete#ignore_case',
|
||||
\ 'ignore_case')
|
||||
call s:check_custom_option(
|
||||
\ 'g:deoplete#ignore_sources',
|
||||
\ 'ignore_sources')
|
||||
call s:check_custom_option(
|
||||
\ 'g:deoplete#keyword_patterns',
|
||||
\ 'keyword_patterns')
|
||||
call s:check_custom_option(
|
||||
\ 'g:deoplete#max_list',
|
||||
\ 'max_list')
|
||||
call s:check_custom_option(
|
||||
\ 'g:deoplete#num_processes',
|
||||
\ 'num_processes')
|
||||
call s:check_custom_option(
|
||||
\ 'g:deoplete#auto_complete_start_length',
|
||||
\ 'min_pattern_length')
|
||||
call s:check_custom_option(
|
||||
\ 'g:deoplete#enable_on_insert_enter',
|
||||
\ 'on_insert_enter')
|
||||
call s:check_custom_option(
|
||||
\ 'g:deoplete#enable_profile',
|
||||
\ 'profile')
|
||||
call s:check_custom_option(
|
||||
\ 'g:deoplete#enable_refresh_always',
|
||||
\ 'refresh_always')
|
||||
call s:check_custom_option(
|
||||
\ 'g:deoplete#skip_chars',
|
||||
\ 'skip_chars')
|
||||
call s:check_custom_option(
|
||||
\ 'g:deoplete#sources',
|
||||
\ 'sources')
|
||||
call s:check_custom_option(
|
||||
\ 'g:deoplete#enable_smart_case',
|
||||
\ 'smart_case')
|
||||
call s:check_custom_option(
|
||||
\ 'g:deoplete#enable_complete_suffix',
|
||||
\ 'complete_suffix')
|
||||
call s:check_custom_option(
|
||||
\ 'g:deoplete#enable_yarp',
|
||||
\ 'yarp')
|
||||
|
||||
" Source variables
|
||||
call s:check_custom_var('file',
|
||||
\ 'g:deoplete#file#enable_buffer_path',
|
||||
\ 'enable_buffer_path')
|
||||
call s:check_custom_var('omni',
|
||||
\ 'g:deoplete#omni#input_patterns',
|
||||
\ 'input_patterns')
|
||||
call s:check_custom_var('omni',
|
||||
\ 'g:deoplete#omni#functions',
|
||||
\ 'functions')
|
||||
endfunction
|
||||
|
||||
function! s:check_custom_var(source_name, old_var, new_var) abort
|
||||
if !exists(a:old_var)
|
||||
return
|
||||
endif
|
||||
|
||||
call deoplete#util#print_error(
|
||||
\ printf('%s is deprecated variable. '.
|
||||
\ 'Please use deoplete#custom#var() instead.', a:old_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
|
||||
if !exists(a:old_var)
|
||||
return
|
||||
endif
|
||||
|
||||
call deoplete#util#print_error(
|
||||
\ printf('%s is deprecated variable. '.
|
||||
\ 'Please use deoplete#custom#option() instead.', a:old_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,
|
||||
\ 'candidate_marks': [],
|
||||
\ '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,
|
||||
\ 'omni_patterns': {},
|
||||
\ 'on_insert_enter': v:true,
|
||||
\ 'on_text_changed_i': v:true,
|
||||
\ 'prev_completion_mode': '',
|
||||
\ 'profile': v:false,
|
||||
\ 'refresh_always': v:true,
|
||||
\ 'skip_chars': ['(', ')'],
|
||||
\ 'skip_multibyte': v:false,
|
||||
\ 'smart_case': &smartcase,
|
||||
\ 'sources': {},
|
||||
\ 'trigger_key': v:char,
|
||||
\ 'yarp': v:false,
|
||||
\ }
|
||||
endfunction
|
||||
function! deoplete#init#_prev_completion() abort
|
||||
let g:deoplete#_prev_completion = {
|
||||
\ 'event': '',
|
||||
\ 'input': '',
|
||||
\ 'linenr': -1,
|
||||
\ 'candidates': [],
|
||||
\ 'complete_position': -1,
|
||||
\ }
|
||||
endfunction
|
||||
|
||||
function! deoplete#init#_python_version_check() abort
|
||||
python3 << EOF
|
||||
import vim
|
||||
import sys
|
||||
vim.vars['deoplete#_python_version_check'] = (
|
||||
sys.version_info.major,
|
||||
sys.version_info.minor,
|
||||
sys.version_info.micro) < (3, 6, 1)
|
||||
EOF
|
||||
return get(g:, 'deoplete#_python_version_check', 0)
|
||||
endfunction
|
||||
|
||||
function! deoplete#init#_msgpack_version_check() abort
|
||||
python3 << EOF
|
||||
import vim
|
||||
import msgpack
|
||||
vim.vars['deoplete#_msgpack_version'] = msgpack.version
|
||||
vim.vars['deoplete#_msgpack_version_check'] = msgpack.version < (1, 0, 0)
|
||||
EOF
|
||||
return get(g:, 'deoplete#_msgpack_version_check', 0)
|
||||
endfunction
|
154
bundle/deoplete.nvim/autoload/deoplete/mapping.vim
Normal file
154
bundle/deoplete.nvim/autoload/deoplete/mapping.vim
Normal file
@ -0,0 +1,154 @@
|
||||
"=============================================================================
|
||||
" FILE: mapping.vim
|
||||
" AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
" License: MIT license
|
||||
"=============================================================================
|
||||
|
||||
function! deoplete#mapping#_init() abort
|
||||
" Note: The dummy function is needed for cpoptions bug in neovim
|
||||
inoremap <expr><silent> <Plug>_
|
||||
\ deoplete#mapping#_dummy('deoplete#mapping#_complete')
|
||||
inoremap <expr><silent> <Plug>+
|
||||
\ deoplete#mapping#_dummy('deoplete#mapping#_prev_complete')
|
||||
endfunction
|
||||
function! deoplete#mapping#_dummy(func) abort
|
||||
return "\<C-r>=".a:func."()\<CR>"
|
||||
endfunction
|
||||
function! s:check_completion_info(candidates) abort
|
||||
if !exists('*complete_info')
|
||||
return 0
|
||||
endif
|
||||
|
||||
let info = complete_info()
|
||||
let noinsert = &completeopt =~# 'noinsert'
|
||||
if (info.mode !=# '' && info.mode !=# 'eval')
|
||||
\ || (noinsert && info.selected > 0)
|
||||
\ || (!noinsert && info.selected >= 0)
|
||||
\ || !has_key(g:deoplete#_context, 'complete_position')
|
||||
return 1
|
||||
endif
|
||||
|
||||
let input = getline('.')[: g:deoplete#_context.complete_position - 1]
|
||||
if deoplete#util#check_eskk_phase_henkan()
|
||||
\ && matchstr(input, '.$') =~# '[\u3040-\u304A]$'
|
||||
return 0
|
||||
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
|
||||
endfunction
|
||||
function! deoplete#mapping#_complete() abort
|
||||
if !has_key(g:deoplete#_context, 'candidates')
|
||||
\ || s:check_completion_info(g:deoplete#_context.candidates)
|
||||
\ || !&modifiable
|
||||
return ''
|
||||
endif
|
||||
|
||||
" echomsg string(g:deoplete#_context)
|
||||
if empty(g:deoplete#_context.candidates) && deoplete#util#check_popup()
|
||||
" Note: call complete() to close the popup
|
||||
call complete(1, [])
|
||||
return ''
|
||||
endif
|
||||
|
||||
call complete(g:deoplete#_context.complete_position + 1,
|
||||
\ g:deoplete#_context.candidates)
|
||||
|
||||
return ''
|
||||
endfunction
|
||||
function! deoplete#mapping#_prev_complete() abort
|
||||
if s:check_completion_info(g:deoplete#_filtered_prev.candidates)
|
||||
return ''
|
||||
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')
|
||||
return
|
||||
endif
|
||||
let g:deoplete#_saved_completeopt = &completeopt
|
||||
set completeopt-=longest
|
||||
set completeopt+=menuone
|
||||
set completeopt-=menu
|
||||
if &completeopt !~# 'noinsert\|noselect'
|
||||
set completeopt+=noselect
|
||||
endif
|
||||
endfunction
|
||||
function! deoplete#mapping#_restore_completeopt() abort
|
||||
if exists('g:deoplete#_saved_completeopt')
|
||||
let &completeopt = g:deoplete#_saved_completeopt
|
||||
unlet g:deoplete#_saved_completeopt
|
||||
endif
|
||||
endfunction
|
||||
function! deoplete#mapping#_rpcrequest_wrapper(sources) abort
|
||||
return deoplete#util#rpcnotify(
|
||||
\ 'deoplete_manual_completion_begin',
|
||||
\ {
|
||||
\ 'event': 'Manual',
|
||||
\ 'sources': deoplete#util#convert2list(a:sources)
|
||||
\ })
|
||||
endfunction
|
||||
function! deoplete#mapping#_undo_completion() abort
|
||||
if empty(v:completed_item)
|
||||
return ''
|
||||
endif
|
||||
|
||||
let input = deoplete#util#get_input('')
|
||||
if strridx(input, v:completed_item.word) !=
|
||||
\ len(input) - len(v:completed_item.word)
|
||||
return ''
|
||||
endif
|
||||
|
||||
return repeat("\<C-h>", strchars(v:completed_item.word))
|
||||
endfunction
|
||||
function! deoplete#mapping#_complete_common_string() abort
|
||||
if !deoplete#is_enabled()
|
||||
return ''
|
||||
endif
|
||||
|
||||
" Get cursor word.
|
||||
let prev = g:deoplete#_prev_completion
|
||||
if empty(prev)
|
||||
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:]
|
||||
while stridx(tolower(candidate.word), tolower(common_str)) != 0
|
||||
let common_str = common_str[: -2]
|
||||
endwhile
|
||||
endfor
|
||||
|
||||
if common_str ==# '' || complete_str ==? common_str
|
||||
return ''
|
||||
endif
|
||||
|
||||
return (pumvisible() ? "\<C-e>" : '')
|
||||
\ . repeat("\<BS>", strchars(complete_str)) . common_str
|
||||
endfunction
|
||||
function! deoplete#mapping#_insert_candidate(number) abort
|
||||
let prev = g:deoplete#_prev_completion
|
||||
let candidates = get(prev, 'candidates', [])
|
||||
let word = get(candidates, a:number, {'word': ''}).word
|
||||
if word ==# ''
|
||||
return ''
|
||||
endif
|
||||
|
||||
" Get cursor word.
|
||||
let complete_str = prev.input[prev.complete_position :]
|
||||
return (pumvisible() ? "\<C-e>" : '')
|
||||
\ . repeat("\<BS>", strchars(complete_str)) . word
|
||||
endfunction
|
215
bundle/deoplete.nvim/autoload/deoplete/util.vim
Normal file
215
bundle/deoplete.nvim/autoload/deoplete/util.vim
Normal file
@ -0,0 +1,215 @@
|
||||
"=============================================================================
|
||||
" FILE: util.vim
|
||||
" AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
" License: MIT license
|
||||
"=============================================================================
|
||||
|
||||
function! deoplete#util#print_error(string, ...) abort
|
||||
let name = a:0 ? a:1 : 'deoplete'
|
||||
echohl Error | echomsg printf('[%s] %s', name,
|
||||
\ deoplete#util#string(a:string)) | echohl None
|
||||
endfunction
|
||||
function! deoplete#util#print_warning(string) abort
|
||||
echohl WarningMsg | echomsg '[deoplete] '
|
||||
\ . deoplete#util#string(a:string) | echohl None
|
||||
endfunction
|
||||
function! deoplete#util#print_debug(string) abort
|
||||
echomsg '[deoplete] ' . deoplete#util#string(a:string)
|
||||
endfunction
|
||||
|
||||
function! deoplete#util#convert2list(expr) abort
|
||||
return type(a:expr) ==# v:t_list ? a:expr : [a:expr]
|
||||
endfunction
|
||||
function! deoplete#util#string(expr) abort
|
||||
return type(a:expr) ==# v:t_string ? a:expr : string(a:expr)
|
||||
endfunction
|
||||
|
||||
function! deoplete#util#get_input(event) abort
|
||||
let mode = mode()
|
||||
if a:event ==# 'InsertEnter'
|
||||
let mode = 'i'
|
||||
endif
|
||||
let input = (mode ==# 'i' ? (col('.')-1) : col('.')) >= len(getline('.')) ?
|
||||
\ getline('.') :
|
||||
\ matchstr(getline('.'),
|
||||
\ '^.*\%' . (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
|
||||
return getline('.')[len(deoplete#util#get_input(a:event)) :]
|
||||
endfunction
|
||||
|
||||
function! deoplete#util#vimoption2python(option) abort
|
||||
return '[\w' . s:vimoption2python(a:option) . ']'
|
||||
endfunction
|
||||
function! deoplete#util#vimoption2python_not(option) abort
|
||||
return '[^\w' . s:vimoption2python(a:option) . ']'
|
||||
endfunction
|
||||
function! s:vimoption2python(option) abort
|
||||
let has_dash = 0
|
||||
let patterns = []
|
||||
for pattern in split(a:option, ',')
|
||||
if pattern =~# '\d\+'
|
||||
let pattern = substitute(pattern, '\d\+',
|
||||
\ '\=nr2char(submatch(0))', 'g')
|
||||
endif
|
||||
|
||||
if pattern ==# ''
|
||||
" ,
|
||||
call add(patterns, ',')
|
||||
elseif pattern ==# '\'
|
||||
call add(patterns, '\\')
|
||||
elseif pattern ==# '-'
|
||||
let has_dash = 1
|
||||
else
|
||||
" Avoid ambiguous Python 3 RE syntax for nested sets
|
||||
if pattern =~# '^--'
|
||||
let pattern = '\' . pattern
|
||||
elseif pattern =~# '--$'
|
||||
let pattern = split(pattern, '-')[0] . '-\-'
|
||||
endif
|
||||
|
||||
call add(patterns, pattern)
|
||||
endif
|
||||
endfor
|
||||
|
||||
" Dash must be last.
|
||||
if has_dash
|
||||
call add(patterns, '-')
|
||||
endif
|
||||
|
||||
return join(deoplete#util#uniq(patterns), '')
|
||||
endfunction
|
||||
|
||||
function! deoplete#util#uniq(list) abort
|
||||
let list = map(copy(a:list), '[v:val, v:val]')
|
||||
let i = 0
|
||||
let seen = {}
|
||||
while i < len(list)
|
||||
let key = string(list[i][1])
|
||||
if has_key(seen, key)
|
||||
call remove(list, i)
|
||||
else
|
||||
let seen[key] = 1
|
||||
let i += 1
|
||||
endif
|
||||
endwhile
|
||||
return map(list, 'v:val[0]')
|
||||
endfunction
|
||||
|
||||
function! deoplete#util#get_syn_names() abort
|
||||
if col('$') >= 200
|
||||
return []
|
||||
endif
|
||||
|
||||
let names = []
|
||||
try
|
||||
" Note: synstack() seems broken in concealed text.
|
||||
for id in synstack(line('.'), (mode() ==# 'i' ? col('.')-1 : col('.')))
|
||||
let name = synIDattr(id, 'name')
|
||||
call add(names, name)
|
||||
if synIDattr(synIDtrans(id), 'name') !=# name
|
||||
call add(names, synIDattr(synIDtrans(id), 'name'))
|
||||
endif
|
||||
endfor
|
||||
catch
|
||||
" Ignore error
|
||||
endtry
|
||||
return names
|
||||
endfunction
|
||||
|
||||
function! deoplete#util#neovim_version() abort
|
||||
redir => v
|
||||
silent version
|
||||
redir END
|
||||
return split(v, '\n')[0]
|
||||
endfunction
|
||||
|
||||
function! deoplete#util#has_yarp() abort
|
||||
return !has('nvim') || deoplete#custom#_get_option('yarp')
|
||||
endfunction
|
||||
|
||||
function! deoplete#util#get_keyword_pattern(filetype) abort
|
||||
let keyword_patterns = deoplete#custom#_get_option('keyword_patterns')
|
||||
if empty(keyword_patterns)
|
||||
let patterns = deoplete#custom#_get_filetype_option(
|
||||
\ 'keyword_patterns', a:filetype, '')
|
||||
else
|
||||
let filetype = has_key(keyword_patterns, a:filetype) ? a:filetype : '_'
|
||||
let patterns = get(keyword_patterns, filetype, '')
|
||||
endif
|
||||
let pattern = join(deoplete#util#convert2list(patterns), '|')
|
||||
|
||||
" Convert keyword.
|
||||
let k_pattern = deoplete#util#vimoption2python(
|
||||
\ &l:iskeyword . (&l:lisp ? ',-' : ''))
|
||||
return substitute(pattern, '\\k', '\=k_pattern', 'g')
|
||||
endfunction
|
||||
|
||||
function! deoplete#util#rpcnotify(method, context) abort
|
||||
if !deoplete#init#_channel_initialized()
|
||||
return ''
|
||||
endif
|
||||
|
||||
let a:context['rpc'] = a:method
|
||||
|
||||
if deoplete#util#has_yarp()
|
||||
if g:deoplete#_yarp.job_is_dead
|
||||
return ''
|
||||
endif
|
||||
call g:deoplete#_yarp.notify(a:method, a:context)
|
||||
else
|
||||
call rpcnotify(g:deoplete#_channel_id, a:method, a:context)
|
||||
endif
|
||||
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
" Compare versions. Return values is the distance between versions. Each
|
||||
" version integer (from right to left) is an ascending power of 100.
|
||||
"
|
||||
" Example:
|
||||
" '0.1.10' is (1 * 100) + 10, or 110.
|
||||
" '1.2.3' is (1 * 10000) + (2 * 100) + 3, or 10203.
|
||||
"
|
||||
" Returns:
|
||||
" <0 if a < b
|
||||
" >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 l = min([len(a), len(b)])
|
||||
let d = 0
|
||||
|
||||
" Only compare the parts that are common to both versions.
|
||||
for i in range(l)
|
||||
let d += (a[i] - b[i]) * pow(100, l - i - 1)
|
||||
endfor
|
||||
|
||||
return d
|
||||
endfunction
|
||||
|
||||
function! deoplete#util#split(string) abort
|
||||
return split(a:string, '\s*,\s*')
|
||||
endfunction
|
||||
|
||||
function! deoplete#util#check_eskk_phase_henkan() abort
|
||||
if !exists('b:eskk') || empty(b:eskk)
|
||||
return 0
|
||||
endif
|
||||
|
||||
let preedit = eskk#get_preedit()
|
||||
let phase = preedit.get_henkan_phase()
|
||||
return phase is g:eskk#preedit#PHASE_HENKAN
|
||||
endfunction
|
||||
|
||||
function! deoplete#util#check_popup() abort
|
||||
return exists('*complete_info') && complete_info().mode ==# 'eval'
|
||||
endfunction
|
79
bundle/deoplete.nvim/autoload/health/deoplete.vim
Normal file
79
bundle/deoplete.nvim/autoload/health/deoplete.vim
Normal file
@ -0,0 +1,79 @@
|
||||
"=============================================================================
|
||||
" FILE: deoplete.vim
|
||||
" AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
" TJ DeVries <devries.timothyj at gmail.com>
|
||||
" License: MIT license
|
||||
"=============================================================================
|
||||
|
||||
function! s:check_t_list() abort
|
||||
if exists('v:t_list')
|
||||
call health#report_ok('exists("v:t_list") was successful')
|
||||
else
|
||||
call health#report_error('exists("v:t_list") was not successful',
|
||||
\ 'Deoplete requires neovim 0.3.0+!')
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:check_timers() abort
|
||||
if has('timers')
|
||||
call health#report_ok('has("timers") was successful')
|
||||
else
|
||||
call health#report_error('has("timers") was not successful',
|
||||
\ 'Deoplete requires timers support("+timers").')
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:check_required_python() abort
|
||||
if has('python3')
|
||||
call health#report_ok('has("python3") was successful')
|
||||
else
|
||||
call health#report_error('has("python3") was not successful', [
|
||||
\ 'Please install the python3 package for neovim.',
|
||||
\ 'A good guide can be found here: ' .
|
||||
\ 'https://github.com/tweekmonster/nvim-python-doctor/'
|
||||
\ . 'wiki/Advanced:-Using-pyenv'
|
||||
\ ]
|
||||
\ )
|
||||
endif
|
||||
|
||||
if !deoplete#init#_python_version_check()
|
||||
call health#report_ok('Require Python 3.6.1+ was successful')
|
||||
else
|
||||
call health#report_error(
|
||||
\ 'Require Python 3.6.1+ was not successful',
|
||||
\ 'Please use Python 3.6.1+.')
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:check_required_msgpack() abort
|
||||
if !deoplete#init#_msgpack_version_check()
|
||||
call health#report_ok('Require msgpack 1.0.0+ was successful')
|
||||
else
|
||||
call health#report_error(
|
||||
\ 'Require msgpack 1.0.0+ was not successful',
|
||||
\ 'Please install/upgrade msgpack 1.0.0+.')
|
||||
endif
|
||||
endfunction
|
||||
|
||||
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'
|
||||
\ )
|
||||
endfunction
|
||||
|
||||
function! health#deoplete#check() abort
|
||||
call health#report_start('deoplete.nvim')
|
||||
|
||||
call s:check_t_list()
|
||||
call s:check_timers()
|
||||
call s:check_required_python()
|
||||
call s:check_required_msgpack()
|
||||
|
||||
call s:still_have_issues()
|
||||
endfunction
|
8
bundle/deoplete.nvim/codecov.yml
Normal file
8
bundle/deoplete.nvim/codecov.yml
Normal file
@ -0,0 +1,8 @@
|
||||
coverage:
|
||||
status:
|
||||
project: false
|
||||
patch: true
|
||||
changes: true
|
||||
|
||||
comment:
|
||||
layout: "diff"
|
2118
bundle/deoplete.nvim/doc/deoplete.txt
Normal file
2118
bundle/deoplete.nvim/doc/deoplete.txt
Normal file
File diff suppressed because it is too large
Load Diff
15
bundle/deoplete.nvim/plugin/deoplete.vim
Normal file
15
bundle/deoplete.nvim/plugin/deoplete.vim
Normal file
@ -0,0 +1,15 @@
|
||||
"=============================================================================
|
||||
" FILE: deoplete.vim
|
||||
" AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
" License: MIT license
|
||||
"=============================================================================
|
||||
|
||||
if exists('g:loaded_deoplete')
|
||||
finish
|
||||
endif
|
||||
let g:loaded_deoplete = 1
|
||||
|
||||
" Global options definition.
|
||||
if get(g:, 'deoplete#enable_at_startup', 0)
|
||||
call deoplete#enable()
|
||||
endif
|
72
bundle/deoplete.nvim/rplugin/python3/deoplete/__init__.py
Normal file
72
bundle/deoplete.nvim/rplugin/python3/deoplete/__init__.py
Normal file
@ -0,0 +1,72 @@
|
||||
# ============================================================================
|
||||
# FILE: __init__.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import typing
|
||||
|
||||
from importlib.util import find_spec
|
||||
from deoplete.deoplete import Deoplete
|
||||
from deoplete.util import Nvim
|
||||
|
||||
|
||||
if find_spec('yarp'):
|
||||
import vim
|
||||
elif find_spec('pynvim'):
|
||||
import pynvim as vim
|
||||
else:
|
||||
import neovim as vim
|
||||
|
||||
Context = typing.Dict[str, typing.Any]
|
||||
|
||||
if hasattr(vim, 'plugin'):
|
||||
# Neovim only
|
||||
|
||||
@vim.plugin
|
||||
class DeopleteHandlers(object):
|
||||
|
||||
def __init__(self, vim: Nvim):
|
||||
self._vim = vim
|
||||
|
||||
@vim.function('_deoplete_init', sync=False) # type: ignore
|
||||
def init_channel(self,
|
||||
args: typing.List[typing.Any]) -> None:
|
||||
self._deoplete = Deoplete(self._vim)
|
||||
self._vim.call('deoplete#send_event', 'BufReadPost')
|
||||
|
||||
@vim.rpc_export('deoplete_enable_logging') # type: ignore
|
||||
def enable_logging(self, context: Context) -> None:
|
||||
self._deoplete.enable_logging()
|
||||
|
||||
@vim.rpc_export('deoplete_auto_completion_begin') # type: ignore
|
||||
def auto_completion_begin(self, context: Context) -> None:
|
||||
self._deoplete.completion_begin(context)
|
||||
|
||||
@vim.rpc_export('deoplete_manual_completion_begin') # type: ignore
|
||||
def manual_completion_begin(self, context: Context) -> None:
|
||||
self._deoplete.completion_begin(context)
|
||||
|
||||
@vim.rpc_export('deoplete_on_event') # type: ignore
|
||||
def on_event(self, context: Context) -> None:
|
||||
self._deoplete.on_event(context)
|
||||
|
||||
|
||||
if find_spec('yarp'):
|
||||
|
||||
global_deoplete = Deoplete(vim)
|
||||
|
||||
def deoplete_init() -> None:
|
||||
global_deoplete._vim.call('deoplete#send_event', 'BufReadPost')
|
||||
|
||||
def deoplete_enable_logging(context: Context) -> None:
|
||||
global_deoplete.enable_logging()
|
||||
|
||||
def deoplete_auto_completion_begin(context: Context) -> None:
|
||||
global_deoplete.completion_begin(context)
|
||||
|
||||
def deoplete_manual_completion_begin(context: Context) -> None:
|
||||
global_deoplete.completion_begin(context)
|
||||
|
||||
def deoplete_on_event(context: Context) -> None:
|
||||
global_deoplete.on_event(context)
|
39
bundle/deoplete.nvim/rplugin/python3/deoplete/base/filter.py
Normal file
39
bundle/deoplete.nvim/rplugin/python3/deoplete/base/filter.py
Normal file
@ -0,0 +1,39 @@
|
||||
# ============================================================================
|
||||
# FILE: filter.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import typing
|
||||
|
||||
from abc import abstractmethod
|
||||
from deoplete.logger import LoggingMixin
|
||||
from deoplete.util import error_vim, Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Base(LoggingMixin):
|
||||
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
self.vim = vim
|
||||
self.name = 'base'
|
||||
self.description = ''
|
||||
self.vars: typing.Dict[str, typing.Any] = {}
|
||||
|
||||
def on_event(self, context: UserContext) -> None:
|
||||
pass
|
||||
|
||||
def get_var(self, var_name: str) -> typing.Optional[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
|
||||
|
||||
@abstractmethod
|
||||
def filter(self, context: UserContext) -> Candidates:
|
||||
return []
|
||||
|
||||
def print_error(self, expr: typing.Any) -> None:
|
||||
error_vim(self.vim, expr)
|
100
bundle/deoplete.nvim/rplugin/python3/deoplete/base/source.py
Normal file
100
bundle/deoplete.nvim/rplugin/python3/deoplete/base/source.py
Normal file
@ -0,0 +1,100 @@
|
||||
# ============================================================================
|
||||
# FILE: source.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import re
|
||||
import typing
|
||||
from abc import abstractmethod
|
||||
|
||||
from deoplete.logger import LoggingMixin
|
||||
from deoplete.util import debug, error_vim, Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Base(LoggingMixin):
|
||||
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
self.vim = vim
|
||||
self.description = ''
|
||||
self.mark = ''
|
||||
self.name = ''
|
||||
self.max_pattern_length = 80
|
||||
self.min_pattern_length = -1
|
||||
self.input_pattern = ''
|
||||
self.input_patterns: typing.Dict[str, str] = {}
|
||||
self.matchers = ['matcher_fuzzy']
|
||||
self.sorters = ['sorter_rank']
|
||||
self.converters = [
|
||||
'converter_remove_overlap',
|
||||
'converter_truncate_abbr',
|
||||
'converter_truncate_kind',
|
||||
'converter_truncate_info',
|
||||
'converter_truncate_menu']
|
||||
self.filetypes: typing.List[str] = []
|
||||
self.keyword_patterns: typing.List[str] = []
|
||||
self.is_debug_enabled = False
|
||||
self.is_bytepos = False
|
||||
self.is_initialized = False
|
||||
self.is_volatile = False
|
||||
self.is_async = False
|
||||
self.is_silent = False
|
||||
self.is_skip_langmap = True
|
||||
self.rank = 100
|
||||
self.disabled_syntaxes: typing.List[str] = []
|
||||
self.events: typing.List[str] = []
|
||||
self.vars: typing.Dict[str, typing.Any] = {}
|
||||
self.max_abbr_width = 80
|
||||
self.max_kind_width = 40
|
||||
self.max_info_width = 200
|
||||
self.max_menu_width = 40
|
||||
self.max_candidates = 500
|
||||
self.matcher_key = ''
|
||||
self.dup = False
|
||||
|
||||
def get_complete_position(self, context: UserContext) -> int:
|
||||
m = re.search('(?:' + context['keyword_pattern'] + ')$|$',
|
||||
context['input'])
|
||||
return m.start() if m else -1
|
||||
|
||||
def print(self, expr: typing.Any) -> None:
|
||||
if not self.is_silent:
|
||||
debug(self.vim, expr)
|
||||
|
||||
def print_error(self, expr: typing.Any) -> None:
|
||||
if not self.is_silent:
|
||||
error_vim(self.vim, expr)
|
||||
|
||||
@abstractmethod
|
||||
def gather_candidates(self, context: UserContext) -> Candidates:
|
||||
return []
|
||||
|
||||
def on_event(self, context: UserContext) -> None:
|
||||
pass
|
||||
|
||||
def get_var(self, var_name: str) -> typing.Optional[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
|
||||
|
||||
def get_filetype_var(self, filetype: str,
|
||||
var_name: str) -> typing.Optional[typing.Any]:
|
||||
var = self.get_var(var_name)
|
||||
if var is None:
|
||||
return None
|
||||
ft = filetype if (filetype in var) else '_'
|
||||
return var.get(ft, '')
|
||||
|
||||
def get_input_pattern(self, filetype: str) -> str:
|
||||
if not self.input_patterns:
|
||||
return self.input_pattern
|
||||
|
||||
ft = filetype if (filetype in self.input_patterns) else '_'
|
||||
return self.input_patterns.get(ft, self.input_pattern)
|
||||
|
||||
def get_buf_option(self, option: str) -> typing.Any:
|
||||
return self.vim.call('getbufvar', '%', '&' + option)
|
604
bundle/deoplete.nvim/rplugin/python3/deoplete/child.py
Normal file
604
bundle/deoplete.nvim/rplugin/python3/deoplete/child.py
Normal file
@ -0,0 +1,604 @@
|
||||
# ============================================================================
|
||||
# FILE: child.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import copy
|
||||
import os.path
|
||||
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)
|
||||
|
||||
UserContext = typing.Dict[str, typing.Any]
|
||||
Candidates = typing.Dict[str, typing.Any]
|
||||
Result = typing.Dict[str, typing.Any]
|
||||
|
||||
|
||||
class Child(logger.LoggingMixin):
|
||||
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
self.name = 'child'
|
||||
|
||||
self._vim = vim
|
||||
self._filters: typing.Dict[str, typing.Any] = {}
|
||||
self._sources: typing.Dict[str, typing.Any] = {}
|
||||
self._profile_flag = None
|
||||
self._profile_start_time = 0
|
||||
self._loaded_sources: typing.Dict[str, typing.Any] = {}
|
||||
self._loaded_filters: typing.Dict[str, typing.Any] = {}
|
||||
self._source_errors: typing.Dict[str, int] = defaultdict(int)
|
||||
self._prev_results: typing.Dict[str, Result] = {}
|
||||
if msgpack.version < (1, 0, 0):
|
||||
self._packer = msgpack.Packer(
|
||||
encoding='utf-8',
|
||||
unicode_errors='surrogateescape')
|
||||
self._unpacker = msgpack.Unpacker(
|
||||
encoding='utf-8',
|
||||
unicode_errors='surrogateescape')
|
||||
else:
|
||||
self._unpacker = msgpack.Unpacker(
|
||||
unicode_errors='surrogateescape')
|
||||
self._packer = msgpack.Packer(
|
||||
unicode_errors='surrogateescape')
|
||||
self._ignore_sources: typing.List[typing.Any] = []
|
||||
|
||||
def main_loop(self, stdout: typing.Any) -> None:
|
||||
while True:
|
||||
feed = sys.stdin.buffer.raw.read(102400) # type: ignore
|
||||
if feed is None:
|
||||
continue
|
||||
if feed == b'':
|
||||
# EOF
|
||||
return
|
||||
|
||||
self._unpacker.feed(feed)
|
||||
|
||||
for child_in in self._unpacker:
|
||||
name = child_in['name']
|
||||
args = child_in['args']
|
||||
queue_id = child_in['queue_id']
|
||||
|
||||
ret = self.main(name, args, queue_id)
|
||||
if ret:
|
||||
self._write(stdout, ret)
|
||||
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]:
|
||||
ret = None
|
||||
if name == 'enable_logging':
|
||||
self._enable_logging()
|
||||
elif name == 'add_source':
|
||||
self._add_source(args[0])
|
||||
elif name == 'add_filter':
|
||||
self._add_filter(args[0])
|
||||
elif name == 'set_source_attributes':
|
||||
self._set_source_attributes(args[0])
|
||||
elif name == 'on_event':
|
||||
self._on_event(args[0])
|
||||
elif name == 'merge_results':
|
||||
results = self._merge_results(args[0], queue_id)
|
||||
if results['is_async'] or results['merged_results']:
|
||||
ret = results
|
||||
return ret
|
||||
|
||||
def _write(self, stdout: typing.Any, expr: typing.Any) -> None:
|
||||
stdout.buffer.write(self._packer.pack(expr))
|
||||
stdout.flush()
|
||||
|
||||
def _enable_logging(self) -> None:
|
||||
logging = self._vim.vars['deoplete#_logging']
|
||||
logger.setup(self._vim, logging['level'], logging['logfile'])
|
||||
self.is_debug_enabled = True
|
||||
|
||||
def _add_source(self, path: str) -> None:
|
||||
source = None
|
||||
try:
|
||||
Source = import_plugin(path, 'source', 'Source')
|
||||
if not Source:
|
||||
return
|
||||
|
||||
source = Source(self._vim)
|
||||
name = os.path.splitext(os.path.basename(path))[0]
|
||||
source.name = getattr(source, 'name', name)
|
||||
source.path = path
|
||||
if source.name in self._loaded_sources:
|
||||
# Duplicated name
|
||||
error_tb(self._vim, 'Duplicated source: %s' % source.name)
|
||||
error_tb(self._vim, 'path: "%s" "%s"' %
|
||||
(path, self._loaded_sources[source.name]))
|
||||
source = None
|
||||
except Exception:
|
||||
error_tb(self._vim, 'Could not load source: %s' % path)
|
||||
finally:
|
||||
if source:
|
||||
self._loaded_sources[source.name] = path
|
||||
self._sources[source.name] = source
|
||||
self.debug( # type: ignore
|
||||
f'Loaded Source: {source.name} ({path})')
|
||||
|
||||
def _add_filter(self, path: str) -> None:
|
||||
f = None
|
||||
try:
|
||||
Filter = import_plugin(path, 'filter', 'Filter')
|
||||
if not Filter:
|
||||
return
|
||||
|
||||
f = Filter(self._vim)
|
||||
name = os.path.splitext(os.path.basename(path))[0]
|
||||
f.name = getattr(f, 'name', name)
|
||||
f.path = path
|
||||
if f.name in self._loaded_filters:
|
||||
# Duplicated name
|
||||
error_tb(self._vim, 'Duplicated filter: %s' % f.name)
|
||||
error_tb(self._vim, 'path: "%s" "%s"' %
|
||||
(path, self._loaded_filters[f.name]))
|
||||
f = None
|
||||
except Exception:
|
||||
# Exception occurred when loading a filter. Log stack trace.
|
||||
error_tb(self._vim, 'Could not load filter: %s' % path)
|
||||
finally:
|
||||
if f:
|
||||
self._loaded_filters[f.name] = path
|
||||
self._filters[f.name] = f
|
||||
self.debug( # type: ignore
|
||||
f'Loaded Filter: {f.name} ({path})')
|
||||
|
||||
def _merge_results(self, context: UserContext,
|
||||
queue_id: typing.Optional[int]) -> typing.Dict[
|
||||
str, typing.Any]:
|
||||
results = self._gather_results(context)
|
||||
|
||||
merged_results = []
|
||||
for result in [x for x in results
|
||||
if not self._is_skip(x['context'], x['source'])]:
|
||||
candidates = self._get_candidates(
|
||||
result, context['input'], context['next_input'])
|
||||
if candidates:
|
||||
rank = get_custom(context['custom'],
|
||||
result['source'].name, 'rank',
|
||||
result['source'].rank)
|
||||
merged_results.append({
|
||||
'complete_position': result['complete_position'],
|
||||
'candidates': candidates,
|
||||
'rank': rank,
|
||||
})
|
||||
|
||||
is_async = len([x for x in results if x['is_async']]) > 0
|
||||
|
||||
return {
|
||||
'queue_id': queue_id,
|
||||
'is_async': is_async,
|
||||
'merged_results': merged_results,
|
||||
}
|
||||
|
||||
def _gather_results(self, context: UserContext) -> typing.List[Result]:
|
||||
# Note: self._vim.current.buffer may not work when Vim quit
|
||||
if context['changedtick'] != self._vim.eval('b:changedtick'):
|
||||
return []
|
||||
results = []
|
||||
|
||||
for source in [x[1] for x in self._itersource(context)]:
|
||||
try:
|
||||
result = self._get_result(context, source)
|
||||
if not result:
|
||||
continue
|
||||
self._prev_results[source.name] = result
|
||||
results.append(result)
|
||||
except Exception as exc:
|
||||
self._handle_source_exception(source, exc)
|
||||
|
||||
return results
|
||||
|
||||
def _get_result(self, context: UserContext,
|
||||
source: typing.Any) -> Result:
|
||||
if source.disabled_syntaxes and 'syntax_names' not in context:
|
||||
context['syntax_names'] = get_syn_names(self._vim)
|
||||
|
||||
ctx = copy.deepcopy(context)
|
||||
|
||||
charpos = source.get_complete_position(ctx)
|
||||
if charpos >= 0 and source.is_bytepos:
|
||||
charpos = bytepos2charpos(
|
||||
ctx['encoding'], ctx['input'], charpos)
|
||||
|
||||
ctx['char_position'] = charpos
|
||||
ctx['complete_position'] = charpos2bytepos(
|
||||
ctx['encoding'], ctx['input'], charpos)
|
||||
ctx['complete_str'] = ctx['input'][ctx['char_position']:]
|
||||
|
||||
if charpos < 0 or self._is_skip(ctx, source):
|
||||
if source.name in self._prev_results:
|
||||
self._prev_results.pop(source.name)
|
||||
# Skip
|
||||
return {}
|
||||
|
||||
if (source.name in self._prev_results and
|
||||
self._use_previous_result(
|
||||
context, self._prev_results[source.name],
|
||||
source.is_volatile, source.is_async)):
|
||||
return self._prev_results[source.name]
|
||||
|
||||
ctx['is_async'] = False
|
||||
ctx['is_refresh'] = True
|
||||
ctx['max_abbr_width'] = min(source.max_abbr_width,
|
||||
ctx['max_abbr_width'])
|
||||
ctx['max_kind_width'] = min(source.max_kind_width,
|
||||
ctx['max_kind_width'])
|
||||
ctx['max_info_width'] = source.max_info_width
|
||||
ctx['max_menu_width'] = min(source.max_menu_width,
|
||||
ctx['max_menu_width'])
|
||||
if ctx['max_abbr_width'] > 0:
|
||||
ctx['max_abbr_width'] = max(20, ctx['max_abbr_width'])
|
||||
if ctx['max_kind_width'] > 0:
|
||||
ctx['max_kind_width'] = max(10, ctx['max_kind_width'])
|
||||
if ctx['max_info_width'] > 0:
|
||||
ctx['max_info_width'] = max(10, ctx['max_info_width'])
|
||||
if ctx['max_menu_width'] > 0:
|
||||
ctx['max_menu_width'] = max(10, ctx['max_menu_width'])
|
||||
|
||||
# Gathering
|
||||
self._profile_start(ctx, source.name)
|
||||
ctx['vars'] = self._vim.vars
|
||||
ctx['candidates'] = source.gather_candidates(ctx)
|
||||
if ctx['is_async']:
|
||||
source.is_async = True
|
||||
ctx['vars'] = None
|
||||
self._profile_end(source.name)
|
||||
|
||||
if ctx['candidates'] is None:
|
||||
return {}
|
||||
|
||||
ctx['candidates'] = convert2candidates(ctx['candidates'])
|
||||
|
||||
return {
|
||||
'name': source.name,
|
||||
'source': source,
|
||||
'context': ctx,
|
||||
'is_async': ctx['is_async'],
|
||||
'prev_linenr': ctx['position'][1],
|
||||
'prev_input': ctx['input'],
|
||||
'input': ctx['input'],
|
||||
'complete_position': ctx['complete_position'],
|
||||
'candidates': ctx['candidates'],
|
||||
}
|
||||
|
||||
def _gather_async_results(self, result: Result,
|
||||
source: typing.Any) -> None:
|
||||
try:
|
||||
context = result['context']
|
||||
context['is_refresh'] = False
|
||||
context['vars'] = self._vim.vars
|
||||
async_candidates = source.gather_candidates(context)
|
||||
context['vars'] = None
|
||||
result['is_async'] = context['is_async']
|
||||
if async_candidates is None:
|
||||
return
|
||||
context['candidates'] += convert2candidates(async_candidates)
|
||||
except Exception as exc:
|
||||
self._handle_source_exception(source, exc)
|
||||
|
||||
def _handle_source_exception(self,
|
||||
source: typing.Any, exc: Exception) -> None:
|
||||
if isinstance(exc, SourceInitError):
|
||||
error(self._vim,
|
||||
f'Error when loading source {source.name}: {exc}. '
|
||||
'Ignoring.')
|
||||
self._ignore_sources.append(source.name)
|
||||
return
|
||||
|
||||
self._source_errors[source.name] += 1
|
||||
if source.is_silent:
|
||||
return
|
||||
if self._source_errors[source.name] > 2:
|
||||
error(self._vim,
|
||||
f'Too many errors from "{source.name}". '
|
||||
'This source is disabled until Neovim is restarted.')
|
||||
self._ignore_sources.append(source.name)
|
||||
else:
|
||||
error_tb(self._vim, f'Error from {source.name}: {exc}')
|
||||
|
||||
def _process_filter(self, f: typing.Any,
|
||||
context: UserContext, max_candidates: int) -> None:
|
||||
try:
|
||||
self._profile_start(context, f.name)
|
||||
if (isinstance(context['candidates'], dict) and
|
||||
'sorted_candidates' in context['candidates']):
|
||||
filtered: typing.List[typing.Any] = []
|
||||
context['is_sorted'] = True
|
||||
for candidates in context['candidates']['sorted_candidates']:
|
||||
context['candidates'] = candidates
|
||||
filtered += f.filter(context)
|
||||
else:
|
||||
filtered = f.filter(context)
|
||||
if max_candidates > 0:
|
||||
filtered = filtered[: max_candidates]
|
||||
context['candidates'] = filtered
|
||||
self._profile_end(f.name)
|
||||
except Exception:
|
||||
error_tb(self._vim, 'Errors from: %s' % f)
|
||||
|
||||
def _get_candidates(self, result: Result,
|
||||
context_input: str, next_input: str
|
||||
) -> typing.Optional[Candidates]:
|
||||
source = result['source']
|
||||
|
||||
# Gather async results
|
||||
if result['is_async']:
|
||||
self._gather_async_results(result, source)
|
||||
|
||||
if not result['candidates']:
|
||||
return None
|
||||
|
||||
# Source context
|
||||
ctx = copy.copy(result['context'])
|
||||
|
||||
ctx['input'] = context_input
|
||||
ctx['next_input'] = next_input
|
||||
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']
|
||||
|
||||
# Match
|
||||
matchers = [self._filters[x] for x
|
||||
in source.matchers if x in self._filters]
|
||||
if source.matcher_key != '':
|
||||
original_candidates = ctx['candidates']
|
||||
# Convert word key to matcher_key
|
||||
for candidate in original_candidates:
|
||||
candidate['__save_word'] = candidate['word']
|
||||
candidate['word'] = candidate[source.matcher_key]
|
||||
for f in matchers:
|
||||
self._process_filter(f, ctx, source.max_candidates)
|
||||
if source.matcher_key != '':
|
||||
# Restore word key
|
||||
for candidate in original_candidates:
|
||||
candidate['word'] = candidate['__save_word']
|
||||
del candidate['__save_word']
|
||||
|
||||
# Sort
|
||||
sorters = [self._filters[x] for x
|
||||
in source.sorters if x in self._filters]
|
||||
for f in sorters:
|
||||
self._process_filter(f, ctx, source.max_candidates)
|
||||
|
||||
# Note: converter may break candidates
|
||||
ctx['candidates'] = copy.deepcopy(ctx['candidates'])
|
||||
|
||||
# Convert
|
||||
converters = [self._filters[x] for x
|
||||
in source.converters if x in self._filters]
|
||||
for f in converters:
|
||||
self._process_filter(f, ctx, source.max_candidates)
|
||||
|
||||
if (isinstance(ctx['candidates'], dict) and
|
||||
'sorted_candidates' in ctx['candidates']):
|
||||
sorted_candidates = ctx['candidates']['sorted_candidates']
|
||||
ctx['candidates'] = []
|
||||
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 + ' '
|
||||
|
||||
refresh = False
|
||||
refresh_always = self._vim.call(
|
||||
'deoplete#custom#_get_option', 'refresh_always')
|
||||
auto_complete = self._vim.call(
|
||||
'deoplete#custom#_get_option', 'auto_complete')
|
||||
eskk_check = self._vim.call(
|
||||
'deoplete#util#check_eskk_phase_henkan')
|
||||
if refresh_always and auto_complete and not eskk_check:
|
||||
refresh = True
|
||||
|
||||
for candidate in ctx['candidates']:
|
||||
candidate['icase'] = 1
|
||||
candidate['equal'] = refresh
|
||||
|
||||
# Set default menu
|
||||
if (mark != ' ' and
|
||||
candidate.get('menu', '').find(mark) != 0):
|
||||
candidate['menu'] = mark + candidate.get('menu', '')
|
||||
|
||||
if source.dup:
|
||||
candidate['dup'] = 1
|
||||
# Note: cannot use set() for dict
|
||||
if source.dup:
|
||||
# Remove duplicates
|
||||
ctx['candidates'] = uniq_list_dict(ctx['candidates'])
|
||||
|
||||
return ctx['candidates'] # type: ignore
|
||||
|
||||
def _itersource(self, context: UserContext
|
||||
) -> typing.Generator[typing.Any, None, None]:
|
||||
filetypes = context['filetypes']
|
||||
ignore_sources = set(self._ignore_sources)
|
||||
for ft in filetypes:
|
||||
ignore_sources.update(
|
||||
self._vim.call('deoplete#custom#_get_filetype_option',
|
||||
'ignore_sources', ft, []))
|
||||
|
||||
for source_name, source in self._get_sources().items():
|
||||
if source.filetypes is None or source_name in ignore_sources:
|
||||
continue
|
||||
if context['sources'] and source_name not in context['sources']:
|
||||
continue
|
||||
if source.filetypes and not any(x in filetypes
|
||||
for x in source.filetypes):
|
||||
continue
|
||||
if not source.is_initialized and hasattr(source, 'on_init'):
|
||||
self.debug('on_init Source: ' + source.name) # type: ignore
|
||||
try:
|
||||
context['vars'] = self._vim.vars
|
||||
source.on_init(context)
|
||||
context['vars'] = None
|
||||
except Exception as exc:
|
||||
if isinstance(exc, SourceInitError):
|
||||
error(self._vim, 'Error when loading source '
|
||||
f'{source_name}: {exc}. Ignoring.')
|
||||
else:
|
||||
error_tb(self._vim, 'Error when loading source '
|
||||
f'{source_name}: {exc}. Ignoring.')
|
||||
self._ignore_sources.append(source_name)
|
||||
continue
|
||||
else:
|
||||
source.is_initialized = True
|
||||
yield source_name, source
|
||||
|
||||
def _profile_start(self, context: UserContext, name: str) -> None:
|
||||
if self._profile_flag == 0 or not self.is_debug_enabled:
|
||||
return
|
||||
|
||||
if not self._profile_flag:
|
||||
self._profile_flag = self._vim.call(
|
||||
'deoplete#custom#_get_option', 'profile')
|
||||
if self._profile_flag:
|
||||
return self._profile_start(context, name)
|
||||
elif self._profile_flag:
|
||||
self.debug(f'Profile Start: {name}')
|
||||
self._profile_start_time = time.monotonic()
|
||||
|
||||
def _profile_end(self, name: str) -> None:
|
||||
if not self._profile_start_time:
|
||||
return
|
||||
|
||||
self.debug( # type: ignore
|
||||
'Profile End : {0:<25} time={1:2.10f}'.format(
|
||||
name, time.monotonic() - self._profile_start_time))
|
||||
|
||||
def _use_previous_result(self, context: UserContext,
|
||||
result: Result, is_volatile: bool,
|
||||
is_async: bool) -> bool:
|
||||
if context['position'][1] != result['prev_linenr']:
|
||||
return False
|
||||
elif is_async:
|
||||
# Note: If it is async, the cache must be used to call
|
||||
# gather_async_results().
|
||||
return bool(context['input'] == result['prev_input'])
|
||||
elif is_volatile:
|
||||
# Note: If it is volatile, the cache must be disabled to refresh
|
||||
# candidates.
|
||||
return False
|
||||
else:
|
||||
return bool(re.sub(r'\w*$', '', context['input']) ==
|
||||
re.sub(r'\w*$', '', result['prev_input']) and
|
||||
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
|
||||
|
||||
iminsert = self._vim.call('getbufvar', '%', '&iminsert')
|
||||
if iminsert == 1 and source.is_skip_langmap:
|
||||
return True
|
||||
|
||||
for ft in context['filetypes']:
|
||||
input_pattern = source.get_input_pattern(ft)
|
||||
if (input_pattern != '' and
|
||||
re.search('(' + input_pattern + ')$', context['input'])):
|
||||
return False
|
||||
auto_complete_popup = self._vim.call(
|
||||
'deoplete#custom#_get_option', 'auto_complete_popup')
|
||||
if context['event'] == 'Manual' or auto_complete_popup == 'manual':
|
||||
return False
|
||||
return not (source.min_pattern_length <=
|
||||
len(context['complete_str']) <= source.max_pattern_length)
|
||||
|
||||
def _set_source_attributes(self, context: UserContext) -> None:
|
||||
"""Set source attributes from the context.
|
||||
|
||||
Each item in `attrs` is the attribute name.
|
||||
"""
|
||||
attrs = (
|
||||
'converters',
|
||||
'disabled_syntaxes',
|
||||
'dup',
|
||||
'filetypes',
|
||||
'input_pattern',
|
||||
'is_debug_enabled',
|
||||
'is_silent',
|
||||
'is_volatile',
|
||||
'mark',
|
||||
'matchers',
|
||||
'max_abbr_width',
|
||||
'max_candidates',
|
||||
'max_info_width',
|
||||
'max_kind_width',
|
||||
'max_menu_width',
|
||||
'max_pattern_length',
|
||||
'min_pattern_length',
|
||||
'sorters',
|
||||
)
|
||||
|
||||
for name, source in self._get_sources().items():
|
||||
self.debug('Set Source attributes: %s', name) # type: ignore
|
||||
|
||||
source.dup = bool(source.filetypes)
|
||||
|
||||
for attr in attrs:
|
||||
source_attr = getattr(source, attr, None)
|
||||
custom = get_custom(context['custom'],
|
||||
name, attr, source_attr)
|
||||
if type(getattr(source, attr)) != type(custom):
|
||||
# Type check
|
||||
error(self._vim, f'source {source.name}: '
|
||||
f'custom attr "{attr}" is wrong type.')
|
||||
elif custom and isinstance(source_attr, dict):
|
||||
# Update values if it is dict
|
||||
source_attr.update(custom)
|
||||
else:
|
||||
setattr(source, attr, custom)
|
||||
|
||||
self.debug('Attribute: %s (%s)', # type: ignore
|
||||
attr, getattr(source, attr))
|
||||
|
||||
# Default min_pattern_length
|
||||
if source.min_pattern_length < 0:
|
||||
source.min_pattern_length = self._vim.call(
|
||||
'deoplete#custom#_get_option', 'min_pattern_length')
|
||||
|
||||
def _on_event(self, context: UserContext) -> None:
|
||||
event = context['event']
|
||||
context['vars'] = self._vim.vars
|
||||
for source_name, source in self._itersource(context):
|
||||
if not source.events or event in source.events:
|
||||
try:
|
||||
source.on_event(context)
|
||||
except Exception as exc:
|
||||
error_tb(self._vim,
|
||||
f'Exception during {source.name}.on_event '
|
||||
'for event {!r}: {}'.format(event, exc))
|
||||
|
||||
for f in self._filters.values():
|
||||
f.on_event(context)
|
||||
context['vars'] = None
|
||||
|
||||
def _get_sources(self) -> typing.Dict[str, typing.Any]:
|
||||
# Note: for the size change of "self._sources" error
|
||||
return copy.copy(self._sources)
|
150
bundle/deoplete.nvim/rplugin/python3/deoplete/context.py
Normal file
150
bundle/deoplete.nvim/rplugin/python3/deoplete/context.py
Normal file
@ -0,0 +1,150 @@
|
||||
# ============================================================================
|
||||
# FILE: context.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import os
|
||||
import re
|
||||
import typing
|
||||
|
||||
from deoplete.util import Nvim
|
||||
|
||||
UserContext = typing.Dict[str, typing.Any]
|
||||
|
||||
|
||||
class Context(object):
|
||||
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
self._vim = vim
|
||||
self._prev_filetype = ''
|
||||
self._cached: typing.Optional[UserContext] = None
|
||||
self._cached_filetype = self._init_cached_filetype(
|
||||
self._prev_filetype)
|
||||
self._init_cached()
|
||||
self._context_filetype: UserContext = {}
|
||||
|
||||
def get(self, event: str) -> UserContext:
|
||||
text = self._vim.call('deoplete#util#get_input', event)
|
||||
[filetype, filetypes, same_filetypes] = self._get_context_filetype(
|
||||
text, event, self._vim.call('getbufvar', '%', '&filetype'))
|
||||
|
||||
m = re.search(r'\w$', text)
|
||||
word_len = len(m.group(0)) if m else 0
|
||||
max_width = self._vim.call('winwidth', 0) - self._vim.call('col', '.')
|
||||
max_width += word_len
|
||||
|
||||
context: UserContext = {
|
||||
'changedtick': self._vim.call(
|
||||
'getbufvar', '%', 'changedtick', 0),
|
||||
'event': event,
|
||||
'filetype': filetype,
|
||||
'filetypes': filetypes,
|
||||
'input': text,
|
||||
'max_abbr_width': max_width,
|
||||
'max_kind_width': max_width,
|
||||
'max_menu_width': max_width,
|
||||
'next_input': self._vim.call(
|
||||
'deoplete#util#get_next_input', event),
|
||||
'position': self._vim.call('getpos', '.'),
|
||||
'same_filetypes': same_filetypes,
|
||||
}
|
||||
context.update(self._cached) # type: ignore
|
||||
|
||||
if filetype != self._prev_filetype:
|
||||
self._prev_filetype = filetype
|
||||
self._cached_filetype = self._init_cached_filetype(filetype)
|
||||
|
||||
context.update(self._cached_filetype)
|
||||
|
||||
return context
|
||||
|
||||
def _init_cached_filetype(self, filetype: str) -> UserContext:
|
||||
return {
|
||||
'keyword_pattern': self._vim.call(
|
||||
'deoplete#util#get_keyword_pattern', filetype),
|
||||
'sources': self._vim.call(
|
||||
'deoplete#custom#_get_filetype_option',
|
||||
'sources', filetype, []),
|
||||
}
|
||||
|
||||
def _init_cached(self) -> None:
|
||||
bufnr = self._vim.call('expand', '<abuf>')
|
||||
if not bufnr:
|
||||
bufnr = self._vim.call('bufnr', '%')
|
||||
if not bufnr:
|
||||
bufnr = -1
|
||||
bufname = ''
|
||||
else:
|
||||
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 = ''
|
||||
|
||||
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,
|
||||
text: str, event: str, filetype: str
|
||||
) -> typing.List[typing.Any]:
|
||||
if not self._context_filetype and self._vim.call(
|
||||
'exists', '*context_filetype#get_filetype'):
|
||||
# Force context_filetype call
|
||||
self._vim.call('context_filetype#get_filetype')
|
||||
|
||||
linenr = self._vim.call('line', '.')
|
||||
bufnr = self._vim.call('bufnr', '%')
|
||||
|
||||
if (not self._context_filetype or
|
||||
self._context_filetype['prev_filetype'] != filetype or
|
||||
self._context_filetype['line'] != linenr or
|
||||
self._context_filetype['bufnr'] != bufnr or
|
||||
re.sub(r'\w+$', '', self._context_filetype['input']) !=
|
||||
re.sub(r'\w+$', '', self._context_filetype['input']) or
|
||||
event == 'InsertEnter'):
|
||||
self._cache_context_filetype(text, filetype, linenr, bufnr)
|
||||
|
||||
return [
|
||||
self._context_filetype['filetype'],
|
||||
self._context_filetype['filetypes'],
|
||||
self._context_filetype['same_filetypes']
|
||||
]
|
||||
|
||||
def _cache_context_filetype(self, text: str, filetype: str,
|
||||
linenr: int, bufnr: int) -> None:
|
||||
exists_context_filetype = self._vim.call(
|
||||
'exists', '*context_filetype#get_filetype')
|
||||
self._context_filetype = {
|
||||
'line': linenr,
|
||||
'bufnr': bufnr,
|
||||
'input': text,
|
||||
'prev_filetype': filetype,
|
||||
'filetype': (
|
||||
self._vim.call('context_filetype#get_filetype')
|
||||
if exists_context_filetype
|
||||
else (filetype if filetype else 'nothing')),
|
||||
'filetypes': (
|
||||
self._vim.call('context_filetype#get_filetypes')
|
||||
if exists_context_filetype
|
||||
else filetype.split('.')),
|
||||
'same_filetypes': (
|
||||
self._vim.call('context_filetype#get_same_filetypes')
|
||||
if exists_context_filetype else []),
|
||||
}
|
288
bundle/deoplete.nvim/rplugin/python3/deoplete/deoplete.py
Normal file
288
bundle/deoplete.nvim/rplugin/python3/deoplete/deoplete.py
Normal file
@ -0,0 +1,288 @@
|
||||
# ============================================================================
|
||||
# FILE: deoplete.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
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
|
||||
|
||||
UserContext = typing.Dict[str, typing.Any]
|
||||
Candidates = typing.Dict[str, typing.Any]
|
||||
Parent = typing.Union[deoplete.parent.SyncParent, deoplete.parent.AsyncParent]
|
||||
|
||||
|
||||
class Deoplete(logger.LoggingMixin):
|
||||
|
||||
def __init__(self, vim: Nvim):
|
||||
self.name = 'core'
|
||||
|
||||
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] = {}
|
||||
self._prev_input = ''
|
||||
self._prev_next_input = ''
|
||||
self._context: typing.Optional[Context] = None
|
||||
self._parents: typing.List[Parent] = []
|
||||
self._parent_count = 0
|
||||
self._max_parents = self._vim.call('deoplete#custom#_get_option',
|
||||
'num_processes')
|
||||
|
||||
if self._max_parents != 1 and not hasattr(self._vim, 'loop'):
|
||||
msg = ('pynvim 0.3.0+ is required for %d parents. '
|
||||
'Using single process.' % self._max_parents)
|
||||
error(self._vim, msg)
|
||||
self._max_parents = 1
|
||||
|
||||
# Enable logging for more information, and e.g.
|
||||
# deoplete-jedi picks up the log filename from deoplete's handler in
|
||||
# its on_init.
|
||||
if self._vim.vars['deoplete#_logging']:
|
||||
self.enable_logging()
|
||||
|
||||
if hasattr(self._vim, 'channel_id'):
|
||||
self._vim.vars['deoplete#_channel_id'] = self._vim.channel_id
|
||||
self._vim.vars['deoplete#_initialized'] = True
|
||||
|
||||
def enable_logging(self) -> None:
|
||||
logging = self._vim.vars['deoplete#_logging']
|
||||
logger.setup(self._vim, logging['level'], logging['logfile'])
|
||||
self.is_debug_enabled = True
|
||||
|
||||
def init_context(self) -> None:
|
||||
self._context = Context(self._vim)
|
||||
|
||||
# Initialization
|
||||
context = self._context.get('Init')
|
||||
context['rpc'] = 'deoplete_on_event'
|
||||
self.on_event(context)
|
||||
|
||||
def completion_begin(self, user_context: UserContext) -> None:
|
||||
if not self._context:
|
||||
self.init_context()
|
||||
|
||||
context = self._context.get(user_context['event']) # type: ignore
|
||||
context.update(user_context)
|
||||
|
||||
self.debug('completion_begin (%s): %r', # type: ignore
|
||||
context['event'], context['input'])
|
||||
|
||||
if self._vim.call('deoplete#handler#_check_omnifunc', context):
|
||||
return
|
||||
|
||||
self._check_recache(context)
|
||||
|
||||
try:
|
||||
(is_async, needs_poll,
|
||||
position, candidates) = self._merge_results(context)
|
||||
except Exception:
|
||||
error_tb(self._vim, 'Error while gathering completions')
|
||||
|
||||
is_async = False
|
||||
needs_poll = False
|
||||
position = -1
|
||||
candidates = []
|
||||
|
||||
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']
|
||||
prev_candidates = prev_completion['candidates']
|
||||
event = context['event']
|
||||
if (event == 'Async' or event == 'Update' and
|
||||
prev_candidates and candidates == prev_candidates):
|
||||
return
|
||||
|
||||
# error(self._vim, candidates)
|
||||
self._vim.vars['deoplete#_context'] = {
|
||||
'complete_position': position,
|
||||
'candidates': candidates,
|
||||
'event': context['event'],
|
||||
'input': context['input'],
|
||||
'is_async': is_async,
|
||||
}
|
||||
|
||||
if candidates or self._vim.call('deoplete#util#check_popup'):
|
||||
self.debug('do_complete (%s): ' # type: ignore
|
||||
+ '%d candidates, input=%s, complete_position=%d, '
|
||||
+ 'is_async=%d',
|
||||
context['event'],
|
||||
len(candidates), context['input'], position,
|
||||
is_async)
|
||||
self._vim.call('deoplete#handler#_do_complete')
|
||||
|
||||
def on_event(self, user_context: UserContext) -> None:
|
||||
self._vim.call('deoplete#custom#_update_cache')
|
||||
|
||||
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)
|
||||
|
||||
self.debug('initialized context: %s', context) # type: ignore
|
||||
|
||||
self.debug('on_event: %s', context['event']) # type: ignore
|
||||
|
||||
self._check_recache(context)
|
||||
|
||||
for parent in self._parents:
|
||||
parent.on_event(context)
|
||||
|
||||
def _get_results(self, context: UserContext) -> typing.List[typing.Any]:
|
||||
is_async = False
|
||||
needs_poll = False
|
||||
results: typing.List[Candidates] = []
|
||||
for cnt, parent in enumerate(self._parents):
|
||||
if cnt in self._prev_results:
|
||||
# Use previous result
|
||||
results += copy.deepcopy(
|
||||
self._prev_results[cnt]) # type: ignore
|
||||
else:
|
||||
result = parent.merge_results(context)
|
||||
is_async = is_async or result[0]
|
||||
needs_poll = needs_poll or result[1]
|
||||
if not result[0]:
|
||||
self._prev_results[cnt] = result[2]
|
||||
results += result[2]
|
||||
return [is_async, needs_poll, results]
|
||||
|
||||
def _merge_results(self, context: UserContext) -> typing.Tuple[
|
||||
bool, bool, int, typing.List[typing.Any]]:
|
||||
use_prev = (context['input'] == self._prev_input
|
||||
and context['next_input'] == self._prev_next_input
|
||||
and context['event'] != 'Manual')
|
||||
if not use_prev:
|
||||
self._prev_results = {}
|
||||
|
||||
self._prev_input = context['input']
|
||||
self._prev_next_input = context['next_input']
|
||||
|
||||
[is_async, needs_poll, results] = self._get_results(context)
|
||||
|
||||
if not results:
|
||||
return (is_async, needs_poll, -1, [])
|
||||
|
||||
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):
|
||||
candidates = result['candidates']
|
||||
prefix = context['input'][
|
||||
complete_position:result['complete_position']]
|
||||
|
||||
if prefix != '':
|
||||
for candidate in candidates:
|
||||
# Add prefix
|
||||
candidate['word'] = prefix + candidate['word']
|
||||
|
||||
all_candidates += candidates
|
||||
|
||||
# self.debug(candidates)
|
||||
max_list = self._vim.call(
|
||||
'deoplete#custom#_get_option', 'max_list')
|
||||
if max_list > 0:
|
||||
all_candidates = all_candidates[: max_list]
|
||||
|
||||
candidate_marks = self._vim.call(
|
||||
'deoplete#custom#_get_option', 'candidate_marks')
|
||||
if candidate_marks:
|
||||
all_candidates = copy.deepcopy(all_candidates)
|
||||
for i, candidate in enumerate(all_candidates):
|
||||
mark = (candidate_marks[i] if i < len(candidate_marks) and
|
||||
candidate_marks[i] else ' ')
|
||||
candidate['menu'] = mark + ' ' + candidate.get('menu', '')
|
||||
|
||||
return (is_async, needs_poll, complete_position, all_candidates)
|
||||
|
||||
def _add_parent(self, parent_cls: typing.Callable[
|
||||
[Nvim], Parent]) -> None:
|
||||
parent = parent_cls(self._vim)
|
||||
if self._vim.vars['deoplete#_logging']:
|
||||
parent.enable_logging()
|
||||
self._parents.append(parent)
|
||||
|
||||
def _find_rplugins(self,
|
||||
source: str) -> typing.Generator[str, None, None]:
|
||||
"""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))
|
||||
|
||||
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'):
|
||||
continue
|
||||
self._loaded_paths.add(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.debug( # type: ignore
|
||||
f'Process {self._parent_count}: {path}')
|
||||
|
||||
self._parent_count += 1
|
||||
if self._max_parents > 0:
|
||||
self._parent_count %= self._max_parents
|
||||
|
||||
self._set_source_attributes(context)
|
||||
|
||||
def _load_filters(self, context: UserContext) -> None:
|
||||
for path in self._find_rplugins('filter'):
|
||||
for parent in self._parents:
|
||||
parent.add_filter(path)
|
||||
|
||||
def _set_source_attributes(self, context: UserContext) -> None:
|
||||
for parent in self._parents:
|
||||
parent.set_source_attributes(context)
|
||||
|
||||
def _check_recache(self, context: UserContext) -> None:
|
||||
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)
|
||||
|
||||
if context['rpc'] != 'deoplete_on_event':
|
||||
self.on_event(context)
|
||||
elif context['custom'] != self._custom:
|
||||
self._set_source_attributes(context)
|
||||
self._custom = context['custom']
|
@ -0,0 +1,6 @@
|
||||
class SourceInitError(Exception):
|
||||
"""Error during source initialization.
|
||||
|
||||
This can be used to have a clearer message, where not traceback gets
|
||||
displayed.
|
||||
"""
|
14
bundle/deoplete.nvim/rplugin/python3/deoplete/filter/base.py
Normal file
14
bundle/deoplete.nvim/rplugin/python3/deoplete/filter/base.py
Normal file
@ -0,0 +1,14 @@
|
||||
# ============================================================================
|
||||
# FILE: base.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
# For backward compatibility
|
||||
from deoplete.base.filter import Base as _Base
|
||||
from deoplete.util import Nvim
|
||||
|
||||
|
||||
class Base(_Base):
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
@ -0,0 +1,40 @@
|
||||
# ============================================================================
|
||||
# FILE: converter_auto_delimiter.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import typing
|
||||
|
||||
from deoplete.base.filter import Base
|
||||
from deoplete.util import Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'converter_auto_delimiter'
|
||||
self.description = 'auto delimiter converter'
|
||||
self.vars = {
|
||||
'delimiters': ['/'],
|
||||
}
|
||||
|
||||
def filter(self, context: UserContext) -> Candidates:
|
||||
delimiters: typing.List[str] = self.get_var( # type: ignore
|
||||
'delimiters')
|
||||
for candidate, delimiter in [
|
||||
[x, last_find(x['abbr'], delimiters)]
|
||||
for x in context['candidates']
|
||||
if 'abbr' in x and x['abbr'] and
|
||||
not last_find(x['word'], delimiters) and
|
||||
last_find(x['abbr'], delimiters)]:
|
||||
candidate['word'] += delimiter
|
||||
return context['candidates'] # type: ignore
|
||||
|
||||
|
||||
def last_find(s: str, needles: typing.List[str]) -> typing.Optional[str]:
|
||||
for needle in needles:
|
||||
if len(s) >= len(needle) and s[-len(needle):] == needle:
|
||||
return needle
|
||||
return None
|
@ -0,0 +1,29 @@
|
||||
# ============================================================================
|
||||
# FILE: converter_auto_paren.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import re
|
||||
|
||||
from deoplete.base.filter import Base
|
||||
from deoplete.util import Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'converter_auto_paren'
|
||||
self.description = 'auto add parentheses converter'
|
||||
|
||||
def filter(self, context: UserContext) -> Candidates:
|
||||
p1 = re.compile(r'\(\)?$')
|
||||
p2 = re.compile(r'\(.*\)')
|
||||
for candidate in [
|
||||
x for x in context['candidates']
|
||||
if not p1.search(x['word']) and
|
||||
(('abbr' in x and p2.search(x['abbr'])) or
|
||||
('info' in x and p2.search(x['info'])))]:
|
||||
candidate['word'] += '('
|
||||
return context['candidates'] # type: ignore
|
@ -0,0 +1,32 @@
|
||||
# ============================================================================
|
||||
# FILE: converter_case.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import re
|
||||
|
||||
from deoplete.base.filter import Base
|
||||
from deoplete.util import Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'converter_case'
|
||||
self.description = 'case converter'
|
||||
|
||||
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
|
||||
|
||||
complete_lower = complete_str.lower()
|
||||
complete_len = len(complete_str)
|
||||
for candidate in [
|
||||
x for x in context['candidates']
|
||||
if x['word'].lower().startswith(complete_lower)]:
|
||||
candidate['word'] = complete_str + candidate[
|
||||
'word'][complete_len:]
|
||||
return context['candidates'] # type: ignore
|
@ -0,0 +1,49 @@
|
||||
# ============================================================================
|
||||
# FILE: converter_remove_overlap.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import re
|
||||
import typing
|
||||
|
||||
from deoplete.base.filter import Base
|
||||
from deoplete.util import Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'converter_remove_overlap'
|
||||
self.description = 'remove overlap converter'
|
||||
|
||||
def filter(self, context: UserContext) -> Candidates:
|
||||
if not context['next_input']:
|
||||
return context['candidates'] # type: ignore
|
||||
next_input_words = [x for x in re.split(
|
||||
r'([a-zA-Z_]+|\W)', context['next_input']) if x]
|
||||
|
||||
check_pairs = []
|
||||
if self.vim.call('searchpair', '(', '', ')', 'bnw'):
|
||||
check_pairs.append(['(', ')'])
|
||||
if self.vim.call('searchpair', '[', '', ']', 'bnw'):
|
||||
check_pairs.append(['[', ']'])
|
||||
|
||||
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]:
|
||||
continue
|
||||
if 'abbr' not in candidate:
|
||||
candidate['abbr'] = word
|
||||
candidate['word'] = word[: -overlap]
|
||||
return context['candidates'] # type: ignore
|
||||
|
||||
|
||||
def overlap_length(left: str, next_input_words: typing.List[str]) -> int:
|
||||
pos = len(next_input_words)
|
||||
while pos > 0 and not left.endswith(''.join(next_input_words[:pos])):
|
||||
pos -= 1
|
||||
return len(''.join(next_input_words[:pos]))
|
@ -0,0 +1,25 @@
|
||||
# ============================================================================
|
||||
# FILE: converter_remove_paren.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import re
|
||||
|
||||
from deoplete.base.filter import Base
|
||||
from deoplete.util import Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'converter_remove_paren'
|
||||
self.description = 'remove parentheses converter'
|
||||
|
||||
def filter(self, context: UserContext) -> Candidates:
|
||||
for candidate in [x for x in context['candidates']
|
||||
if '(' in x['word']]:
|
||||
candidate['word'] = re.sub(r'\(.*\)(\$\d+)?', '',
|
||||
candidate['word'])
|
||||
return context['candidates'] # type: ignore
|
@ -0,0 +1,77 @@
|
||||
# ============================================================================
|
||||
# FILE: converter_reorder_attr.py
|
||||
# AUTHOR: @reaysawa
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import re
|
||||
import typing
|
||||
|
||||
from deoplete.base.filter import Base
|
||||
from deoplete.util import Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'converter_reorder_attr'
|
||||
self.description = 'Reorder candidates based on their attributes'
|
||||
self.vars = {
|
||||
'attrs_order': {},
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def filter_attrs(candidates: Candidates,
|
||||
preferred_order_attrs: typing.Dict[str, typing.Any],
|
||||
max_list_size: int = 500) -> Candidates:
|
||||
context_candidates = candidates[:]
|
||||
new_candidates = []
|
||||
new_candidates_len = 0
|
||||
|
||||
for attr in preferred_order_attrs.keys():
|
||||
for expr in preferred_order_attrs[attr]:
|
||||
disabled = expr[0] == '!'
|
||||
if disabled:
|
||||
expr = expr[1:]
|
||||
|
||||
expr = re.compile(expr)
|
||||
size = len(context_candidates)
|
||||
i = 0
|
||||
while i < size:
|
||||
candidate = context_candidates[i]
|
||||
if attr in candidate and expr.search(candidate[attr]):
|
||||
candidate = context_candidates.pop(i)
|
||||
# Popping will make 'i' effectively go forward an extra
|
||||
# time; because of that, decrease for now and wait for
|
||||
# the +1 at the bottom to balance that out.
|
||||
i -= 1
|
||||
size -= 1
|
||||
if not disabled:
|
||||
new_candidates.append(candidate)
|
||||
new_candidates_len += 1
|
||||
# Stop filtering if the maximum has been achieved
|
||||
if new_candidates_len == max_list_size:
|
||||
return new_candidates
|
||||
i += 1
|
||||
|
||||
# Add remaining at the bottom
|
||||
new_candidates.extend(context_candidates)
|
||||
# Go to the next attribute with the new list order
|
||||
context_candidates = new_candidates
|
||||
|
||||
return new_candidates
|
||||
|
||||
def filter(self, context: UserContext) -> Candidates:
|
||||
preferred_order_attrs = self.get_var( # type: ignore
|
||||
'attrs_order').get(context['filetype'], [])
|
||||
if not context['candidates'] or not preferred_order_attrs:
|
||||
return context['candidates'] # type: ignore
|
||||
|
||||
max_list_size = self.vim.call(
|
||||
'deoplete#custom#_get_option', 'max_list'
|
||||
)
|
||||
|
||||
return self.filter_attrs(
|
||||
context['candidates'], preferred_order_attrs, max_list_size
|
||||
)
|
@ -0,0 +1,28 @@
|
||||
# ============================================================================
|
||||
# FILE: converter_truncate_abbr.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from deoplete.base.filter import Base
|
||||
from deoplete.util import truncate_skipping, Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'converter_truncate_abbr'
|
||||
self.description = 'truncate abbr converter'
|
||||
|
||||
def filter(self, context: UserContext) -> Candidates:
|
||||
max_width = context['max_abbr_width']
|
||||
if max_width <= 0:
|
||||
return context['candidates'] # type: ignore
|
||||
|
||||
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
|
@ -0,0 +1,28 @@
|
||||
# ============================================================================
|
||||
# FILE: converter_truncate_info.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from deoplete.base.filter import Base
|
||||
from deoplete.util import truncate_skipping, Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'converter_truncate_info'
|
||||
self.description = 'truncate info converter'
|
||||
|
||||
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
|
||||
|
||||
footer_width = 1
|
||||
for candidate in context['candidates']:
|
||||
candidate['info'] = truncate_skipping(
|
||||
candidate.get('info', ''),
|
||||
max_width, '..', footer_width)
|
||||
return context['candidates'] # type: ignore
|
@ -0,0 +1,29 @@
|
||||
# ============================================================================
|
||||
# FILE: converter_truncate_kind.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from deoplete.base.filter import Base
|
||||
from deoplete.util import truncate_skipping, Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'converter_truncate_kind'
|
||||
self.description = 'truncate kind converter'
|
||||
|
||||
def filter(self, context: UserContext) -> Candidates:
|
||||
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
|
||||
|
||||
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
|
@ -0,0 +1,29 @@
|
||||
# ============================================================================
|
||||
# FILE: converter_truncate_menu.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from deoplete.base.filter import Base
|
||||
from deoplete.util import truncate_skipping, Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'converter_truncate_menu'
|
||||
self.description = 'truncate menu converter'
|
||||
|
||||
def filter(self, context: UserContext) -> Candidates:
|
||||
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
|
||||
|
||||
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
|
@ -0,0 +1,76 @@
|
||||
# ============================================================================
|
||||
# FILE: matcher_cpsm.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import os
|
||||
import sys
|
||||
import typing
|
||||
|
||||
from deoplete.base.filter import Base
|
||||
from deoplete.util import error, globruntime
|
||||
from deoplete.util import Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'matcher_cpsm'
|
||||
self.description = 'cpsm matcher'
|
||||
|
||||
self._cpsm: typing.Optional[typing.Any] = None
|
||||
|
||||
def filter(self, context: UserContext) -> Candidates:
|
||||
if (not context['candidates'] or not context['input']
|
||||
or self._cpsm is False):
|
||||
return context['candidates'] # type: ignore
|
||||
|
||||
if self._cpsm is None:
|
||||
errmsg = self._init_cpsm(context)
|
||||
if errmsg:
|
||||
error(self.vim, 'matcher_cpsm: %s' % errmsg)
|
||||
return []
|
||||
|
||||
complete_str = context['complete_str']
|
||||
if context['ignorecase']:
|
||||
complete_str = complete_str.lower()
|
||||
|
||||
cpsm_result = self._get_cpsm_result(
|
||||
context['candidates'], complete_str)
|
||||
return [x for x in context['candidates']
|
||||
if x['word'] in sorted(cpsm_result, key=cpsm_result.index)]
|
||||
|
||||
def _init_cpsm(self, context: UserContext) -> str:
|
||||
ext = '.pyd' if context['is_windows'] else '.so'
|
||||
fname = 'bin/cpsm_py' + ext
|
||||
found = globruntime(self.vim.options['runtimepath'], fname)
|
||||
errmsg = ''
|
||||
if found:
|
||||
sys.path.insert(0, os.path.dirname(found[0]))
|
||||
try:
|
||||
import cpsm_py
|
||||
except ImportError as exc:
|
||||
import traceback
|
||||
errmsg = 'Could not import cpsm_py: %s\n%s' % (
|
||||
exc, traceback.format_exc())
|
||||
else:
|
||||
self._cpsm = cpsm_py
|
||||
finally:
|
||||
sys.path.pop(0)
|
||||
else:
|
||||
errmsg = (
|
||||
'%s was not found in runtimepath. '
|
||||
'You must install/build cpsm with Python 3 support.' % (
|
||||
fname))
|
||||
if errmsg:
|
||||
self._cpsm = False
|
||||
return errmsg
|
||||
|
||||
def _get_cpsm_result(self, candidates: Candidates,
|
||||
pattern: str) -> typing.List[str]:
|
||||
return self._cpsm.ctrlp_match( # type: ignore
|
||||
(d['word'] for d in candidates),
|
||||
pattern, limit=1000, ispath=False)[0]
|
@ -0,0 +1,30 @@
|
||||
# ============================================================================
|
||||
# FILE: matcher_full_fuzzy.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import re
|
||||
from deoplete.base.filter import Base
|
||||
from deoplete.util import fuzzy_escape, Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'matcher_full_fuzzy'
|
||||
self.description = 'full fuzzy matcher'
|
||||
|
||||
def filter(self, context: UserContext) -> Candidates:
|
||||
complete_str = context['complete_str']
|
||||
if context['ignorecase']:
|
||||
complete_str = complete_str.lower()
|
||||
p = re.compile(fuzzy_escape(complete_str, context['camelcase']))
|
||||
if context['ignorecase']:
|
||||
return [x for x in context['candidates']
|
||||
if p.search(x['word'].lower())]
|
||||
else:
|
||||
return [x for x in context['candidates']
|
||||
if p.search(x['word'])]
|
@ -0,0 +1,45 @@
|
||||
# ============================================================================
|
||||
# FILE: matcher_fuzzy.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
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
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'matcher_fuzzy'
|
||||
self.description = 'fuzzy matcher'
|
||||
|
||||
def filter(self, context: UserContext) -> Candidates:
|
||||
complete_str = context['complete_str']
|
||||
if context['ignorecase']:
|
||||
complete_str = complete_str.lower()
|
||||
if not complete_str:
|
||||
return context['candidates'] # type: ignore
|
||||
|
||||
if context['is_sorted']:
|
||||
begin = binary_search_begin(
|
||||
context['candidates'], complete_str[0])
|
||||
end = binary_search_end(
|
||||
context['candidates'], complete_str[0])
|
||||
if begin < 0 or end < 0:
|
||||
return []
|
||||
candidates = context['candidates'][begin:end+1]
|
||||
else:
|
||||
candidates = context['candidates']
|
||||
|
||||
p = re.compile(fuzzy_escape(complete_str, context['camelcase']))
|
||||
if context['ignorecase']:
|
||||
return [x for x in candidates if p.match(x['word'].lower())]
|
||||
else:
|
||||
return [x for x in candidates if p.match(x['word'])]
|
@ -0,0 +1,44 @@
|
||||
# ============================================================================
|
||||
# FILE: matcher_head.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from deoplete.base.filter import Base
|
||||
from deoplete.util import binary_search_begin, binary_search_end
|
||||
from deoplete.util import Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'matcher_head'
|
||||
self.description = 'head matcher'
|
||||
|
||||
def filter(self, context: UserContext) -> Candidates:
|
||||
complete_str = context['complete_str']
|
||||
if context['ignorecase']:
|
||||
complete_str = complete_str.lower()
|
||||
|
||||
if context['is_sorted']:
|
||||
begin = binary_search_begin(
|
||||
context['candidates'], complete_str)
|
||||
end = binary_search_end(
|
||||
context['candidates'], complete_str)
|
||||
if begin < 0 or end < 0:
|
||||
return []
|
||||
candidates = context['candidates'][begin:end+1]
|
||||
|
||||
if context['ignorecase']:
|
||||
return candidates # type: ignore
|
||||
else:
|
||||
candidates = context['candidates']
|
||||
|
||||
if context['ignorecase']:
|
||||
return [x for x in context['candidates']
|
||||
if x['word'].lower().startswith(complete_str)]
|
||||
else:
|
||||
return [x for x in context['candidates']
|
||||
if x['word'].startswith(complete_str)]
|
@ -0,0 +1,22 @@
|
||||
# ============================================================================
|
||||
# FILE: matcher_length.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from deoplete.base.filter import Base
|
||||
from deoplete.util import Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'matcher_length'
|
||||
self.description = 'length matcher'
|
||||
|
||||
def filter(self, context: UserContext) -> Candidates:
|
||||
input_len = len(context['complete_str'])
|
||||
return [x for x in context['candidates']
|
||||
if len(x['word']) > input_len]
|
@ -0,0 +1,52 @@
|
||||
# ============================================================================
|
||||
# FILE: sorter_rank.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import re
|
||||
import typing
|
||||
|
||||
from deoplete.base.filter import Base
|
||||
from deoplete.util import getlines
|
||||
from deoplete.util import Nvim, UserContext, Candidates, Candidate
|
||||
|
||||
|
||||
LINES_MAX = 150
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'sorter_rank'
|
||||
self.description = 'rank sorter'
|
||||
self._cache: typing.Dict[str, typing.Set[int]] = {}
|
||||
|
||||
def on_event(self, context: UserContext) -> None:
|
||||
self._cache = {}
|
||||
start = max([1, context['position'][1] - LINES_MAX])
|
||||
linenr = start
|
||||
for line in getlines(self.vim, start, start + LINES_MAX):
|
||||
for m in re.finditer(context['keyword_pattern'], line):
|
||||
k = m.group(0)
|
||||
if k not in self._cache:
|
||||
self._cache[k] = set()
|
||||
self._cache[k].add(linenr)
|
||||
linenr += 1
|
||||
|
||||
def filter(self, context: UserContext) -> Candidates:
|
||||
complete_str = context['complete_str'].lower()
|
||||
linenr = context['position'][1]
|
||||
|
||||
def compare(x: Candidate) -> int:
|
||||
word = x['word']
|
||||
matched = int(complete_str in word.lower())
|
||||
score = -matched * 40
|
||||
if word in self._cache:
|
||||
mru = min([abs(x - linenr) for x in self._cache[word]])
|
||||
mru -= LINES_MAX
|
||||
score += mru * 10
|
||||
return score
|
||||
return sorted(context['candidates'], key=compare)
|
@ -0,0 +1,21 @@
|
||||
# ============================================================================
|
||||
# FILE: sorter_word.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from deoplete.base.filter import Base
|
||||
from deoplete.util import Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'sorter_word'
|
||||
self.description = 'word sorter'
|
||||
|
||||
def filter(self, context: UserContext) -> Candidates:
|
||||
return sorted(context['candidates'],
|
||||
key=lambda x: x['word'].swapcase())
|
166
bundle/deoplete.nvim/rplugin/python3/deoplete/logger.py
Normal file
166
bundle/deoplete.nvim/rplugin/python3/deoplete/logger.py
Normal file
@ -0,0 +1,166 @@
|
||||
# ============================================================================
|
||||
# FILE: logger.py
|
||||
# AUTHOR: Tommy Allen <tommy at esdf.io>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from deoplete.util import Nvim
|
||||
|
||||
from functools import wraps
|
||||
from collections import defaultdict
|
||||
|
||||
log_format = '%(asctime)s %(levelname)-8s [%(process)d] (%(name)s) %(message)s'
|
||||
log_message_cooldown = 0.5
|
||||
|
||||
root = logging.getLogger('deoplete')
|
||||
root.propagate = False
|
||||
init = False
|
||||
|
||||
FUNC = typing.Callable[..., typing.Any]
|
||||
|
||||
|
||||
def getLogger(name: str) -> logging.Logger:
|
||||
"""Get a logger that is a child of the 'root' logger.
|
||||
"""
|
||||
return root.getChild(name)
|
||||
|
||||
|
||||
def setup(vim: Nvim, level: str, output_file: str = '') -> None:
|
||||
"""Setup logging for Deoplete
|
||||
"""
|
||||
global init
|
||||
if init:
|
||||
return
|
||||
init = True
|
||||
|
||||
if output_file:
|
||||
formatter = logging.Formatter(log_format)
|
||||
handler = logging.FileHandler(filename=output_file)
|
||||
handler.setFormatter(formatter)
|
||||
handler.addFilter(DeopleteLogFilter(vim))
|
||||
root.addHandler(handler)
|
||||
|
||||
level = str(level).upper()
|
||||
if level not in ('DEBUG', 'INFO', 'WARN', 'WARNING', 'ERROR',
|
||||
'CRITICAL', 'FATAL'):
|
||||
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',
|
||||
vim.call('deoplete#util#neovim_version'),
|
||||
'.'.join(map(str, sys.version_info[:3])),
|
||||
pynvim_version)
|
||||
|
||||
if 'deoplete#_logging_notified' not in vim.vars:
|
||||
vim.vars['deoplete#_logging_notified'] = 1
|
||||
vim.call('deoplete#util#print_debug', 'Logging to %s' % (
|
||||
output_file))
|
||||
|
||||
|
||||
def logmethod(func: FUNC) -> typing.Callable[[FUNC], FUNC]:
|
||||
"""Decorator for setting up the logger in LoggingMixin subclasses.
|
||||
|
||||
This does not guarantee that log messages will be generated. If
|
||||
`LoggingMixin.is_debug_enabled` is True, it will be propagated up to the
|
||||
root 'deoplete' logger.
|
||||
"""
|
||||
@wraps(func)
|
||||
def wrapper(self, # type: ignore
|
||||
*args: typing.Any,
|
||||
**kwargs: typing.Any) -> typing.Any:
|
||||
if not init or not self.is_debug_enabled:
|
||||
return
|
||||
if self._logger is None:
|
||||
self._logger = getLogger(getattr(self, 'name', 'unknown'))
|
||||
return func(self, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
class LoggingMixin(object):
|
||||
"""Class that adds logging functions to a subclass.
|
||||
"""
|
||||
is_debug_enabled = False
|
||||
_logger = None # type: logging.Logger
|
||||
|
||||
@logmethod
|
||||
def debug(self, msg: str,
|
||||
*args: typing.Any, **kwargs: typing.Any) -> None:
|
||||
self._logger.debug(msg, *args, **kwargs)
|
||||
|
||||
@logmethod
|
||||
def info(self, msg: str,
|
||||
*args: typing.Any, **kwargs: typing.Any) -> None:
|
||||
self._logger.info(msg, *args, **kwargs)
|
||||
|
||||
@logmethod
|
||||
def warning(self, msg: str,
|
||||
*args: typing.Any, **kwargs: typing.Any) -> None:
|
||||
self._logger.warning(msg, *args, **kwargs)
|
||||
warn = warning
|
||||
|
||||
@logmethod
|
||||
def error(self, msg: str,
|
||||
*args: typing.Any, **kwargs: typing.Any) -> None:
|
||||
self._logger.error(msg, *args, **kwargs)
|
||||
|
||||
@logmethod
|
||||
def exception(self, msg: str,
|
||||
*args: typing.Any, **kwargs: typing.Any) -> None:
|
||||
# This will not produce a log message if there is no exception to log.
|
||||
self._logger.exception(msg, *args, **kwargs)
|
||||
|
||||
@logmethod
|
||||
def critical(self, msg: str,
|
||||
*args: typing.Any, **kwargs: typing.Any) -> None:
|
||||
self._logger.critical(msg, *args, **kwargs)
|
||||
fatal = critical
|
||||
|
||||
|
||||
class DeopleteLogFilter(logging.Filter):
|
||||
def __init__(self, vim: Nvim, name: str = ''):
|
||||
self.vim = vim
|
||||
self.counter: typing.Dict[str, int] = defaultdict(int)
|
||||
self.last_message_time: float = 0
|
||||
self.last_message: typing.Tuple[typing.Any, ...] = ()
|
||||
|
||||
def filter(self, record: logging.LogRecord) -> bool:
|
||||
t = time.time()
|
||||
elapsed = t - self.last_message_time
|
||||
self.last_message_time = t
|
||||
|
||||
message = (record.levelno, record.name, record.msg, record.args)
|
||||
if message == self.last_message and elapsed < log_message_cooldown:
|
||||
# Ignore if the same message comes in too fast.
|
||||
return False
|
||||
self.last_message = message
|
||||
|
||||
if record.levelno >= logging.ERROR:
|
||||
self.counter[record.name] += 1
|
||||
if self.counter[record.name] <= 2:
|
||||
# Only permit 2 errors in succession from a logging source to
|
||||
# display errors inside of Neovim. After this, it is no longer
|
||||
# permitted to emit any more errors and should be addressed.
|
||||
self.vim.call('deoplete#util#print_error', record.getMessage(),
|
||||
record.name)
|
||||
if record.exc_info and record.stack_info:
|
||||
# Add a penalty for messages that generate exceptions to avoid
|
||||
# making the log harder to read with doubled stack traces.
|
||||
self.counter[record.name] += 1
|
||||
elif self.counter[record.name] < 2:
|
||||
# If below the threshold for silencing a logging source, reset its
|
||||
# counter.
|
||||
self.counter[record.name] = 0
|
||||
return True
|
221
bundle/deoplete.nvim/rplugin/python3/deoplete/parent.py
Normal file
221
bundle/deoplete.nvim/rplugin/python3/deoplete/parent.py
Normal file
@ -0,0 +1,221 @@
|
||||
# ============================================================================
|
||||
# FILE: parent.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# 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 queue import Queue
|
||||
|
||||
from deoplete import logger
|
||||
from deoplete.process import Process
|
||||
from deoplete.util import error_tb, error, Nvim
|
||||
|
||||
UserContext = typing.Dict[str, typing.Any]
|
||||
|
||||
|
||||
class _Parent(logger.LoggingMixin):
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
self.name = 'parent'
|
||||
|
||||
self._vim = vim
|
||||
self._loaded_filters: typing.Set[str] = set()
|
||||
|
||||
self._start_process()
|
||||
|
||||
def enable_logging(self) -> None:
|
||||
self._put('enable_logging', [])
|
||||
self.is_debug_enabled = True
|
||||
|
||||
def add_source(self, path: str) -> None:
|
||||
self._put('add_source', [path])
|
||||
|
||||
def add_filter(self, path: str) -> None:
|
||||
if path in self._loaded_filters:
|
||||
return
|
||||
self._loaded_filters.add(path)
|
||||
|
||||
self._put('add_filter', [path])
|
||||
|
||||
def set_source_attributes(self, context: UserContext) -> None:
|
||||
self._put('set_source_attributes', [context])
|
||||
|
||||
def set_custom(self, custom: typing.Any) -> None:
|
||||
self._put('set_custom', [custom])
|
||||
|
||||
def on_event(self, context: UserContext) -> None:
|
||||
self._put('on_event', [context])
|
||||
|
||||
@abstractmethod
|
||||
def _start_process(self) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _put(self, name: str,
|
||||
args: typing.List[typing.Any]) -> typing.Optional[str]:
|
||||
pass
|
||||
|
||||
|
||||
class SyncParent(_Parent):
|
||||
def _start_process(self) -> None:
|
||||
from deoplete.child import Child
|
||||
self._child = Child(self._vim)
|
||||
|
||||
def merge_results(self,
|
||||
context: UserContext) -> typing.Tuple[typing.Any]:
|
||||
results = self._child._merge_results(context, queue_id=None)
|
||||
ret = (results['is_async'], results['is_async'],
|
||||
results['merged_results']) if results else (False, [])
|
||||
return ret # type: ignore
|
||||
|
||||
def _put(self, name: str,
|
||||
args: typing.List[typing.Any]) -> typing.Optional[str]:
|
||||
self._child.main(name, args, queue_id=None)
|
||||
return None
|
||||
|
||||
|
||||
class AsyncParent(_Parent):
|
||||
def _get_python_executable(self) -> str:
|
||||
"""Get Python executable.
|
||||
|
||||
This handles Python being embedded in Vim on Windows or OSX.
|
||||
|
||||
Taken from jedi.api.environment._try_get_same_env.
|
||||
"""
|
||||
exe = sys.executable
|
||||
if not os.path.basename(exe).lower().startswith('python'):
|
||||
checks: typing.Tuple[typing.Any, ...]
|
||||
if sys.platform == 'win32':
|
||||
checks = (r'Scripts\python.exe', 'python.exe')
|
||||
else:
|
||||
checks = (
|
||||
'bin/python%s.%s' % (
|
||||
sys.version_info[0], sys.version[1]),
|
||||
'bin/python%s' % (sys.version_info[0]),
|
||||
'bin/python',
|
||||
)
|
||||
for check in checks:
|
||||
guess = os.path.join(sys.exec_prefix, check)
|
||||
if os.path.isfile(str(guess)):
|
||||
return str(guess)
|
||||
if 'python3_host_prog' not in self._vim.vars:
|
||||
return 'python3'
|
||||
return str(self._vim.vars['python3_host_prog'])
|
||||
return exe
|
||||
|
||||
def _start_process(self) -> None:
|
||||
self._stdin: typing.Optional[typing.Any] = None
|
||||
self._queue_id = ''
|
||||
self._queue_in: 'Queue[bytes]' = Queue()
|
||||
self._queue_out: 'Queue[typing.Any]' = Queue()
|
||||
self._queue_err: 'Queue[typing.Any]' = Queue()
|
||||
if msgpack.version < (1, 0, 0):
|
||||
self._packer = msgpack.Packer(
|
||||
encoding='utf-8',
|
||||
unicode_errors='surrogateescape')
|
||||
self._unpacker = msgpack.Unpacker(
|
||||
encoding='utf-8',
|
||||
unicode_errors='surrogateescape')
|
||||
else:
|
||||
self._packer = msgpack.Packer(
|
||||
unicode_errors='surrogateescape')
|
||||
self._unpacker = msgpack.Unpacker(
|
||||
unicode_errors='surrogateescape')
|
||||
self._prev_pos: typing.List[typing.Any] = []
|
||||
|
||||
info = None
|
||||
if sys.platform == 'win32':
|
||||
info = subprocess.STARTUPINFO()
|
||||
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
|
||||
main = str(Path(__file__).parent.parent.parent.parent.joinpath(
|
||||
'autoload', 'deoplete', '_main.py'))
|
||||
|
||||
self._hnd = self._vim.loop.create_task(
|
||||
self._vim.loop.subprocess_exec(
|
||||
partial(Process, self),
|
||||
self._get_python_executable(),
|
||||
main,
|
||||
self._vim.vars['deoplete#_serveraddr'],
|
||||
startupinfo=info))
|
||||
|
||||
def _print_error(self, message: str) -> None:
|
||||
error(self._vim, message)
|
||||
|
||||
def _connect_stdin(self, stdin: int) -> msgpack.Unpacker:
|
||||
self._stdin = stdin
|
||||
return self._unpacker
|
||||
|
||||
def merge_results(self,
|
||||
context: UserContext) -> typing.Tuple[typing.Any, ...]:
|
||||
# Note: TextChangedP is triggered when Update
|
||||
event = context['event']
|
||||
if ((event == 'Update' or event == 'TextChangedP') and
|
||||
context['position'] == self._prev_pos and self._queue_id):
|
||||
# Use previous id
|
||||
queue_id = self._queue_id
|
||||
else:
|
||||
queue_id = self._put('merge_results', [context]) # type: ignore
|
||||
if not queue_id:
|
||||
return (False, False, [])
|
||||
|
||||
get = self._get(queue_id)
|
||||
if not get:
|
||||
# Skip the next merge_results
|
||||
self._queue_id = queue_id
|
||||
self._prev_pos = context['position']
|
||||
return (True, False, [])
|
||||
self._queue_id = ''
|
||||
results = get[0]
|
||||
return (results['is_async'], results['is_async'],
|
||||
results['merged_results']) if results else (False, [])
|
||||
|
||||
def _put(self, name: str,
|
||||
args: typing.List[typing.Any]) -> typing.Optional[str]:
|
||||
if not self._hnd:
|
||||
return None
|
||||
|
||||
queue_id = str(time.time())
|
||||
msg = self._packer.pack({
|
||||
'name': name, 'args': args, 'queue_id': queue_id
|
||||
})
|
||||
self._queue_in.put(msg)
|
||||
|
||||
if self._stdin:
|
||||
try:
|
||||
while not self._queue_in.empty():
|
||||
self._stdin.write(self._queue_in.get_nowait())
|
||||
except BrokenPipeError:
|
||||
error_tb(self._vim, 'Crash in child process')
|
||||
error(self._vim, 'stderr=' +
|
||||
str(self._proc.read_error())) # type: ignore
|
||||
self._hnd = None
|
||||
return queue_id
|
||||
|
||||
def _get(self, queue_id: str) -> typing.List[typing.Any]:
|
||||
if not self._hnd:
|
||||
return []
|
||||
|
||||
check_stderr = self._vim.call(
|
||||
'deoplete#custom#_get_option', 'check_stderr')
|
||||
while check_stderr and not self._queue_err.empty():
|
||||
self._print_error(self._queue_err.get_nowait())
|
||||
|
||||
outs = []
|
||||
while not self._queue_out.empty():
|
||||
outs.append(self._queue_out.get_nowait())
|
||||
try:
|
||||
return [x for x in outs if x['queue_id'] == queue_id]
|
||||
except TypeError:
|
||||
error_tb(self._vim,
|
||||
'"stdout" seems contaminated by sources. '
|
||||
'"stdout" is used for RPC; Please pipe or discard')
|
||||
return []
|
33
bundle/deoplete.nvim/rplugin/python3/deoplete/process.py
Normal file
33
bundle/deoplete.nvim/rplugin/python3/deoplete/process.py
Normal file
@ -0,0 +1,33 @@
|
||||
# ============================================================================
|
||||
# FILE: process.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import asyncio
|
||||
import typing
|
||||
|
||||
|
||||
class Process(asyncio.SubprocessProtocol):
|
||||
|
||||
def __init__(self, plugin: typing.Any) -> None:
|
||||
self._plugin = plugin
|
||||
self._vim = plugin._vim
|
||||
|
||||
def connection_made(self, transport: typing.Any) -> None:
|
||||
self._unpacker = self._plugin._connect_stdin(
|
||||
transport.get_pipe_transport(0))
|
||||
|
||||
def pipe_data_received(self, fd: int, data: typing.Any) -> None:
|
||||
if fd == 2:
|
||||
# stderr
|
||||
self._plugin._queue_err.put(f'stderr from child process:{data}')
|
||||
return
|
||||
|
||||
unpacker = self._unpacker
|
||||
unpacker.feed(data)
|
||||
for child_out in unpacker:
|
||||
self._plugin._queue_out.put(child_out)
|
||||
|
||||
def process_exited(self) -> None:
|
||||
self._plugin._queue_err.put('The child process is exited!')
|
@ -0,0 +1,74 @@
|
||||
# ============================================================================
|
||||
# FILE: around.py
|
||||
# AUTHOR: Khalidov Oleg <brooth at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import re
|
||||
|
||||
from deoplete.base.source import Base
|
||||
from deoplete.util import parse_buffer_pattern, getlines
|
||||
from deoplete.util import Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Source(Base):
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
self.name = 'around'
|
||||
self.rank = 300
|
||||
self.vars = {
|
||||
'mark_above': '[A]',
|
||||
'mark_below': '[A]',
|
||||
'mark_changes': '[A]',
|
||||
'range_above': 20,
|
||||
'range_below': 20,
|
||||
}
|
||||
custom_vars = self.vim.call(
|
||||
'deoplete#custom#_get_source_vars', self.name
|
||||
)
|
||||
if custom_vars:
|
||||
self.vars.update(custom_vars)
|
||||
|
||||
def gather_candidates(self, context: UserContext) -> Candidates:
|
||||
line = context['position'][1]
|
||||
candidates: Candidates = []
|
||||
|
||||
# lines above
|
||||
words = parse_buffer_pattern(
|
||||
reversed(
|
||||
getlines(
|
||||
self.vim, max([1, line - self.vars['range_above']]), line
|
||||
)
|
||||
),
|
||||
context['keyword_pattern'],
|
||||
)
|
||||
candidates += [
|
||||
{'word': x, 'menu': self.vars['mark_above']} for x in words
|
||||
]
|
||||
|
||||
# grab ':changes' command output
|
||||
p = re.compile(r'[\s\d]+')
|
||||
lines = set()
|
||||
for change_line in [
|
||||
x[p.search(x).span()[1]:] # type: ignore
|
||||
for x in self.vim.call('execute', 'changes').split('\n')[2:]
|
||||
if p.search(x)
|
||||
]:
|
||||
if change_line and change_line != '-invalid-':
|
||||
lines.add(change_line)
|
||||
|
||||
words = parse_buffer_pattern(lines, context['keyword_pattern'])
|
||||
candidates += [
|
||||
{'word': x, 'menu': self.vars['mark_changes']} for x in words
|
||||
]
|
||||
|
||||
# lines below
|
||||
words = parse_buffer_pattern(
|
||||
getlines(self.vim, line, line + self.vars['range_below']),
|
||||
context['keyword_pattern'],
|
||||
)
|
||||
candidates += [
|
||||
{'word': x, 'menu': self.vars['mark_below']} for x in words
|
||||
]
|
||||
|
||||
return candidates
|
14
bundle/deoplete.nvim/rplugin/python3/deoplete/source/base.py
Normal file
14
bundle/deoplete.nvim/rplugin/python3/deoplete/source/base.py
Normal file
@ -0,0 +1,14 @@
|
||||
# ============================================================================
|
||||
# FILE: base.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
# For backward compatibility
|
||||
from deoplete.base.source import Base as _Base
|
||||
from deoplete.util import Nvim
|
||||
|
||||
|
||||
class Base(_Base):
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
@ -0,0 +1,70 @@
|
||||
# ============================================================================
|
||||
# FILE: buffer.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import typing
|
||||
|
||||
from deoplete.base.source import Base
|
||||
from deoplete.util import parse_buffer_pattern, getlines
|
||||
from deoplete.util import Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Source(Base):
|
||||
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'buffer'
|
||||
self.mark = '[B]'
|
||||
self.events = ['Init', 'BufReadPost', 'BufWritePost', 'InsertLeave']
|
||||
self.vars = {
|
||||
'require_same_filetype': True,
|
||||
}
|
||||
|
||||
self._limit = 1000000
|
||||
self._buffers: typing.Dict[int, typing.Any] = {}
|
||||
self._max_lines = 5000
|
||||
|
||||
def on_event(self, context: UserContext) -> None:
|
||||
self._make_cache(context)
|
||||
|
||||
tab_bufnrs = self.vim.call('tabpagebuflist')
|
||||
self._buffers = {
|
||||
x['bufnr']: x for x in self._buffers.values()
|
||||
if x['bufnr'] in tab_bufnrs or
|
||||
self.vim.call('buflisted', x['bufnr'])
|
||||
}
|
||||
|
||||
def gather_candidates(self, context: UserContext) -> Candidates:
|
||||
tab_bufnrs = self.vim.call('tabpagebuflist')
|
||||
same_filetype = self.get_var('require_same_filetype')
|
||||
return {'sorted_candidates': [ # type: ignore
|
||||
x['candidates'] for x in self._buffers.values()
|
||||
if not same_filetype or
|
||||
x['filetype'] in context['filetypes'] or
|
||||
x['filetype'] in context['same_filetypes'] or
|
||||
x['bufnr'] in tab_bufnrs
|
||||
]}
|
||||
|
||||
def _make_cache(self, context: UserContext) -> None:
|
||||
# Bufsize check
|
||||
size = self.vim.call('line2byte',
|
||||
self.vim.call('line', '$') + 1) - 1
|
||||
if size > self._limit:
|
||||
return
|
||||
|
||||
try:
|
||||
self._buffers[context['bufnr']] = {
|
||||
'bufnr': context['bufnr'],
|
||||
'filetype': self.get_buf_option('filetype'),
|
||||
'candidates': [
|
||||
{'word': x} for x in
|
||||
sorted(parse_buffer_pattern(getlines(self.vim),
|
||||
context['keyword_pattern']),
|
||||
key=str.lower)
|
||||
]
|
||||
}
|
||||
except UnicodeDecodeError:
|
||||
return
|
100
bundle/deoplete.nvim/rplugin/python3/deoplete/source/file.py
Normal file
100
bundle/deoplete.nvim/rplugin/python3/deoplete/source/file.py
Normal file
@ -0,0 +1,100 @@
|
||||
# ============================================================================
|
||||
# FILE: file.py
|
||||
# AUTHOR: Felipe Morales <hel.sheep at gmail.com>
|
||||
# Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import os
|
||||
import re
|
||||
import typing
|
||||
from os.path import exists, dirname
|
||||
|
||||
from deoplete.base.source import Base
|
||||
from deoplete.util import expand, Nvim, UserContext, Candidates
|
||||
|
||||
|
||||
class Source(Base):
|
||||
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'file'
|
||||
self.mark = '[F]'
|
||||
self.min_pattern_length = 0
|
||||
self.rank = 150
|
||||
self.events: typing.List[str] = ['InsertEnter']
|
||||
self.vars = {
|
||||
'enable_buffer_path': True,
|
||||
'force_completion_length': -1,
|
||||
}
|
||||
|
||||
self._isfname = ''
|
||||
|
||||
def on_event(self, context: UserContext) -> None:
|
||||
self._isfname = self.vim.call(
|
||||
'deoplete#util#vimoption2python_not',
|
||||
self.vim.options['isfname'])
|
||||
|
||||
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
|
||||
if pos < 0 and force_completion_length >= 0:
|
||||
fmt = '[a-zA-Z0-9.-]{{{}}}$'.format(force_completion_length)
|
||||
m = re.search(fmt, context['input'])
|
||||
if m:
|
||||
return m.start()
|
||||
return pos if pos < 0 else pos + 1
|
||||
|
||||
def gather_candidates(self, context: UserContext) -> Candidates:
|
||||
if not self._isfname:
|
||||
self.on_event(context)
|
||||
|
||||
input_str = (context['input']
|
||||
if context['input'].rfind('/') >= 0
|
||||
else './')
|
||||
|
||||
p = self._longest_path_that_exists(context, input_str)
|
||||
if not p or p == '/' or re.search('//+$', p):
|
||||
return []
|
||||
complete_str = self._substitute_path(context, dirname(p) + '/')
|
||||
if not os.path.isdir(complete_str):
|
||||
return []
|
||||
hidden = context['complete_str'].find('.') == 0
|
||||
contents: typing.List[typing.Any] = [[], []]
|
||||
try:
|
||||
for item in sorted(os.listdir(complete_str), key=str.lower):
|
||||
if not hidden and item[0] == '.':
|
||||
continue
|
||||
contents[not os.path.isdir(complete_str + item)].append(item)
|
||||
except PermissionError:
|
||||
pass
|
||||
|
||||
dirs, files = contents
|
||||
return [{'word': x, 'abbr': x + '/'} for x in dirs
|
||||
] + [{'word': x} for x in files]
|
||||
|
||||
def _longest_path_that_exists(self, context: UserContext,
|
||||
input_str: str) -> str:
|
||||
input_str = re.sub(r'[^/]*$', '', input_str)
|
||||
data = re.split(r'((?:%s+|(?:(?<![\w\s/\.])(?:~|\.{1,2})?/)+))' %
|
||||
self._isfname, input_str)
|
||||
data = [''.join(data[i:]) for i in range(len(data))]
|
||||
existing_paths = sorted(filter(lambda x: exists(
|
||||
dirname(self._substitute_path(context, x))), data))
|
||||
return existing_paths[-1] if existing_paths else ''
|
||||
|
||||
def _substitute_path(self, context: UserContext, path: 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')
|
||||
|
||||
for _ in m.group(1):
|
||||
base = dirname(base)
|
||||
return os.path.abspath(os.path.join(
|
||||
base, path[len(m.group(0)):])) + '/'
|
||||
return expand(path)
|
@ -0,0 +1,66 @@
|
||||
# ============================================================================
|
||||
# FILE: member.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
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
|
||||
|
||||
|
||||
class Source(Base):
|
||||
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'member'
|
||||
self.mark = '[M]'
|
||||
self.min_pattern_length = 0
|
||||
|
||||
self._object_pattern = r'[a-zA-Z_]\w*(?:\(\)?)?'
|
||||
self._prefix = ''
|
||||
|
||||
prefix_patterns: typing.Dict[str, str] = {}
|
||||
set_pattern(prefix_patterns,
|
||||
'_', r'\.')
|
||||
set_pattern(prefix_patterns,
|
||||
'c,objc', [r'\.', '->'])
|
||||
set_pattern(prefix_patterns,
|
||||
'cpp,objcpp', [r'\.', '->', '::'])
|
||||
set_pattern(prefix_patterns,
|
||||
'perl,php', ['->'])
|
||||
set_pattern(prefix_patterns,
|
||||
'ruby', [r'\.', '::'])
|
||||
set_pattern(prefix_patterns,
|
||||
'lua', [r'\.', ':'])
|
||||
self.vars = {
|
||||
'prefix_patterns': prefix_patterns,
|
||||
}
|
||||
|
||||
def get_complete_position(self, context: UserContext) -> int:
|
||||
# Check member prefix pattern.
|
||||
for prefix_pattern in convert2list(
|
||||
self.get_filetype_var(
|
||||
context['filetype'], 'prefix_patterns')):
|
||||
m = re.search(self._object_pattern + prefix_pattern + r'\w*$',
|
||||
context['input'])
|
||||
if m is None or prefix_pattern == '':
|
||||
continue
|
||||
self._prefix = re.sub(r'\w*$', '', m.group(0))
|
||||
m = re.search(r'\w*$', context['input'])
|
||||
if m:
|
||||
return m.start()
|
||||
return -1
|
||||
|
||||
def gather_candidates(self, context: UserContext) -> Candidates:
|
||||
return [{'word': x} for x in
|
||||
parse_buffer_pattern(
|
||||
getlines(self.vim),
|
||||
r'(?<=' + re.escape(self._prefix) + r')\w+'
|
||||
)
|
||||
if x != context['complete_str']]
|
98
bundle/deoplete.nvim/rplugin/python3/deoplete/source/omni.py
Normal file
98
bundle/deoplete.nvim/rplugin/python3/deoplete/source/omni.py
Normal file
@ -0,0 +1,98 @@
|
||||
# ============================================================================
|
||||
# FILE: omni.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
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
|
||||
|
||||
|
||||
class Source(Base):
|
||||
|
||||
def __init__(self, vim: Nvim) -> None:
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'omni'
|
||||
self.mark = '[O]'
|
||||
self.rank = 500
|
||||
self.is_bytepos = True
|
||||
self.min_pattern_length = 0
|
||||
|
||||
input_patterns: typing.Dict[str, str] = {}
|
||||
set_pattern(input_patterns, 'css,less,scss,sass',
|
||||
[r'\w{2}', r'\w+:?\s*\w*', r'[@!]'])
|
||||
self.vars = {
|
||||
'input_patterns': input_patterns,
|
||||
'functions': {},
|
||||
}
|
||||
|
||||
def get_complete_position(self, context: UserContext) -> int:
|
||||
current_ft = self.get_buf_option('filetype')
|
||||
|
||||
for filetype in list(set([context['filetype']] +
|
||||
context['filetype'].split('.'))):
|
||||
pos = self._get_complete_position(context, current_ft, filetype)
|
||||
if pos >= 0:
|
||||
return pos
|
||||
return -1
|
||||
|
||||
def _get_complete_position(self, context: UserContext,
|
||||
current_ft: str, filetype: str) -> int:
|
||||
for omnifunc in convert2list(
|
||||
self.get_filetype_var(filetype, 'functions')):
|
||||
if omnifunc == '' and (filetype == current_ft or
|
||||
filetype in ['css', 'javascript']):
|
||||
omnifunc = self.get_buf_option('omnifunc')
|
||||
if omnifunc == '':
|
||||
continue
|
||||
self._omnifunc = omnifunc
|
||||
for input_pattern in convert2list(
|
||||
self.get_filetype_var(filetype, 'input_patterns')):
|
||||
|
||||
m = re.search('(' + input_pattern + ')$', context['input'])
|
||||
# self.debug(filetype)
|
||||
# self.debug(input_pattern)
|
||||
if input_pattern == '' or (context['event'] !=
|
||||
'Manual' and m is None):
|
||||
continue
|
||||
|
||||
if self._omnifunc in [
|
||||
'ccomplete#Complete',
|
||||
'htmlcomplete#CompleteTags',
|
||||
'LanguageClient#complete',
|
||||
'rubycomplete#Complete',
|
||||
'phpcomplete#CompletePHP']:
|
||||
# In the blacklist
|
||||
return -1
|
||||
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
|
||||
|
||||
def gather_candidates(self, context: UserContext) -> Candidates:
|
||||
try:
|
||||
candidates = self.vim.call(self._omnifunc, 0, '')
|
||||
if isinstance(candidates, dict):
|
||||
candidates = candidates['words']
|
||||
elif not isinstance(candidates, list):
|
||||
candidates = []
|
||||
except Exception:
|
||||
candidates = []
|
||||
|
||||
candidates = convert2candidates(candidates)
|
||||
|
||||
for candidate in candidates:
|
||||
candidate['dup'] = 1
|
||||
candidate['equal'] = 1
|
||||
|
||||
return candidates # type: ignore
|
298
bundle/deoplete.nvim/rplugin/python3/deoplete/util.py
Normal file
298
bundle/deoplete.nvim/rplugin/python3/deoplete/util.py
Normal file
@ -0,0 +1,298 @@
|
||||
# ============================================================================
|
||||
# FILE: util.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import glob
|
||||
import importlib.util
|
||||
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]
|
||||
|
||||
|
||||
def set_pattern(variable: typing.Dict[str, str],
|
||||
keys: str, pattern: typing.Any) -> None:
|
||||
for key in keys.split(','):
|
||||
variable[key] = pattern
|
||||
|
||||
|
||||
def convert2list(expr: typing.Any) -> typing.List[typing.Any]:
|
||||
return (expr if isinstance(expr, list) else [expr])
|
||||
|
||||
|
||||
def convert2candidates(li: typing.Any) -> Candidates:
|
||||
ret = []
|
||||
if li and isinstance(li, list):
|
||||
for x in li:
|
||||
if isinstance(x, str):
|
||||
ret.append({'word': x})
|
||||
else:
|
||||
ret.append(x)
|
||||
else:
|
||||
ret = li
|
||||
return ret
|
||||
|
||||
|
||||
def globruntime(runtimepath: str, path: str) -> typing.List[str]:
|
||||
ret: typing.List[str] = []
|
||||
for rtp in runtimepath.split(','):
|
||||
ret += glob.glob(rtp + '/' + path)
|
||||
return ret
|
||||
|
||||
|
||||
def import_plugin(path: str, source: str,
|
||||
classname: str) -> typing.Optional[typing.Any]:
|
||||
"""Import Deoplete plugin source class.
|
||||
|
||||
If the class exists, add its directory to sys.path.
|
||||
"""
|
||||
name = os.path.splitext(os.path.basename(path))[0]
|
||||
module_name = 'deoplete.%s.%s' % (source, name)
|
||||
|
||||
spec = importlib.util.spec_from_file_location(module_name, path)
|
||||
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)
|
||||
if dirname not in sys.path:
|
||||
sys.path.insert(0, dirname)
|
||||
return cls
|
||||
|
||||
|
||||
def debug(vim: Nvim, expr: typing.Any) -> None:
|
||||
if hasattr(vim, 'out_write'):
|
||||
vim.out_write(f'[deoplete] {expr}\n')
|
||||
else:
|
||||
vim.call('deoplete#util#print_debug', expr)
|
||||
|
||||
|
||||
def error(vim: Nvim, expr: typing.Any) -> None:
|
||||
if hasattr(vim, 'err_write'):
|
||||
vim.err_write(f'[deoplete] {expr}\n')
|
||||
else:
|
||||
vim.call('deoplete#util#print_error', expr)
|
||||
|
||||
|
||||
def error_tb(vim: Nvim, msg: str) -> None:
|
||||
lines: typing.List[str] = []
|
||||
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]
|
||||
if hasattr(vim, 'err_write'):
|
||||
vim.err_write('[deoplete] %s\n' % '\n'.join(lines))
|
||||
else:
|
||||
for line in lines:
|
||||
vim.call('deoplete#util#print_error', line)
|
||||
|
||||
|
||||
def error_vim(vim: Nvim, msg: str) -> None:
|
||||
throwpoint = vim.eval('v:throwpoint')
|
||||
if throwpoint != '':
|
||||
error(vim, 'v:throwpoint = ' + throwpoint)
|
||||
exception = vim.eval('v:exception')
|
||||
if exception != '':
|
||||
error(vim, 'v:exception = ' + exception)
|
||||
error_tb(vim, msg)
|
||||
|
||||
|
||||
def escape(expr: str) -> str:
|
||||
return expr.replace("'", "''")
|
||||
|
||||
|
||||
def charpos2bytepos(encoding: str, text: str, pos: int) -> int:
|
||||
return len(bytes(text[: pos], encoding, errors='replace'))
|
||||
|
||||
|
||||
def bytepos2charpos(encoding: str, text: str, pos: int) -> int:
|
||||
return len(bytes(text, encoding, errors='replace')[: pos].decode(
|
||||
encoding, errors='replace'))
|
||||
|
||||
|
||||
def get_custom(custom: typing.Dict[str, typing.Any],
|
||||
source_name: str, key: str,
|
||||
default: typing.Any) -> typing.Any:
|
||||
custom_source = custom['source']
|
||||
if source_name not in custom_source:
|
||||
return get_custom(custom, '_', key, default)
|
||||
elif key in custom_source[source_name]:
|
||||
return custom_source[source_name][key]
|
||||
elif key in custom_source['_']:
|
||||
return custom_source['_'][key]
|
||||
else:
|
||||
return default
|
||||
|
||||
|
||||
def get_syn_names(vim: Nvim) -> str:
|
||||
return str(vim.call('deoplete#util#get_syn_names'))
|
||||
|
||||
|
||||
def parse_file_pattern(f: typing.Iterable[str],
|
||||
pattern: str) -> typing.List[str]:
|
||||
p = re.compile(pattern)
|
||||
ret: typing.List[str] = []
|
||||
for li in f:
|
||||
ret += p.findall(li)
|
||||
return list(set(ret))
|
||||
|
||||
|
||||
def parse_buffer_pattern(b: Buffer, pattern: str) -> typing.List[str]:
|
||||
return list(set(re.compile(pattern).findall('\n'.join(b))))
|
||||
|
||||
|
||||
def fuzzy_escape(string: str, camelcase: bool) -> str:
|
||||
# Escape string for python regexp.
|
||||
p = re.sub(r'([a-zA-Z0-9_])', r'\1.*', re.escape(string))
|
||||
if camelcase and re.search(r'[A-Z]', string):
|
||||
p = re.sub(r'([a-z])', (lambda pat:
|
||||
f'[{pat.group(1)}{pat.group(1).upper()}]'), p)
|
||||
p = re.sub(r'([a-zA-Z0-9_])\.\*', r'\1[^\1]*', p)
|
||||
return p
|
||||
|
||||
|
||||
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)
|
||||
if module_dir not in sys.path:
|
||||
sys.path.insert(0, module_dir)
|
||||
|
||||
|
||||
def truncate_skipping(string: str, max_width: int,
|
||||
footer: str, footer_len: int) -> str:
|
||||
if not string:
|
||||
return ''
|
||||
if len(string) <= max_width / 2:
|
||||
return string
|
||||
if strwidth(string) <= max_width:
|
||||
return string
|
||||
|
||||
footer += string[
|
||||
-len(truncate(string[::-1], footer_len)):]
|
||||
return truncate(string, max_width - strwidth(footer)) + footer
|
||||
|
||||
|
||||
def truncate(string: str, max_width: int) -> str:
|
||||
if len(string) <= max_width / 2:
|
||||
return string
|
||||
if strwidth(string) <= max_width:
|
||||
return string
|
||||
|
||||
width = 0
|
||||
ret = ''
|
||||
for c in string:
|
||||
wc = charwidth(c)
|
||||
if width + wc > max_width:
|
||||
break
|
||||
ret += c
|
||||
width += wc
|
||||
return ret
|
||||
|
||||
|
||||
def strwidth(string: str) -> int:
|
||||
width = 0
|
||||
for c in string:
|
||||
width += charwidth(c)
|
||||
return width
|
||||
|
||||
|
||||
def charwidth(c: str) -> int:
|
||||
wc = unicodedata.east_asian_width(c)
|
||||
return 2 if wc == 'F' or wc == 'W' else 1
|
||||
|
||||
|
||||
def expand(path: str) -> str:
|
||||
return os.path.expanduser(os.path.expandvars(path))
|
||||
|
||||
|
||||
def getlines(vim: Nvim, start: int = 1,
|
||||
end: typing.Union[int, str] = '$') -> typing.List[str]:
|
||||
if end == '$':
|
||||
end = len(vim.current.buffer)
|
||||
max_len = min([int(end) - start, 5000])
|
||||
lines: typing.List[str] = []
|
||||
current = start
|
||||
while current <= int(end):
|
||||
lines += vim.call('getline', current, current + max_len)
|
||||
current += max_len + 1
|
||||
return lines
|
||||
|
||||
|
||||
def binary_search_begin(li: typing.List[Candidates], prefix: str) -> int:
|
||||
if not li:
|
||||
return -1
|
||||
if len(li) == 1:
|
||||
word = li[0]['word'] # type: ignore
|
||||
return 0 if word.lower().startswith(prefix) else -1
|
||||
|
||||
s = 0
|
||||
e = len(li)
|
||||
prefix = prefix.lower()
|
||||
while s < e:
|
||||
index = int((s + e) / 2)
|
||||
word = li[index]['word'].lower() # type: ignore
|
||||
if word.startswith(prefix):
|
||||
if (index - 1) < 0:
|
||||
return index
|
||||
prev_word = li[index-1]['word'] # type: ignore
|
||||
if not prev_word.lower().startswith(prefix):
|
||||
return index
|
||||
e = index
|
||||
elif prefix < word:
|
||||
e = index
|
||||
else:
|
||||
s = index + 1
|
||||
return -1
|
||||
|
||||
|
||||
def binary_search_end(li: typing.List[Candidates], prefix: str) -> int:
|
||||
if not li:
|
||||
return -1
|
||||
if len(li) == 1:
|
||||
word = li[0]['word'] # type: ignore
|
||||
return 0 if word.lower().startswith(prefix) else -1
|
||||
|
||||
s = 0
|
||||
e = len(li)
|
||||
prefix = prefix.lower()
|
||||
while s < e:
|
||||
index = int((s + e) / 2)
|
||||
word = li[index]['word'].lower() # type: ignore
|
||||
if word.startswith(prefix):
|
||||
if (index + 1) >= len(li):
|
||||
return index
|
||||
next_word = li[index+1]['word'] # type: ignore
|
||||
if not next_word.lower().startswith(prefix):
|
||||
return index
|
||||
s = index + 1
|
||||
elif prefix < word:
|
||||
e = index
|
||||
else:
|
||||
s = index + 1
|
||||
return -1
|
||||
|
||||
|
||||
def uniq_list_dict(li: typing.List[typing.Any]) -> typing.List[typing.Any]:
|
||||
# Uniq list of dictionaries
|
||||
ret: typing.List[typing.Any] = []
|
||||
for d in li:
|
||||
if d not in ret:
|
||||
ret.append(d)
|
||||
return ret
|
123
bundle/deoplete.nvim/test/autoload/deoplete/custom.vim
Normal file
123
bundle/deoplete.nvim/test/autoload/deoplete/custom.vim
Normal file
@ -0,0 +1,123 @@
|
||||
let s:suite = themis#suite('custom')
|
||||
let s:assert = themis#helper('assert')
|
||||
|
||||
function! s:suite.custom_source() abort
|
||||
call deoplete#custom#_init()
|
||||
|
||||
call deoplete#custom#source('_',
|
||||
\ 'matchers', ['matcher_head'])
|
||||
|
||||
call deoplete#custom#source('_', 'converters',
|
||||
\ ['converter_auto_delimiter', 'remove_overlap'])
|
||||
|
||||
call s:assert.equals(
|
||||
\ deoplete#custom#_get().source,
|
||||
\ {'_' : {
|
||||
\ 'matchers': ['matcher_head'],
|
||||
\ 'converters': ['converter_auto_delimiter', 'remove_overlap']}})
|
||||
|
||||
call deoplete#custom#_init()
|
||||
|
||||
call deoplete#custom#source('buffer',
|
||||
\ 'min_pattern_length', 9999)
|
||||
call deoplete#custom#source('buffer', 'rank', 9999)
|
||||
call deoplete#custom#source('buffer', {'filetypes': []})
|
||||
call s:assert.equals(
|
||||
\ deoplete#custom#_get_source('buffer').filetypes, [])
|
||||
call s:assert.equals(
|
||||
\ deoplete#custom#_get_source('buffer').rank, 9999)
|
||||
|
||||
call deoplete#custom#var('file', 'force_completion_length', 2)
|
||||
call deoplete#custom#var('file', {'foo': -1, 'bar': 1})
|
||||
call deoplete#custom#_update_cache()
|
||||
call s:assert.equals(
|
||||
\ deoplete#custom#_get_source_vars('file'),
|
||||
\ {'force_completion_length' : 2, 'foo': -1, 'bar': 1})
|
||||
call deoplete#custom#buffer_var('file', 'force_completion_length', 0)
|
||||
call deoplete#custom#_update_cache()
|
||||
call s:assert.equals(
|
||||
\ deoplete#custom#_get_source_vars('file'),
|
||||
\ {'force_completion_length' : 0, 'foo': -1, 'bar': 1})
|
||||
endfunction
|
||||
|
||||
function! s:suite.custom_option() abort
|
||||
" Simple option test
|
||||
call deoplete#custom#_init()
|
||||
call deoplete#custom#_init_buffer()
|
||||
call deoplete#custom#option('auto_complete', v:true)
|
||||
call deoplete#custom#_update_cache()
|
||||
call s:assert.equals(
|
||||
\ deoplete#custom#_get_option('auto_complete'), v:true)
|
||||
|
||||
" Buffer option test
|
||||
call deoplete#custom#buffer_option('auto_complete', v:false)
|
||||
call deoplete#custom#_update_cache()
|
||||
call s:assert.equals(
|
||||
\ deoplete#custom#_get_option('auto_complete'), v:false)
|
||||
|
||||
" Compatibility test
|
||||
call deoplete#custom#_init()
|
||||
call deoplete#custom#_init_buffer()
|
||||
let g:deoplete#disable_auto_complete = 1
|
||||
call deoplete#init#_custom_variables()
|
||||
call deoplete#custom#_update_cache()
|
||||
call s:assert.equals(
|
||||
\ deoplete#custom#_get_option('auto_complete'), v:false)
|
||||
|
||||
" Filetype option test
|
||||
call deoplete#custom#_init()
|
||||
call deoplete#custom#_init_buffer()
|
||||
let s:java_pattern = '[^. *\t]\.\w*'
|
||||
call deoplete#custom#option('omni_patterns', {
|
||||
\ 'java': s:java_pattern,
|
||||
\})
|
||||
call deoplete#custom#_update_cache()
|
||||
call s:assert.equals(
|
||||
\ deoplete#custom#_get_filetype_option(
|
||||
\ 'omni_patterns', 'java', ''), s:java_pattern)
|
||||
call s:assert.equals(
|
||||
\ deoplete#custom#_get_filetype_option(
|
||||
\ 'omni_patterns', 'foobar', ''), '')
|
||||
|
||||
" Compatibility test
|
||||
call deoplete#custom#_init()
|
||||
call deoplete#custom#_init_buffer()
|
||||
let s:tex_pattern = '[^\w|\s][a-zA-Z_]\w*'
|
||||
let g:deoplete#keyword_patterns = {}
|
||||
let g:deoplete#keyword_patterns.tex = '[^\w|\s][a-zA-Z_]\w*'
|
||||
call deoplete#init#_custom_variables()
|
||||
call deoplete#custom#_update_cache()
|
||||
call s:assert.equals(
|
||||
\ deoplete#custom#_get_filetype_option(
|
||||
\ 'keyword_patterns', 'tex', ''), s:tex_pattern)
|
||||
|
||||
" Dict type format
|
||||
call deoplete#custom#_init()
|
||||
call deoplete#custom#_init_buffer()
|
||||
call deoplete#custom#option({
|
||||
\ 'auto_complete': v:true, 'camel_case': v:true
|
||||
\ })
|
||||
call deoplete#custom#_update_cache()
|
||||
call s:assert.equals(
|
||||
\ deoplete#custom#_get_option('auto_complete'), v:true)
|
||||
call s:assert.equals(
|
||||
\ deoplete#custom#_get_option('camel_case'), v:true)
|
||||
endfunction
|
||||
|
||||
function! s:suite.custom_filter() abort
|
||||
call deoplete#custom#_init()
|
||||
call deoplete#custom#filter('converter_auto_delimiter', {
|
||||
\ 'delimiters': ['foo', 'bar'],
|
||||
\ })
|
||||
call deoplete#custom#_update_cache()
|
||||
call s:assert.equals(
|
||||
\ deoplete#custom#_get_filter('converter_auto_delimiter'),
|
||||
\ {'delimiters': ['foo', 'bar']})
|
||||
call deoplete#custom#buffer_filter('converter_auto_delimiter', {
|
||||
\ 'delimiters': ['foo'],
|
||||
\ })
|
||||
call deoplete#custom#_update_cache()
|
||||
call s:assert.equals(
|
||||
\ deoplete#custom#_get_filter('converter_auto_delimiter'),
|
||||
\ {'delimiters': ['foo']})
|
||||
endfunction
|
35
bundle/deoplete.nvim/test/autoload/deoplete/util.vim
Normal file
35
bundle/deoplete.nvim/test/autoload/deoplete/util.vim
Normal file
@ -0,0 +1,35 @@
|
||||
let s:suite = themis#suite('parser')
|
||||
let s:assert = themis#helper('assert')
|
||||
|
||||
function! s:suite.vimoption2python() abort
|
||||
call s:assert.equals(
|
||||
\ deoplete#util#vimoption2python('@,48-57,_,\'), '[\w@0-9_\\]')
|
||||
call s:assert.equals(
|
||||
\ deoplete#util#vimoption2python('@,-,48-57,_'), '[\w@0-9_-]')
|
||||
call s:assert.equals(
|
||||
\ deoplete#util#vimoption2python('@,,,48-57,_'), '[\w@,0-9_]')
|
||||
call s:assert.equals(
|
||||
\ deoplete#util#versioncmp('0.1.10', '0.1.8'), 2)
|
||||
call s:assert.equals(
|
||||
\ deoplete#util#versioncmp('0.1.10', '0.1.10'), 0)
|
||||
call s:assert.equals(
|
||||
\ deoplete#util#versioncmp('0.1.10', '0.1.0010'), 0)
|
||||
call s:assert.equals(
|
||||
\ deoplete#util#versioncmp('0.1.1', '0.1.8'), -7)
|
||||
call s:assert.equals(
|
||||
\ deoplete#util#versioncmp('0.1.1000', '0.1.10'), 990)
|
||||
call s:assert.equals(
|
||||
\ deoplete#util#versioncmp('0.1.0001', '0.1.10'), -9)
|
||||
call s:assert.equals(
|
||||
\ deoplete#util#versioncmp('2.0.1', '1.3.5'), 9696)
|
||||
call s:assert.equals(
|
||||
\ deoplete#util#versioncmp('3.2.1', '0.0.0'), 30201)
|
||||
call s:assert.equals(
|
||||
\ deoplete#util#vimoption2python('45,48-57,65-90,95,97-122'),
|
||||
\ '[\w0-9A-Z_a-z-]')
|
||||
call s:assert.equals(
|
||||
\ deoplete#util#vimoption2python('33,35-39,42-43,45-58,60-90,94,95,97-122,126'),
|
||||
\ '[\w!#-''*-+\--:<-Z^_a-z~]')
|
||||
call s:assert.equals(
|
||||
\ deoplete#util#vimoption2python('33-45'), '[\w!-\-]')
|
||||
endfunction
|
5
bundle/deoplete.nvim/test/conftest.py
Normal file
5
bundle/deoplete.nvim/test/conftest.py
Normal file
@ -0,0 +1,5 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
sys.path.insert(0, os.path.join(BASE_DIR, 'rplugin/python3'))
|
6
bundle/deoplete.nvim/test/requirements.txt
Normal file
6
bundle/deoplete.nvim/test/requirements.txt
Normal file
@ -0,0 +1,6 @@
|
||||
pynvim
|
||||
pytest
|
||||
flake8
|
||||
mypy
|
||||
vim-vint
|
||||
pytest-cov
|
@ -0,0 +1,41 @@
|
||||
from deoplete.filter.converter_reorder_attr import Filter
|
||||
|
||||
candidates = [
|
||||
{'word': 'Apple', 'kind': 'Fruit'},
|
||||
{'word': 'Banana', 'kind': 'Fruit'},
|
||||
{'word': 'Pen', 'kind': 'Object'},
|
||||
{'word': 'Cherry Pie', 'kind': 'Pie'},
|
||||
]
|
||||
|
||||
|
||||
def test_reorder():
|
||||
candidates_copy = candidates[:]
|
||||
|
||||
preferred_order = {'kind': ['Pie', 'Fruit']}
|
||||
|
||||
expected_candidates = [
|
||||
{'word': 'Cherry Pie', 'kind': 'Pie'},
|
||||
{'word': 'Apple', 'kind': 'Fruit'},
|
||||
{'word': 'Banana', 'kind': 'Fruit'},
|
||||
{'word': 'Pen', 'kind': 'Object'},
|
||||
]
|
||||
|
||||
assert expected_candidates == Filter.filter_attrs(
|
||||
candidates_copy, preferred_order
|
||||
)
|
||||
|
||||
|
||||
def test_filter():
|
||||
candidates_copy = candidates[:]
|
||||
|
||||
preferred_order = {'word': ['!Pen', 'Banana']}
|
||||
|
||||
expected_candidates = [
|
||||
{'word': 'Banana', 'kind': 'Fruit'},
|
||||
{'word': 'Apple', 'kind': 'Fruit'},
|
||||
{'word': 'Cherry Pie', 'kind': 'Pie'},
|
||||
]
|
||||
|
||||
assert expected_candidates == Filter.filter_attrs(
|
||||
candidates_copy, preferred_order
|
||||
)
|
@ -0,0 +1,41 @@
|
||||
import deoplete.util as util
|
||||
from deoplete.filter.converter_remove_overlap import overlap_length
|
||||
|
||||
|
||||
def test_fuzzy_escapse():
|
||||
assert util.fuzzy_escape('foo', 0) == 'f[^f]*o[^o]*o[^o]*'
|
||||
assert util.fuzzy_escape('foo', 1) == 'f[^f]*o[^o]*o[^o]*'
|
||||
assert util.fuzzy_escape('Foo', 1) == 'F[^F]*[oO].*[oO].*'
|
||||
|
||||
|
||||
def test_overlap_length():
|
||||
assert overlap_length('foo bar', 'bar baz') == 3
|
||||
assert overlap_length('foobar', 'barbaz') == 3
|
||||
assert overlap_length('foob', 'baz') == 1
|
||||
assert overlap_length('foobar', 'foobar') == 6
|
||||
assert overlap_length('тест', 'ст') == len('ст')
|
||||
|
||||
|
||||
def test_charwidth():
|
||||
assert util.charwidth('f') == 1
|
||||
assert util.charwidth('あ') == 2
|
||||
|
||||
|
||||
def test_strwidth():
|
||||
assert util.strwidth('foo bar') == 7
|
||||
assert util.strwidth('あいうえ') == 8
|
||||
assert util.strwidth('fooあい') == 7
|
||||
|
||||
|
||||
def test_truncate():
|
||||
assert util.truncate('foo bar', 3) == 'foo'
|
||||
assert util.truncate('fooあい', 5) == 'fooあ'
|
||||
assert util.truncate('あいうえ', 4) == 'あい'
|
||||
assert util.truncate('fooあい', 4) == 'foo'
|
||||
|
||||
|
||||
def test_skipping():
|
||||
assert util.truncate_skipping('foo bar', 3, '..', 3) == '..bar'
|
||||
assert util.truncate_skipping('foo bar', 6, '..', 3) == 'f..bar'
|
||||
assert util.truncate_skipping('fooあい', 5, '..', 3) == 'f..い'
|
||||
assert util.truncate_skipping('あいうえ', 6, '..', 2) == 'あ..え'
|
@ -0,0 +1,59 @@
|
||||
from deoplete.filter.matcher_full_fuzzy import Filter
|
||||
from test_matcher_fuzzy import _ctx
|
||||
|
||||
|
||||
def test_matcher_full_fuzzy():
|
||||
f = Filter(None)
|
||||
|
||||
assert f.name == 'matcher_full_fuzzy'
|
||||
assert f.description == 'full fuzzy matcher'
|
||||
|
||||
ctx = _ctx('')
|
||||
assert f.filter(ctx) == [
|
||||
{ 'word': 'foobar' },
|
||||
{ 'word': 'afoobar' },
|
||||
{ 'word': 'fooBar' },
|
||||
{ 'word': 'afooBar' },
|
||||
{ 'word': 'Foobar' },
|
||||
{ 'word': 'aFoobar' },
|
||||
{ 'word': 'FooBar' },
|
||||
{ 'word': 'aFooBar' },
|
||||
]
|
||||
|
||||
ctx = _ctx('FOBR')
|
||||
assert f.filter(ctx) == [
|
||||
{ 'word': 'foobar' },
|
||||
{ 'word': 'afoobar' },
|
||||
{ 'word': 'fooBar' },
|
||||
{ 'word': 'afooBar' },
|
||||
{ 'word': 'Foobar' },
|
||||
{ 'word': 'aFoobar' },
|
||||
{ 'word': 'FooBar' },
|
||||
{ 'word': 'aFooBar' },
|
||||
]
|
||||
|
||||
ctx = _ctx('foBr', ignorecase=False)
|
||||
assert f.filter(ctx) == [
|
||||
{ 'word': 'fooBar' },
|
||||
{ 'word': 'afooBar' },
|
||||
{ 'word': 'FooBar' },
|
||||
{ 'word': 'aFooBar' },
|
||||
]
|
||||
|
||||
ctx = _ctx('fobr', camelcase=False)
|
||||
assert f.filter(ctx) == [
|
||||
{ 'word': 'foobar' },
|
||||
{ 'word': 'afoobar' },
|
||||
{ 'word': 'fooBar' },
|
||||
{ 'word': 'afooBar' },
|
||||
{ 'word': 'Foobar' },
|
||||
{ 'word': 'aFoobar' },
|
||||
{ 'word': 'FooBar' },
|
||||
{ 'word': 'aFooBar' },
|
||||
]
|
||||
|
||||
ctx = _ctx('fobr', ignorecase=False, camelcase=False)
|
||||
assert f.filter(ctx) == [
|
||||
{ 'word': 'foobar' },
|
||||
{ 'word': 'afoobar' },
|
||||
]
|
@ -0,0 +1,68 @@
|
||||
from deoplete.filter.matcher_fuzzy import Filter
|
||||
|
||||
|
||||
def _ctx(complete_str, ignorecase=True, camelcase=True):
|
||||
_candidates = [
|
||||
{ 'word': 'foobar' },
|
||||
{ 'word': 'afoobar' },
|
||||
{ 'word': 'fooBar' },
|
||||
{ 'word': 'afooBar' },
|
||||
{ 'word': 'Foobar' },
|
||||
{ 'word': 'aFoobar' },
|
||||
{ 'word': 'FooBar' },
|
||||
{ 'word': 'aFooBar' },
|
||||
]
|
||||
|
||||
return {
|
||||
'complete_str' : complete_str,
|
||||
'ignorecase' : ignorecase,
|
||||
'camelcase' : camelcase,
|
||||
'is_sorted' : False,
|
||||
'candidates' : _candidates
|
||||
}
|
||||
|
||||
|
||||
def test_matcher_fuzzy():
|
||||
f = Filter(None)
|
||||
|
||||
assert f.name == 'matcher_fuzzy'
|
||||
assert f.description == 'fuzzy matcher'
|
||||
|
||||
ctx = _ctx('')
|
||||
assert f.filter(ctx) == [
|
||||
{ 'word': 'foobar' },
|
||||
{ 'word': 'afoobar' },
|
||||
{ 'word': 'fooBar' },
|
||||
{ 'word': 'afooBar' },
|
||||
{ 'word': 'Foobar' },
|
||||
{ 'word': 'aFoobar' },
|
||||
{ 'word': 'FooBar' },
|
||||
{ 'word': 'aFooBar' },
|
||||
]
|
||||
|
||||
ctx = _ctx('FOBR')
|
||||
assert f.filter(ctx) == [
|
||||
{ 'word': 'foobar' },
|
||||
{ 'word': 'fooBar' },
|
||||
{ 'word': 'Foobar' },
|
||||
{ 'word': 'FooBar' },
|
||||
]
|
||||
|
||||
ctx = _ctx('foBr', ignorecase=False)
|
||||
assert f.filter(ctx) == [
|
||||
{ 'word': 'fooBar' },
|
||||
{ 'word': 'FooBar' },
|
||||
]
|
||||
|
||||
ctx = _ctx('fobr', camelcase=False)
|
||||
assert f.filter(ctx) == [
|
||||
{ 'word': 'foobar' },
|
||||
{ 'word': 'fooBar' },
|
||||
{ 'word': 'Foobar' },
|
||||
{ 'word': 'FooBar' },
|
||||
]
|
||||
|
||||
ctx = _ctx('fobr', ignorecase=False, camelcase=False)
|
||||
assert f.filter(ctx) == [
|
||||
{ 'word': 'foobar' },
|
||||
]
|
@ -0,0 +1,69 @@
|
||||
import deoplete.util as util
|
||||
|
||||
|
||||
def test_pos():
|
||||
assert util.bytepos2charpos('utf-8', 'foo bar', 3) == 3
|
||||
assert util.bytepos2charpos('utf-8', 'あああ', 3) == 1
|
||||
assert util.charpos2bytepos('utf-8', 'foo bar', 3) == 3
|
||||
assert util.charpos2bytepos('utf-8', 'あああ', 3) == 9
|
||||
|
||||
|
||||
def test_custom():
|
||||
custom = {'source':{}}
|
||||
custom['source'] = {'_': {'mark': ''}, 'java': {'converters': []}}
|
||||
assert util.get_custom(custom, 'java', 'mark', 'foobar') == ''
|
||||
assert util.get_custom(custom, 'java', 'converters', 'foobar') == []
|
||||
assert util.get_custom(custom, 'foo', 'mark', 'foobar') == ''
|
||||
assert util.get_custom(custom, 'foo', 'converters', 'foobar') == 'foobar'
|
||||
|
||||
|
||||
def test_globruntime():
|
||||
assert util.globruntime('/usr', 'bin') == ['/usr/bin']
|
||||
|
||||
|
||||
def test_binary_search():
|
||||
assert util.binary_search_begin([], '') == -1
|
||||
assert util.binary_search_begin([{'word': 'abc'}], 'abc') == 0
|
||||
assert util.binary_search_begin([
|
||||
{'word': 'aaa'}, {'word': 'abc'},
|
||||
], 'abc') == 1
|
||||
assert util.binary_search_begin([
|
||||
{'word': 'a'}, {'word': 'aaa'}, {'word': 'abc'},
|
||||
], 'abc') == 2
|
||||
assert util.binary_search_begin([
|
||||
{'word': 'a'}, {'word': 'aaa'}, {'word': 'AbC'},
|
||||
], 'abc') == 2
|
||||
assert util.binary_search_begin([
|
||||
{'word': 'a'}, {'word': 'aaa'}, {'word': 'abc'},
|
||||
], 'b') == -1
|
||||
assert util.binary_search_begin([
|
||||
{'word': 'a'}, {'word': 'aaa'}, {'word': 'aac'}, {'word': 'abc'},
|
||||
], 'aa') == 1
|
||||
|
||||
assert util.binary_search_end([], '') == -1
|
||||
assert util.binary_search_end([{'word': 'abc'}], 'abc') == 0
|
||||
assert util.binary_search_end([
|
||||
{'word': 'aaa'}, {'word': 'abc'},
|
||||
], 'abc') == 1
|
||||
assert util.binary_search_end([
|
||||
{'word': 'a'}, {'word': 'aaa'}, {'word': 'abc'},
|
||||
], 'abc') == 2
|
||||
assert util.binary_search_end([
|
||||
{'word': 'a'}, {'word': 'aaa'}, {'word': 'abc'},
|
||||
], 'b') == -1
|
||||
assert util.binary_search_end([
|
||||
{'word': 'a'}, {'word': 'aaa'}, {'word': 'aac'}, {'word': 'abc'},
|
||||
], 'aa') == 2
|
||||
|
||||
|
||||
def test_uniq_list_dict():
|
||||
assert util.uniq_list_dict([
|
||||
{'abbr': 'word', 'word': 'foobar'},
|
||||
{'word': 'bar'},
|
||||
{'word': 'foobar', 'abbr': 'word'},
|
||||
{'word': 'baz'},
|
||||
]) == [
|
||||
{'word': 'foobar', 'abbr': 'word'},
|
||||
{'word': 'bar'},
|
||||
{'word': 'baz'}
|
||||
]
|
5
bundle/nvim-yarp/.gitignore
vendored
Normal file
5
bundle/nvim-yarp/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
__pycache__
|
||||
|
||||
# demo
|
||||
/plugin/hello.vim
|
||||
/pythonx/hello.py
|
20
bundle/nvim-yarp/LICENSE
Normal file
20
bundle/nvim-yarp/LICENSE
Normal file
@ -0,0 +1,20 @@
|
||||
Copyright © 2018 roxma@qq.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
120
bundle/nvim-yarp/README.md
Normal file
120
bundle/nvim-yarp/README.md
Normal file
@ -0,0 +1,120 @@
|
||||
|
||||
# Yet Another Remote Plugin Framework for Neovim
|
||||
|
||||
This is my attempt on writing a remote plugin framework without
|
||||
`:UpdateRemotePlugins`.
|
||||
|
||||
## Requirements
|
||||
|
||||
- `has('python3')`
|
||||
- For Vim 8:
|
||||
- [roxma/vim-hug-neovim-rpc](https://github.com/roxma/vim-hug-neovim-rpc)
|
||||
- `g:python3_host_prog` pointed to your python3 executable, or `echo
|
||||
exepath('python3')` is not empty.
|
||||
- [pynvim](https://github.com/neovim/pynvim) (`pip3
|
||||
install pynvim`)
|
||||
|
||||
## Use case
|
||||
|
||||
- [shougo/deoplete.nvim](https://github.com/shougo/deoplete.nvim)
|
||||
- [ncm2/ncm2](https://github.com/ncm2/ncm2) and most of its plugins
|
||||
|
||||
## Usage
|
||||
|
||||
pythonx/hello.py
|
||||
|
||||
```python
|
||||
import vim, time
|
||||
def greet():
|
||||
time.sleep(3)
|
||||
vim.command('echo "Hello world"')
|
||||
```
|
||||
|
||||
plugin/hello.vim
|
||||
|
||||
```vim
|
||||
" Create a python3 process running the hello module. The process is lazy load.
|
||||
let s:hello = yarp#py3('hello')
|
||||
|
||||
com HelloSync call s:hello.request('greet')
|
||||
com HelloAsync call s:hello.notify('greet')
|
||||
|
||||
" You could type :Hello greet
|
||||
com -nargs=1 Hello call s:hello.request(<f-args>)
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
Add logging settigns to your vimrc. Log files will be generated with prefix
|
||||
`/tmp/nvim_log`. An alternative is to export environment variables before
|
||||
starting vim/nvim.
|
||||
|
||||
```vim
|
||||
let $NVIM_PYTHON_LOG_FILE="/tmp/nvim_log"
|
||||
let $NVIM_PYTHON_LOG_LEVEL="DEBUG"
|
||||
```
|
||||
|
||||
## Example for existing neovim rplugin porting to Vim 8
|
||||
|
||||
More realistic examples could be found at
|
||||
[nvim-typescript#84](https://github.com/mhartington/nvim-typescript/pull/84),
|
||||
[deoplete#553](https://github.com/Shougo/deoplete.nvim/pull/553),
|
||||
[callmekohei/quickdebug](https://github.com/callmekohei/quickdebug).
|
||||
|
||||
Now let's consider the following simple rplugin.
|
||||
|
||||
After `UpdateRemotePlugins` and restarting neovim, you get `foobar` by `:echo
|
||||
Bar()`.
|
||||
|
||||
```python
|
||||
# rplugin/python3/foo.py
|
||||
import pynvim
|
||||
|
||||
@pynvim.plugin
|
||||
class Foo(object):
|
||||
|
||||
def __init__(self, vim):
|
||||
self._vim = vim
|
||||
|
||||
@pynvim.function("Bar", sync=True)
|
||||
def bar(self, args):
|
||||
return 'hello' + str(args)
|
||||
```
|
||||
|
||||
For working on Vim 8, you need to add these two files:
|
||||
|
||||
|
||||
```vim
|
||||
" plugin/foo.vim
|
||||
if has('nvim')
|
||||
finish
|
||||
endif
|
||||
|
||||
let s:foo = yarp#py3('foo_wrap')
|
||||
|
||||
func! Bar(v)
|
||||
return s:foo.call('bar',a:v)
|
||||
endfunc
|
||||
```
|
||||
|
||||
|
||||
```python
|
||||
# pythonx/foo_wrap.py
|
||||
from foo import Foo as _Foo
|
||||
import vim
|
||||
|
||||
_obj = _Foo(vim)
|
||||
|
||||
|
||||
def bar(*args):
|
||||
return _obj.bar(args)
|
||||
```
|
||||
|
||||
How to use
|
||||
```
|
||||
$ vim
|
||||
|
||||
: echo bar('world')
|
||||
|
||||
hello('world',)
|
||||
```
|
25
bundle/nvim-yarp/autoload/yarp.vim
Normal file
25
bundle/nvim-yarp/autoload/yarp.vim
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
func! yarp#py3(module)
|
||||
if type(a:module) == v:t_string
|
||||
let rp = {}
|
||||
let rp.module = a:module
|
||||
else
|
||||
let rp = a:module
|
||||
endif
|
||||
let rp.init = function('yarp#pyx#init')
|
||||
let rp.type = 'py3'
|
||||
return yarp#core#new(rp)
|
||||
endfunc
|
||||
|
||||
func! yarp#py(module)
|
||||
if type(a:module) == v:t_string
|
||||
let rp = {}
|
||||
let rp.module = a:module
|
||||
else
|
||||
let rp = a:module
|
||||
endif
|
||||
let rp.init = function('yarp#pyx#init')
|
||||
let rp.type = 'py'
|
||||
return yarp#core#new(rp)
|
||||
endfunc
|
||||
|
188
bundle/nvim-yarp/autoload/yarp/core.vim
Normal file
188
bundle/nvim-yarp/autoload/yarp/core.vim
Normal file
@ -0,0 +1,188 @@
|
||||
if get(s:, 'loaded', 0)
|
||||
finish
|
||||
endif
|
||||
let s:loaded = 1
|
||||
|
||||
let s:id = 1
|
||||
let s:reg = {}
|
||||
let s:leaving = 0
|
||||
|
||||
augroup yarp
|
||||
autocmd!
|
||||
" this one is which you're most likely to use?
|
||||
autocmd VimLeavePre * let s:leaving = 1
|
||||
augroup end
|
||||
|
||||
if has('nvim')
|
||||
let s:rpcrequest = 'rpcrequest'
|
||||
let s:rpcnotify = 'rpcnotify'
|
||||
let s:jobstart = 'jobstart'
|
||||
fun! s:_serveraddr()
|
||||
return v:servername
|
||||
endfunc
|
||||
let s:serveraddr = function('s:_serveraddr')
|
||||
else
|
||||
let s:rpcrequest = get(g:, 'yarp_rpcrequest', 'neovim_rpc#rpcrequest')
|
||||
let s:rpcnotify = get(g:, 'yarp_rpcnotify', 'neovim_rpc#rpcnotify')
|
||||
let s:jobstart = get(g:, 'yarp_jobstart', 'neovim_rpc#jobstart')
|
||||
let s:serveraddr = get(g:, 'yarp_serveraddr', 'neovim_rpc#serveraddr')
|
||||
endif
|
||||
|
||||
func! yarp#core#new(rp)
|
||||
let s:id = s:id + 1
|
||||
|
||||
let rp = a:rp
|
||||
let rp.jobstart = function('yarp#core#jobstart')
|
||||
func rp.error(msg) dict
|
||||
call yarp#core#error(self.module, a:msg)
|
||||
endfunc
|
||||
let rp.call = function('yarp#core#request')
|
||||
let rp.request = function('yarp#core#request')
|
||||
let rp.notify = function('yarp#core#notify')
|
||||
let rp.try_notify = function('yarp#core#try_notify')
|
||||
let rp.wait_channel = function('yarp#core#wait_channel')
|
||||
let rp.id = s:id
|
||||
let rp.job_is_dead = 0
|
||||
let s:reg[rp.id] = rp
|
||||
|
||||
" options
|
||||
let rp.on_load = get(rp, 'on_load', function('yarp#core#_nop'))
|
||||
let rp.job_detach = get(rp, 'job_detach', 0)
|
||||
|
||||
" reserved for user
|
||||
let rp.user_data = get(rp, 'user_data', {})
|
||||
return rp
|
||||
endfunc
|
||||
|
||||
func! yarp#core#_nop(...) dict
|
||||
endfunc
|
||||
|
||||
func! yarp#core#on_stderr(chan_id, data, event) dict
|
||||
let mod = self.self
|
||||
call mod.error(filter(a:data, 'len(v:val)'))
|
||||
endfunc
|
||||
|
||||
func! yarp#core#on_exit(chan_id, data, event) dict
|
||||
let mod = self.self
|
||||
let mod.job_is_dead = 1
|
||||
if has_key(mod, 'channel')
|
||||
unlet mod.channel
|
||||
endif
|
||||
|
||||
if has("nvim")
|
||||
if v:exiting is 0
|
||||
return
|
||||
endif
|
||||
elseif v:dying || s:leaving
|
||||
return
|
||||
endif
|
||||
call mod.error("Job is dead. cmd=" . string(mod.cmd))
|
||||
endfunc
|
||||
|
||||
func! yarp#core#channel_started(id, channel)
|
||||
let rp = s:reg[a:id]
|
||||
let rp.channel = a:channel
|
||||
call call(rp.on_load, [], rp)
|
||||
endfunc
|
||||
|
||||
func! yarp#core#request(method, ...) dict
|
||||
call self.wait_channel()
|
||||
return call(s:rpcrequest, [self.channel, a:method] + a:000)
|
||||
endfunc
|
||||
|
||||
func! yarp#core#notify(method, ...) dict
|
||||
call self.wait_channel()
|
||||
call call(s:rpcnotify, [self.channel, a:method] + a:000)
|
||||
endfunc
|
||||
|
||||
func! yarp#core#try_notify(method, ...) dict
|
||||
call self.jobstart()
|
||||
if get(self, 'job_is_dead', 0)
|
||||
call self.error('try_notify ' . a:method . ' failed, job is dead')
|
||||
return 0
|
||||
endif
|
||||
if !has_key(self, 'channel')
|
||||
" not yet started
|
||||
return 0
|
||||
endif
|
||||
let args = [self.channel, a:method] + a:000
|
||||
try
|
||||
call call(s:rpcnotify, args)
|
||||
return 1
|
||||
catch
|
||||
call self.error('try_notify ' . s:rpcnotify . ' ' . a:method . ' failed: ' . v:exception . ', ' . string(args))
|
||||
return 0
|
||||
endtry
|
||||
endfunc
|
||||
|
||||
func! yarp#core#wait_channel() dict
|
||||
if has_key(self, 'channel')
|
||||
return
|
||||
endif
|
||||
if ! has_key(self, 'job')
|
||||
call self.jobstart()
|
||||
endif
|
||||
if get(self, 'job', -1) == -1
|
||||
throw '[yarp] [' . self.module . '] job is not running'
|
||||
endif
|
||||
let cnt = 5000 / 20
|
||||
while ! has_key(self, 'channel')
|
||||
if self.job_is_dead
|
||||
throw '[yarp] [' . self.module .
|
||||
\ '] job is dead. failed establishing channel for ' .
|
||||
\ string(self.cmd)
|
||||
endif
|
||||
if cnt <= 0
|
||||
throw '[yarp] [' . self.module . '] timeout establishing channel for ' . string(self.cmd)
|
||||
endif
|
||||
let cnt = cnt - 1
|
||||
silent sleep 20m
|
||||
endwhile
|
||||
endfunc
|
||||
|
||||
func! yarp#core#jobstart() dict
|
||||
if ! has_key(self, 'cmd')
|
||||
call self.init()
|
||||
if ! has_key(self, 'cmd')
|
||||
call self.error("cmd of the job is not set")
|
||||
return
|
||||
endif
|
||||
endif
|
||||
if has_key(self, 'job')
|
||||
return
|
||||
endif
|
||||
let opts = {'on_stderr': function('yarp#core#on_stderr'),
|
||||
\ 'on_exit': function('yarp#core#on_exit'),
|
||||
\ 'detach': self.job_detach,
|
||||
\ 'self': self}
|
||||
try
|
||||
let self.job = call(s:jobstart, [self.cmd, opts])
|
||||
if self.job == -1
|
||||
call self.error('Failed starting job: ' . string(self.cmd))
|
||||
endif
|
||||
catch
|
||||
let self.job = -1
|
||||
call self.error(['Failed starting job: ' . string(self.cmd), v:exception])
|
||||
endtry
|
||||
endfunc
|
||||
|
||||
func! yarp#core#serveraddr()
|
||||
return call (s:serveraddr, [])
|
||||
endfunc
|
||||
|
||||
func! yarp#core#error(mod, msg)
|
||||
if mode() == 'i'
|
||||
" NOTE: side effect, sorry, but this is necessary
|
||||
set nosmd
|
||||
endif
|
||||
if type(a:msg) == type("")
|
||||
let lines = split(a:msg, "\n", 1)
|
||||
else
|
||||
let lines = a:msg
|
||||
endif
|
||||
echoh ErrorMsg
|
||||
for line in lines
|
||||
echom '[' . a:mod . '@yarp] ' . line
|
||||
endfor
|
||||
echoh None
|
||||
endfunc
|
71
bundle/nvim-yarp/autoload/yarp/pyx.vim
Normal file
71
bundle/nvim-yarp/autoload/yarp/pyx.vim
Normal file
@ -0,0 +1,71 @@
|
||||
|
||||
func! yarp#pyx#init() dict
|
||||
if self.type == 'py'
|
||||
let l:Detect = function('s:pyexe')
|
||||
else
|
||||
let l:Detect = function('s:py3exe')
|
||||
endif
|
||||
|
||||
let exe = call(l:Detect, [], self)
|
||||
|
||||
if get(s:, 'script', '') == ''
|
||||
let s:script = globpath(&rtp,'pythonx/yarp.py',1)
|
||||
endif
|
||||
|
||||
let self.cmd = [exe,
|
||||
\ '-u',
|
||||
\ s:script,
|
||||
\ yarp#core#serveraddr(),
|
||||
\ self.id,
|
||||
\ self.module]
|
||||
|
||||
call self.jobstart()
|
||||
endfunc
|
||||
|
||||
func! s:pyexe() dict
|
||||
if get(g:, '_yarp_py', '')
|
||||
return g:_yarp_py
|
||||
endif
|
||||
let g:_yarp_py = get(g:, 'python_host_prog', '')
|
||||
if g:_yarp_py == '' && has('nvim') && has('python')
|
||||
" heavy weight
|
||||
" but better support for python detection
|
||||
python import sys
|
||||
let g:_yarp_py = pyeval('sys.executable')
|
||||
endif
|
||||
if g:_yarp_py == ''
|
||||
let g:_yarp_py = 'python2'
|
||||
endif
|
||||
return g:_yarp_py
|
||||
endfunc
|
||||
|
||||
func! s:py3exe() dict
|
||||
if get(g:, '_yarp_py3', '')
|
||||
return g:_yarp_py3
|
||||
endif
|
||||
let g:_yarp_py3 = get(g:, 'python3_host_prog', '')
|
||||
if g:_yarp_py3 == '' && has('nvim') && has('python3')
|
||||
" heavy weight
|
||||
" but better support for python detection
|
||||
python3 import sys
|
||||
let g:_yarp_py3 = py3eval('sys.executable')
|
||||
endif
|
||||
if g:_yarp_py3 == ''
|
||||
let g:_yarp_py3 = 'python3'
|
||||
endif
|
||||
if exepath(g:_yarp_py3) == ''
|
||||
call self.error(
|
||||
\ "Python3 executable [" .
|
||||
\ g:_yarp_py3 .
|
||||
\ "] not found.")
|
||||
if has('vim_starting')
|
||||
call self.error("")
|
||||
endif
|
||||
call self.error("###### Please configure g:python3_host_prog properly ######")
|
||||
if has('vim_starting')
|
||||
call self.error("")
|
||||
endif
|
||||
endif
|
||||
return g:_yarp_py3
|
||||
endfunc
|
||||
|
71
bundle/nvim-yarp/pythonx/yarp.py
Normal file
71
bundle/nvim-yarp/pythonx/yarp.py
Normal file
@ -0,0 +1,71 @@
|
||||
from importlib.util import find_spec
|
||||
|
||||
if find_spec('pynvim'):
|
||||
from pynvim import attach, setup_logging
|
||||
else:
|
||||
from neovim import attach, setup_logging
|
||||
|
||||
import sys
|
||||
import importlib
|
||||
from os import environ
|
||||
|
||||
assert __name__ == "__main__"
|
||||
|
||||
if "" in sys.path:
|
||||
sys.path.remove("")
|
||||
|
||||
serveraddr = sys.argv[1]
|
||||
yarpid = int(sys.argv[2])
|
||||
module = sys.argv[3]
|
||||
module_obj = None
|
||||
nvim = None
|
||||
|
||||
environ['NVIM_YARP_MODULE'] = module
|
||||
|
||||
setup_logging(module)
|
||||
|
||||
def on_request(method, args):
|
||||
if hasattr(module_obj, method):
|
||||
return getattr(module_obj, method)(*args)
|
||||
else:
|
||||
raise Exception('method %s not found' % method)
|
||||
|
||||
|
||||
def on_notification(method, args):
|
||||
if hasattr(module_obj, method):
|
||||
getattr(module_obj, method)(*args)
|
||||
else:
|
||||
raise Exception('method %s not found' % method)
|
||||
pass
|
||||
|
||||
|
||||
def on_setup():
|
||||
pass
|
||||
|
||||
try:
|
||||
# create another connection to avoid synchronization issue?
|
||||
if len(serveraddr.split(':')) == 2:
|
||||
serveraddr, port = serveraddr.split(':')
|
||||
port = int(port)
|
||||
nvim = attach('tcp', address=serveraddr, port=port)
|
||||
else:
|
||||
nvim = attach('socket', path=serveraddr)
|
||||
|
||||
sys.modules['vim'] = nvim
|
||||
sys.modules['nvim'] = nvim
|
||||
|
||||
paths = nvim.eval(r'globpath(&rtp,"pythonx",1) . "\n" .'
|
||||
r' globpath(&rtp,"rplugin/python3",1)')
|
||||
for path in paths.split("\n"):
|
||||
if not path:
|
||||
continue
|
||||
if path not in sys.path:
|
||||
sys.path.append(path)
|
||||
|
||||
module_obj = importlib.import_module(module)
|
||||
|
||||
nvim.call('yarp#core#channel_started', yarpid, nvim.channel_id)
|
||||
|
||||
nvim.run_loop(on_request, on_notification, on_setup)
|
||||
finally:
|
||||
nvim.close()
|
3
bundle/vim-hug-neovim-rpc/.gitignore
vendored
Normal file
3
bundle/vim-hug-neovim-rpc/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
*.pyc
|
||||
__pycache__
|
20
bundle/vim-hug-neovim-rpc/LICENSE
Normal file
20
bundle/vim-hug-neovim-rpc/LICENSE
Normal file
@ -0,0 +1,20 @@
|
||||
Copyright © 2018 roxma@qq.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
162
bundle/vim-hug-neovim-rpc/README.md
Normal file
162
bundle/vim-hug-neovim-rpc/README.md
Normal file
@ -0,0 +1,162 @@
|
||||
|
||||
# vim-hug-neovim-rpc
|
||||
|
||||
This is an **experimental project**, trying to build a compatibility layer for
|
||||
[neovim rpc client](https://github.com/neovim/python-client) working on vim8.
|
||||
I started this project because I want to fix the [vim8
|
||||
support](https://github.com/roxma/nvim-completion-manager/issues/14) issue for
|
||||
[nvim-completion-manager](https://github.com/roxma/nvim-completion-manager).
|
||||
|
||||
Since this is a general purpose module, other plugins needing rpc support may
|
||||
benefit from this project. However, there're many neovim rpc methods I haven't
|
||||
implemented yet, which make this an experimental plugin. **Please fork and
|
||||
open a PR if you get any idea on improving it**.
|
||||
|
||||
***Tip: for porting neovim rplugin to vim8, you might need
|
||||
[roxma/nvim-yarp](https://github.com/roxma/nvim-yarp)***
|
||||
|
||||

|
||||
|
||||
## Requirements
|
||||
|
||||
|
||||
1. vim8
|
||||
2. If `has('pythonx')` and `set pyxversion=3`
|
||||
- same requirements as `4. has('python3')`
|
||||
2. Else if `has('pythonx')` and `set pyxversion=2`
|
||||
- same requirements as `5. has('python')`
|
||||
4. Else if `has('python3')`
|
||||
- [pynvim](https://github.com/neovim/pynvim)
|
||||
- Pynvim is normally installed by `:py3 import pip; pip.main(['install',
|
||||
'--user', 'pynvim'])` or `python3 -m pip install pynvim`.
|
||||
- There should be no error for at least one of `:python3 import pynvim` and
|
||||
`:python3 import neovim`
|
||||
5. Else if `has('python')`
|
||||
- [pynvim](https://github.com/neovim/pynvim)
|
||||
- Pynvim is normally installed by `:py import pip; pip.main(['install',
|
||||
'--user', 'pynvim'])` or `python2 -m pip install pynvim`.
|
||||
- There should be no error for at least one of `:python3 import pynvim` and
|
||||
`:python3 import neovim`
|
||||
6. `set encoding=utf-8` in your vimrc.
|
||||
|
||||
***Use `:echo neovim_rpc#serveraddr()` to test the installation***. It should print
|
||||
something like `127.0.0.1:51359` or `/tmp/vmrUX9X/2`.
|
||||
|
||||
## API
|
||||
|
||||
| Function | Similar to neovim's |
|
||||
|----------------------------------------------|------------------------------------------------|
|
||||
| `neovim_rpc#serveraddr()` | `v:servername` |
|
||||
| `neovim_rpc#jobstart(cmd,...)` | `jobstart({cmd}[, {opts}])` |
|
||||
| `neovim_rpc#jobstop(jobid)` | `jobstop({job})` |
|
||||
| `neovim_rpc#rpcnotify(channel,event,...)` | `rpcnotify({channel}, {event}[, {args}...])` |
|
||||
| `neovim_rpc#rpcrequest(channel, event, ...)` | `rpcrequest({channel}, {method}[, {args}...])` |
|
||||
|
||||
Note that `neovim_rpc#jobstart` only support these options:
|
||||
|
||||
- `on_stdout`
|
||||
- `on_stderr`
|
||||
- `on_exit`
|
||||
- `detach`
|
||||
|
||||
## Incompatibility issues
|
||||
|
||||
- Cannot pass `Funcref` object to python client. Pass function name instead.
|
||||
- Python `None` will be converted to `''` instead of `v:null` into vimscript.
|
||||
See [vim#2246](https://github.com/vim/vim/issues/2246)
|
||||
- The following neovim-only API will be ignored quietly:
|
||||
- `nvim_buf_add_highlight`
|
||||
- `nvim_buf_clear_highlight`
|
||||
|
||||
## Overall Implementation
|
||||
|
||||
```
|
||||
"vim-hug-neovim-rpc - Sequence Diagram"
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
┌───┐ ┌──────────┐ ┌───────────┐ ┌──────┐
|
||||
│VIM│ │VIM Server│ │NVIM Server│ │Client│
|
||||
└─┬─┘ └────┬─────┘ └─────┬─────┘ └──┬───┘
|
||||
│ Launch thread │ │ │
|
||||
│───────────────────> │ │
|
||||
│ │ │ │
|
||||
│ Launch thread │ │
|
||||
│─────────────────────────────────────────────────>│ │
|
||||
│ │ │ │
|
||||
│ `ch_open` connect │ │ │
|
||||
│───────────────────> │ │
|
||||
│ │ │ │
|
||||
│ │────┐ │ │
|
||||
│ │ │ Launch VimHandler thread│ │
|
||||
│ │<───┘ │ │
|
||||
│ │ │ │
|
||||
│ │ │ Connect │
|
||||
│ │ │<─────────────────────────────
|
||||
│ │ │ │
|
||||
│ │ ────┐
|
||||
│ │ │ Launch NvimHandler thread
|
||||
│ │ <───┘
|
||||
│ │ │ │
|
||||
│ │ │ Request (msgpack rpc) │
|
||||
│ │ │<─────────────────────────────
|
||||
│ │ │ │
|
||||
│ │ ────┐ │
|
||||
│ │ │ Request enqueue │
|
||||
│ │ <───┘ │
|
||||
│ │ │ │
|
||||
│ │ notify (method call) │ │
|
||||
│ │ <────────────────────────────│ │
|
||||
│ │ │ │
|
||||
│ notify (json rpc) │ │ │
|
||||
│<─────────────────── │ │
|
||||
│ │ │ │
|
||||
────┐ │ │
|
||||
│ Request dequeue │ │
|
||||
<───┘ │ │
|
||||
│ │ │ │
|
||||
────┐ │ │ │
|
||||
│ Process │ │ │
|
||||
<───┘ │ │ │
|
||||
│ │ │ │
|
||||
│ │ Send response (msgpack rpc) │
|
||||
│────────────────────────────────────────────────────────────────────────────────>
|
||||
┌─┴─┐ ┌────┴─────┐ ┌─────┴─────┐ ┌──┴───┐
|
||||
│VIM│ │VIM Server│ │NVIM Server│ │Client│
|
||||
└───┘ └──────────┘ └───────────┘ └──────┘
|
||||
```
|
||||
|
||||
<!--
|
||||
@startuml
|
||||
|
||||
title "vim-hug-neovim-rpc - Sequence Diagram"
|
||||
|
||||
VIM -> "VIM Server": Launch thread
|
||||
VIM -> "NVIM Server": Launch thread
|
||||
VIM -> "VIM Server": `ch_open` connect
|
||||
"VIM Server" -> "VIM Server": Launch VimHandler thread
|
||||
|
||||
Client-> "NVIM Server": Connect
|
||||
"NVIM Server" -> "NVIM Server": Launch NvimHandler thread
|
||||
Client -> "NVIM Server": Request (msgpack rpc)
|
||||
"NVIM Server" -> "NVIM Server": Request enqueue
|
||||
"NVIM Server" -> "VIM Server": notify (method call)
|
||||
"VIM Server" -> VIM: notify (json rpc)
|
||||
VIM -> VIM: Request dequeue
|
||||
VIM -> VIM: Process
|
||||
VIM -> Client: Send response (msgpack rpc)
|
||||
|
||||
@enduml
|
||||
-->
|
||||
|
||||
## Debugging
|
||||
|
||||
Add logging settigns to your vimrc. Log files will be generated with prefix
|
||||
`/tmp/nvim_log`. An alternative is to export environment variables before
|
||||
starting vim/nvim.
|
||||
|
||||
```vim
|
||||
let $NVIM_PYTHON_LOG_FILE="/tmp/nvim_log"
|
||||
let $NVIM_PYTHON_LOG_LEVEL="DEBUG"
|
||||
```
|
||||
|
204
bundle/vim-hug-neovim-rpc/autoload/neovim_rpc.vim
Normal file
204
bundle/vim-hug-neovim-rpc/autoload/neovim_rpc.vim
Normal file
@ -0,0 +1,204 @@
|
||||
|
||||
if has('pythonx')
|
||||
let g:neovim_rpc#py = 'pythonx'
|
||||
let s:pyeval = function('pyxeval')
|
||||
elseif has('python3')
|
||||
let g:neovim_rpc#py = 'python3'
|
||||
let s:pyeval = function('py3eval')
|
||||
else
|
||||
let g:neovim_rpc#py = 'python'
|
||||
let s:pyeval = function('pyeval')
|
||||
endif
|
||||
|
||||
func! s:py(cmd)
|
||||
execute g:neovim_rpc#py a:cmd
|
||||
endfunc
|
||||
|
||||
func! neovim_rpc#serveraddr()
|
||||
if exists('g:_neovim_rpc_nvim_server')
|
||||
return g:_neovim_rpc_nvim_server
|
||||
endif
|
||||
|
||||
if &encoding !=? "utf-8"
|
||||
throw '[vim-hug-neovim-rpc] requires `:set encoding=utf-8`'
|
||||
endif
|
||||
|
||||
try
|
||||
call s:py('import pynvim')
|
||||
catch
|
||||
try
|
||||
call s:py('import neovim')
|
||||
catch
|
||||
call neovim_rpc#_error("failed executing: " .
|
||||
\ g:neovim_rpc#py . " import [pynvim|neovim]")
|
||||
call neovim_rpc#_error(v:exception)
|
||||
throw '[vim-hug-neovim-rpc] requires one of `:' . g:neovim_rpc#py .
|
||||
\ ' import [pynvim|neovim]` command to work'
|
||||
endtry
|
||||
endtry
|
||||
|
||||
call s:py('import neovim_rpc_server')
|
||||
let l:servers = s:pyeval('neovim_rpc_server.start()')
|
||||
|
||||
let g:_neovim_rpc_nvim_server = l:servers[0]
|
||||
let g:_neovim_rpc_vim_server = l:servers[1]
|
||||
|
||||
let g:_neovim_rpc_main_channel = ch_open(g:_neovim_rpc_vim_server)
|
||||
|
||||
" identify myself
|
||||
call ch_sendexpr(g:_neovim_rpc_main_channel,'neovim_rpc_setup')
|
||||
|
||||
return g:_neovim_rpc_nvim_server
|
||||
endfunc
|
||||
|
||||
" elegant python function call wrapper
|
||||
func! neovim_rpc#pyxcall(func,...)
|
||||
call s:py('import vim, json')
|
||||
let g:neovim_rpc#_tmp_args = copy(a:000)
|
||||
let l:ret = s:pyeval(a:func . '(*vim.vars["neovim_rpc#_tmp_args"])')
|
||||
unlet g:neovim_rpc#_tmp_args
|
||||
return l:ret
|
||||
endfunc
|
||||
|
||||
" supported opt keys:
|
||||
" - on_stdout
|
||||
" - on_stderr
|
||||
" - on_exit
|
||||
" - detach
|
||||
func! neovim_rpc#jobstart(cmd,...)
|
||||
|
||||
let l:opts = {}
|
||||
if len(a:000)
|
||||
let l:opts = a:1
|
||||
endif
|
||||
|
||||
let l:opts['_close'] = 0
|
||||
let l:opts['_exit'] = 0
|
||||
|
||||
let l:real_opts = {'mode': 'raw'}
|
||||
if has_key(l:opts,'detach') && l:opts['detach']
|
||||
let l:real_opts['stoponexit'] = ''
|
||||
endif
|
||||
|
||||
if has_key(l:opts,'on_stdout')
|
||||
let l:real_opts['out_cb'] = function('neovim_rpc#_on_stdout')
|
||||
endif
|
||||
if has_key(l:opts,'on_stderr')
|
||||
let l:real_opts['err_cb'] = function('neovim_rpc#_on_stderr')
|
||||
endif
|
||||
let l:real_opts['exit_cb'] = function('neovim_rpc#_on_exit')
|
||||
let l:real_opts['close_cb'] = function('neovim_rpc#_on_close')
|
||||
|
||||
let l:job = job_start(a:cmd, l:real_opts)
|
||||
let l:jobid = ch_info(l:job)['id']
|
||||
|
||||
let g:_neovim_rpc_jobs[l:jobid] = {'cmd':a:cmd, 'opts': l:opts, 'job': l:job}
|
||||
|
||||
return l:jobid
|
||||
endfunc
|
||||
|
||||
func! neovim_rpc#jobstop(jobid)
|
||||
let l:job = g:_neovim_rpc_jobs[a:jobid]['job']
|
||||
return job_stop(l:job)
|
||||
endfunc
|
||||
|
||||
func! neovim_rpc#rpcnotify(channel,event,...)
|
||||
call neovim_rpc#pyxcall('neovim_rpc_server.rpcnotify',a:channel,a:event,a:000)
|
||||
endfunc
|
||||
|
||||
let s:rspid = 1
|
||||
func! neovim_rpc#rpcrequest(channel, event, ...)
|
||||
let s:rspid = s:rspid + 1
|
||||
|
||||
" a unique key for storing response
|
||||
let rspid = '' . s:rspid
|
||||
|
||||
" neovim's rpcrequest doesn't have timeout
|
||||
let opt = {'timeout': 24 * 60 * 60 * 1000}
|
||||
let args = ['rpcrequest', a:channel, a:event, a:000, rspid]
|
||||
call ch_evalexpr(g:_neovim_rpc_main_channel, args, opt)
|
||||
|
||||
let expr = 'neovim_rpc_server.responses.pop("' . rspid . '")'
|
||||
|
||||
call s:py('import neovim_rpc_server, json')
|
||||
let [err, result] = s:pyeval(expr)
|
||||
if err
|
||||
if type(err) == type('')
|
||||
throw err
|
||||
endif
|
||||
throw err[1]
|
||||
endif
|
||||
return result
|
||||
endfunc
|
||||
|
||||
func! neovim_rpc#_on_stdout(job,data)
|
||||
let l:jobid = ch_info(a:job)['id']
|
||||
let l:opts = g:_neovim_rpc_jobs[l:jobid]['opts']
|
||||
" convert to neovim style function call
|
||||
call call(l:opts['on_stdout'],[l:jobid,split(a:data,"\n",1),'stdout'],l:opts)
|
||||
endfunc
|
||||
|
||||
func! neovim_rpc#_on_stderr(job,data)
|
||||
let l:jobid = ch_info(a:job)['id']
|
||||
let l:opts = g:_neovim_rpc_jobs[l:jobid]['opts']
|
||||
" convert to neovim style function call
|
||||
call call(l:opts['on_stderr'],[l:jobid,split(a:data,"\n",1),'stderr'],l:opts)
|
||||
endfunc
|
||||
|
||||
func! neovim_rpc#_on_exit(job,status)
|
||||
let l:jobid = ch_info(a:job)['id']
|
||||
let l:opts = g:_neovim_rpc_jobs[l:jobid]['opts']
|
||||
let l:opts['_exit'] = 1
|
||||
" cleanup when both close_cb and exit_cb is called
|
||||
if l:opts['_close'] && l:opts['_exit']
|
||||
unlet g:_neovim_rpc_jobs[l:jobid]
|
||||
endif
|
||||
if has_key(l:opts, 'on_exit')
|
||||
" convert to neovim style function call
|
||||
call call(l:opts['on_exit'],[l:jobid,a:status,'exit'],l:opts)
|
||||
endif
|
||||
endfunc
|
||||
|
||||
func! neovim_rpc#_on_close(job)
|
||||
let l:jobid = ch_info(a:job)['id']
|
||||
let l:opts = g:_neovim_rpc_jobs[l:jobid]['opts']
|
||||
let l:opts['_close'] = 1
|
||||
" cleanup when both close_cb and exit_cb is called
|
||||
if l:opts['_close'] && l:opts['_exit']
|
||||
unlet g:_neovim_rpc_jobs[l:jobid]
|
||||
endif
|
||||
endfunc
|
||||
|
||||
func! neovim_rpc#_callback()
|
||||
execute g:neovim_rpc#py . ' neovim_rpc_server.process_pending_requests()'
|
||||
endfunc
|
||||
|
||||
let g:_neovim_rpc_main_channel = -1
|
||||
let g:_neovim_rpc_jobs = {}
|
||||
|
||||
let s:leaving = 0
|
||||
|
||||
func! neovim_rpc#_error(msg)
|
||||
if mode() == 'i'
|
||||
" NOTE: side effect, sorry, but this is necessary
|
||||
set nosmd
|
||||
endif
|
||||
echohl ErrorMsg
|
||||
echom '[vim-hug-neovim-rpc] ' . a:msg
|
||||
echohl None
|
||||
endfunc
|
||||
|
||||
func! neovim_rpc#_nvim_err_write(msg)
|
||||
if mode() == 'i'
|
||||
" NOTE: side effect, sorry, but this is necessary
|
||||
set nosmd
|
||||
endif
|
||||
echohl ErrorMsg
|
||||
let g:error = a:msg
|
||||
echom a:msg
|
||||
echohl None
|
||||
endfunc
|
||||
|
||||
func! neovim_rpc#_nvim_out_write(msg)
|
||||
echom a:msg
|
||||
endfunc
|
194
bundle/vim-hug-neovim-rpc/pythonx/neovim_rpc_methods.py
Normal file
194
bundle/vim-hug-neovim-rpc/pythonx/neovim_rpc_methods.py
Normal file
@ -0,0 +1,194 @@
|
||||
# vim:set et sw=4 ts=8:
|
||||
import vim
|
||||
|
||||
# vim's python binding doesn't have the `call` method, wrap it here
|
||||
|
||||
|
||||
def nvim_call_function(method, args):
|
||||
vim.vars['_neovim_rpc_tmp_args'] = args
|
||||
# vim.eval('getcurpos()') return an array of string, it should be an array
|
||||
# of int. Use json_encode to workaround this
|
||||
return vim.bindeval('call("%s",g:_neovim_rpc_tmp_args)' % method)
|
||||
|
||||
|
||||
def nvim_get_current_buf():
|
||||
return vim.current.buffer
|
||||
|
||||
|
||||
def nvim_list_bufs():
|
||||
return list(vim.buffers)
|
||||
|
||||
|
||||
def nvim_buf_get_number(buf):
|
||||
return buf.number
|
||||
|
||||
|
||||
def nvim_buf_get_name(buffer):
|
||||
return buffer.name
|
||||
|
||||
|
||||
def nvim_get_var(name):
|
||||
return vim.vars[name]
|
||||
|
||||
|
||||
def nvim_get_vvar(name):
|
||||
return vim.vvars[name]
|
||||
|
||||
|
||||
def nvim_set_var(name, val):
|
||||
vim.vars[name] = val
|
||||
return val
|
||||
|
||||
|
||||
def nvim_buf_get_var(buffer, name):
|
||||
return buffer.vars[name]
|
||||
|
||||
|
||||
def nvim_buf_set_var(buffer, name, val):
|
||||
buffer.vars[name] = val
|
||||
|
||||
|
||||
def nvim_buf_get_lines(buffer, start, end, *args):
|
||||
if start < 0:
|
||||
start = len(buffer) + 1 + start
|
||||
if end < 0:
|
||||
end = len(buffer) + 1 + end
|
||||
return buffer[start:end]
|
||||
|
||||
|
||||
def nvim_eval(expr):
|
||||
return nvim_call_function('eval', [expr])
|
||||
|
||||
|
||||
def nvim_buf_set_lines(buffer, start, end, err, lines):
|
||||
if start < 0:
|
||||
start = len(buffer) + 1 + start
|
||||
if end < 0:
|
||||
end = len(buffer) + 1 + end
|
||||
buffer[start:end] = lines
|
||||
|
||||
if nvim_call_function('bufwinnr', [buffer.number]) != -1:
|
||||
# vim needs' redraw to update the screen, it seems to be a bug
|
||||
vim.command('redraw')
|
||||
|
||||
|
||||
buffer_set_lines = nvim_buf_set_lines
|
||||
|
||||
|
||||
def buffer_line_count(buffer):
|
||||
return len(buffer)
|
||||
|
||||
|
||||
def nvim_buf_line_count(buffer):
|
||||
return len(buffer)
|
||||
|
||||
|
||||
def nvim_get_option(name):
|
||||
return vim.options[name]
|
||||
|
||||
|
||||
def nvim_buf_get_option(buf, name):
|
||||
return buf.options[name]
|
||||
|
||||
|
||||
def nvim_set_option(name, val):
|
||||
vim.options[name] = val
|
||||
|
||||
|
||||
def nvim_buf_set_option(buf, name, val):
|
||||
buf.options[name] = val
|
||||
|
||||
|
||||
def nvim_command(cmd):
|
||||
vim.command(cmd)
|
||||
|
||||
|
||||
def nvim_get_current_line():
|
||||
return vim.current.line
|
||||
|
||||
|
||||
def nvim_get_current_win():
|
||||
return vim.current.window
|
||||
|
||||
|
||||
def nvim_win_get_cursor(window):
|
||||
return window.cursor
|
||||
|
||||
|
||||
def nvim_win_get_buf(window):
|
||||
return window.buffer
|
||||
|
||||
|
||||
def nvim_win_get_width(window):
|
||||
return window.width
|
||||
|
||||
|
||||
def nvim_win_set_width(window, width):
|
||||
window.width = width
|
||||
|
||||
|
||||
def nvim_win_get_height(window):
|
||||
return window.height
|
||||
|
||||
|
||||
def nvim_win_set_height(window, height):
|
||||
window.height = height
|
||||
|
||||
|
||||
def nvim_win_get_var(window, name):
|
||||
return window.vars[name]
|
||||
|
||||
|
||||
def nvim_win_set_var(window, name, val):
|
||||
window.vars[name] = val
|
||||
|
||||
|
||||
def nvim_win_get_option(window, name):
|
||||
return window.options[name]
|
||||
|
||||
|
||||
def nvim_win_set_option(window, name, val):
|
||||
window.options[name] = val
|
||||
|
||||
|
||||
def nvim_win_get_position(window):
|
||||
return (window.row, window.col)
|
||||
|
||||
|
||||
def nvim_win_get_number(window):
|
||||
return window.number
|
||||
|
||||
|
||||
def nvim_win_is_valid(window):
|
||||
return window.valid
|
||||
|
||||
|
||||
def nvim_out_write(s):
|
||||
nvim_call_function('neovim_rpc#_nvim_out_write', [s])
|
||||
|
||||
|
||||
def nvim_err_write(s):
|
||||
nvim_call_function('neovim_rpc#_nvim_err_write', [s])
|
||||
|
||||
|
||||
def nvim_buf_add_highlight(buf, src_id, *args):
|
||||
# https://github.com/autozimu/LanguageClient-neovim/pull/151#issuecomment-339198527
|
||||
# FIXME
|
||||
return src_id
|
||||
|
||||
|
||||
def nvim_buf_clear_highlight(*args):
|
||||
# https://github.com/autozimu/LanguageClient-neovim/pull/151#issuecomment-339198527
|
||||
# FIXME
|
||||
pass
|
||||
|
||||
|
||||
def nvim_set_client_info(*args):
|
||||
# https://github.com/roxma/vim-hug-neovim-rpc/issues/61
|
||||
vim.vars['_neovim_rpc_client_info'] = args
|
||||
|
||||
|
||||
def nvim_get_client_info():
|
||||
if '_neovim_rpc_client_info' not in vim.vars:
|
||||
return []
|
||||
return vim.vars['_neovim_rpc_client_info']
|
69
bundle/vim-hug-neovim-rpc/pythonx/neovim_rpc_protocol.py
Normal file
69
bundle/vim-hug-neovim-rpc/pythonx/neovim_rpc_protocol.py
Normal file
@ -0,0 +1,69 @@
|
||||
import sys
|
||||
import vim
|
||||
import msgpack
|
||||
import neovim_rpc_server_api_info
|
||||
|
||||
BUFFER_TYPE = type(vim.current.buffer)
|
||||
BUFFER_TYPE_ID = neovim_rpc_server_api_info.API_INFO['types']['Buffer']['id']
|
||||
WINDOW_TYPE = type(vim.current.window)
|
||||
WINDOW_TYPE_ID = neovim_rpc_server_api_info.API_INFO['types']['Window']['id']
|
||||
|
||||
|
||||
def decode_if_bytes(obj):
|
||||
if isinstance(obj, bytes):
|
||||
return obj.decode("utf-8")
|
||||
return obj
|
||||
|
||||
|
||||
def walk(fn, obj):
|
||||
if type(obj) in [list, tuple, vim.List]:
|
||||
return list(walk(fn, o) for o in obj)
|
||||
if type(obj) in [dict, vim.Dictionary]:
|
||||
return dict((walk(fn, k), walk(fn, v)) for k, v in
|
||||
obj.items())
|
||||
return fn(obj)
|
||||
|
||||
|
||||
if vim.eval("has('patch-8.0.1280')"):
|
||||
def from_client(msg):
|
||||
def handler(obj):
|
||||
if type(obj) is msgpack.ExtType:
|
||||
if obj.code == BUFFER_TYPE_ID:
|
||||
return vim.buffers[msgpack.unpackb(obj.data)]
|
||||
if obj.code == WINDOW_TYPE_ID:
|
||||
return vim.windows[msgpack.unpackb(obj.data) - 1]
|
||||
if sys.version_info.major != 2:
|
||||
# python3 needs decode
|
||||
obj = decode_if_bytes(obj)
|
||||
return obj
|
||||
return walk(handler, msg)
|
||||
else:
|
||||
def from_client(msg):
|
||||
def handler(obj):
|
||||
if type(obj) is msgpack.ExtType:
|
||||
if obj.code == BUFFER_TYPE_ID:
|
||||
return vim.buffers[msgpack.unpackb(obj.data)]
|
||||
if obj.code == WINDOW_TYPE_ID:
|
||||
return vim.windows[msgpack.unpackb(obj.data) - 1]
|
||||
elif obj is None:
|
||||
return ''
|
||||
if sys.version_info.major != 2:
|
||||
# python3 needs decode
|
||||
obj = decode_if_bytes(obj)
|
||||
return obj
|
||||
return walk(handler, msg)
|
||||
|
||||
|
||||
def to_client(msg):
|
||||
def handler(obj):
|
||||
if type(obj) == BUFFER_TYPE:
|
||||
return msgpack.ExtType(BUFFER_TYPE_ID, msgpack.packb(obj.number))
|
||||
if type(obj) == WINDOW_TYPE:
|
||||
return msgpack.ExtType(WINDOW_TYPE_ID, msgpack.packb(obj.number))
|
||||
if type(obj) == vim.Function:
|
||||
try:
|
||||
return obj.name.encode()
|
||||
except Exception:
|
||||
return ""
|
||||
return obj
|
||||
return walk(handler, msg)
|
474
bundle/vim-hug-neovim-rpc/pythonx/neovim_rpc_server.py
Normal file
474
bundle/vim-hug-neovim-rpc/pythonx/neovim_rpc_server.py
Normal file
@ -0,0 +1,474 @@
|
||||
# vim:set et sw=4 ts=8:
|
||||
|
||||
import json
|
||||
import socket
|
||||
import sys
|
||||
import os
|
||||
import threading
|
||||
import vim
|
||||
import logging
|
||||
import msgpack
|
||||
import neovim_rpc_server_api_info
|
||||
import neovim_rpc_methods
|
||||
import neovim_rpc_protocol
|
||||
|
||||
vim_error = vim.Function('neovim_rpc#_error')
|
||||
vim_py = vim.eval('g:neovim_rpc#py')
|
||||
|
||||
|
||||
if sys.version_info.major == 2:
|
||||
from Queue import Queue, Empty as QueueEmpty
|
||||
else:
|
||||
from queue import Queue, Empty as QueueEmpty
|
||||
|
||||
# NVIM_PYTHON_LOG_FILE=nvim.log NVIM_PYTHON_LOG_LEVEL=INFO vim test.md
|
||||
|
||||
try:
|
||||
# Python 3
|
||||
import socketserver
|
||||
except ImportError:
|
||||
# Python 2
|
||||
import SocketServer as socketserver
|
||||
|
||||
# globals
|
||||
logger = logging.getLogger(__name__)
|
||||
# supress the annoying error message:
|
||||
# No handlers could be found for logger "neovim_rpc_server"
|
||||
logger.addHandler(logging.NullHandler())
|
||||
|
||||
request_queue = Queue()
|
||||
responses = {}
|
||||
|
||||
|
||||
def _channel_id_new():
|
||||
with _channel_id_new._lock:
|
||||
_channel_id_new._counter += 1
|
||||
return _channel_id_new._counter
|
||||
|
||||
|
||||
# static local
|
||||
_channel_id_new._counter = 0
|
||||
_channel_id_new._lock = threading.Lock()
|
||||
|
||||
|
||||
class VimHandler(socketserver.BaseRequestHandler):
|
||||
|
||||
_lock = threading.Lock()
|
||||
_sock = None
|
||||
|
||||
@classmethod
|
||||
def notify(cls, cmd=None):
|
||||
try:
|
||||
if cmd is None:
|
||||
cmd = vim_py + " neovim_rpc_server.process_pending_requests()"
|
||||
if not VimHandler._sock:
|
||||
return
|
||||
with VimHandler._lock:
|
||||
encoded = json.dumps(['ex', cmd])
|
||||
logger.info("sending notification: %s", encoded)
|
||||
VimHandler._sock.send(encoded.encode('utf-8'))
|
||||
except Exception as ex:
|
||||
logger.exception(
|
||||
'VimHandler notify exception for [%s]: %s', cmd, ex)
|
||||
|
||||
@classmethod
|
||||
def notify_exited(cls, channel):
|
||||
try:
|
||||
cls.notify("call neovim_rpc#_on_exit(%s)" % channel)
|
||||
except Exception as ex:
|
||||
logger.exception(
|
||||
'notify_exited for channel [%s] exception: %s', channel, ex)
|
||||
|
||||
# each connection is a thread
|
||||
def handle(self):
|
||||
logger.info("=== socket opened ===")
|
||||
data = None
|
||||
while True:
|
||||
try:
|
||||
rcv = self.request.recv(4096)
|
||||
# 16k buffer by default
|
||||
if data:
|
||||
data += rcv
|
||||
else:
|
||||
data = rcv
|
||||
except socket.error:
|
||||
logger.info("=== socket error ===")
|
||||
break
|
||||
except IOError:
|
||||
logger.info("=== socket closed ===")
|
||||
break
|
||||
if len(rcv) == 0:
|
||||
logger.info("=== socket closed ===")
|
||||
break
|
||||
logger.info("received: %s", data)
|
||||
try:
|
||||
decoded = json.loads(data.decode('utf-8'))
|
||||
except ValueError:
|
||||
logger.exception("json decoding failed, wait for more data")
|
||||
continue
|
||||
data = None
|
||||
|
||||
# Send a response if the sequence number is positive.
|
||||
# Negative numbers are used for "eval" responses.
|
||||
if (len(decoded) >= 2 and decoded[0] >= 0 and
|
||||
decoded[1] == 'neovim_rpc_setup'):
|
||||
|
||||
VimHandler._sock = self.request
|
||||
|
||||
# initial setup
|
||||
encoded = json.dumps(['ex', "call neovim_rpc#_callback()"])
|
||||
logger.info("sending {0}".format(encoded))
|
||||
self.request.send(encoded.encode('utf-8'))
|
||||
|
||||
else:
|
||||
# recognize as rpcrequest
|
||||
reqid = decoded[0]
|
||||
channel = decoded[1][1]
|
||||
event = decoded[1][2]
|
||||
args = decoded[1][3]
|
||||
rspid = decoded[1][4]
|
||||
NvimHandler.request(self.request,
|
||||
channel,
|
||||
reqid,
|
||||
event,
|
||||
args,
|
||||
rspid)
|
||||
|
||||
|
||||
class SocketToStream():
|
||||
|
||||
def __init__(self, sock):
|
||||
self._sock = sock
|
||||
|
||||
def read(self, cnt):
|
||||
if cnt > 4096:
|
||||
cnt = 4096
|
||||
return self._sock.recv(cnt)
|
||||
|
||||
def write(self, w):
|
||||
return self._sock.send(w)
|
||||
|
||||
|
||||
class NvimHandler(socketserver.BaseRequestHandler):
|
||||
|
||||
channel_sockets = {}
|
||||
|
||||
def handle(self):
|
||||
|
||||
logger.info("=== socket opened for client ===")
|
||||
|
||||
channel = _channel_id_new()
|
||||
|
||||
sock = self.request
|
||||
chinfo = dict(sock=sock)
|
||||
NvimHandler.channel_sockets[channel] = chinfo
|
||||
|
||||
try:
|
||||
f = SocketToStream(sock)
|
||||
unpacker = msgpack.Unpacker(f)
|
||||
for unpacked in unpacker:
|
||||
logger.info("unpacked: %s", unpacked)
|
||||
|
||||
# response format:
|
||||
# - msg[0]: 1
|
||||
# - msg[1]: the request id
|
||||
# - msg[2]: error(if any), format: [code,str]
|
||||
# - msg[3]: result(if not errored)
|
||||
if int(unpacked[0]) == 1:
|
||||
unpacked = neovim_rpc_protocol.from_client(unpacked)
|
||||
reqid = int(unpacked[1])
|
||||
rspid, vimsock = chinfo[reqid]
|
||||
err = unpacked[2]
|
||||
result = unpacked[3]
|
||||
# VIM fails to parse response when there a sleep in neovim
|
||||
# client. I cannot figure out why. Use global responses to
|
||||
# workaround this issue.
|
||||
responses[rspid] = [err, result]
|
||||
content = [reqid, '']
|
||||
tosend = json.dumps(content)
|
||||
# vimsock.send
|
||||
vimsock.send(tosend.encode('utf-8'))
|
||||
chinfo.pop(reqid)
|
||||
continue
|
||||
|
||||
request_queue.put((f, channel, unpacked))
|
||||
# notify vim in order to process request in main thread, and
|
||||
# avoiding the stupid json protocol
|
||||
VimHandler.notify()
|
||||
|
||||
logger.info('channel %s closed.', channel)
|
||||
|
||||
except Exception:
|
||||
logger.exception('unpacker failed.')
|
||||
finally:
|
||||
try:
|
||||
NvimHandler.channel_sockets.pop(channel)
|
||||
sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def notify(cls, channel, event, args):
|
||||
try:
|
||||
channel = int(channel)
|
||||
if channel not in cls.channel_sockets:
|
||||
logger.info("channel[%s] not in NvimHandler", channel)
|
||||
return
|
||||
sock = cls.channel_sockets[channel]['sock']
|
||||
|
||||
# notification format:
|
||||
# - msg[0] type, which is 2
|
||||
# - msg[1] method
|
||||
# - msg[2] arguments
|
||||
content = [2, event, args]
|
||||
|
||||
logger.info("notify channel[%s]: %s", channel, content)
|
||||
packed = msgpack.packb(neovim_rpc_protocol.to_client(content))
|
||||
sock.send(packed)
|
||||
except Exception as ex:
|
||||
logger.exception("notify failed: %s", ex)
|
||||
|
||||
@classmethod
|
||||
def request(cls, vimsock, channel, reqid, event, args, rspid):
|
||||
try:
|
||||
reqid = int(reqid)
|
||||
channel = int(channel)
|
||||
chinfo = cls.channel_sockets[channel]
|
||||
|
||||
if channel not in cls.channel_sockets:
|
||||
logger.info("channel[%s] not in NvimHandler", channel)
|
||||
return
|
||||
|
||||
sock = chinfo['sock']
|
||||
# request format:
|
||||
# - msg[0] type, which is 0
|
||||
# - msg[1] request id
|
||||
# - msg[2] method
|
||||
# - msg[3] arguments
|
||||
content = [0, reqid, event, args]
|
||||
|
||||
chinfo[reqid] = [rspid, vimsock]
|
||||
|
||||
logger.info("request channel[%s]: %s", channel, content)
|
||||
packed = msgpack.packb(neovim_rpc_protocol.to_client(content))
|
||||
sock.send(packed)
|
||||
except Exception as ex:
|
||||
logger.exception("request failed: %s", ex)
|
||||
|
||||
@classmethod
|
||||
def shutdown(cls):
|
||||
# close all sockets
|
||||
for channel in list(cls.channel_sockets.keys()):
|
||||
chinfo = cls.channel_sockets.get(channel, None)
|
||||
if chinfo:
|
||||
sock = chinfo['sock']
|
||||
logger.info("closing client %s", channel)
|
||||
# if don't shutdown the socket, vim will never exit because the
|
||||
# recv thread is still blocking
|
||||
sock.shutdown(socket.SHUT_RDWR)
|
||||
sock.close()
|
||||
|
||||
|
||||
# copied from neovim python-client/neovim/__init__.py
|
||||
def _setup_logging(name):
|
||||
"""Setup logging according to environment variables."""
|
||||
logger = logging.getLogger(__name__)
|
||||
if 'NVIM_PYTHON_LOG_FILE' in os.environ:
|
||||
prefix = os.environ['NVIM_PYTHON_LOG_FILE'].strip()
|
||||
major_version = sys.version_info[0]
|
||||
logfile = '{}_py{}_{}'.format(prefix, major_version, name)
|
||||
handler = logging.FileHandler(logfile, 'w', encoding='utf-8')
|
||||
handler.formatter = logging.Formatter(
|
||||
'%(asctime)s [%(levelname)s @ '
|
||||
'%(filename)s:%(funcName)s:%(lineno)s] %(process)s - %(message)s')
|
||||
logging.root.addHandler(handler)
|
||||
level = logging.INFO
|
||||
if 'NVIM_PYTHON_LOG_LEVEL' in os.environ:
|
||||
lv = getattr(logging,
|
||||
os.environ['NVIM_PYTHON_LOG_LEVEL'].strip(),
|
||||
level)
|
||||
if isinstance(lv, int):
|
||||
level = lv
|
||||
logger.setLevel(level)
|
||||
|
||||
|
||||
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
pass
|
||||
|
||||
|
||||
if sys.platform in ['linux', 'darwin']:
|
||||
class ThreadedUnixServer(socketserver.ThreadingMixIn,
|
||||
socketserver.UnixStreamServer):
|
||||
pass
|
||||
has_unix = True
|
||||
else:
|
||||
has_unix = False
|
||||
|
||||
|
||||
def start():
|
||||
|
||||
_setup_logging('neovim_rpc_server')
|
||||
|
||||
# 0 for random port
|
||||
global _vim_server
|
||||
global _nvim_server
|
||||
|
||||
_vim_server = ThreadedTCPServer(("127.0.0.1", 0), VimHandler)
|
||||
_vim_server.daemon_threads = True
|
||||
vim_server_addr = "{addr[0]}:{addr[1]}".format(
|
||||
addr=_vim_server.server_address)
|
||||
|
||||
if 'NVIM_LISTEN_ADDRESS' in os.environ:
|
||||
nvim_server_addr = os.environ['NVIM_LISTEN_ADDRESS']
|
||||
if nvim_server_addr.find(':') >= 0:
|
||||
host, port = nvim_server_addr.split(':')
|
||||
port = int(port)
|
||||
_nvim_server = ThreadedTCPServer((host, port), NvimHandler)
|
||||
elif has_unix:
|
||||
if os.path.exists(nvim_server_addr):
|
||||
try:
|
||||
os.unlink(nvim_server_addr)
|
||||
except Exception:
|
||||
pass
|
||||
_nvim_server = ThreadedUnixServer(nvim_server_addr, NvimHandler)
|
||||
else:
|
||||
# FIXME named pipe server ?
|
||||
_nvim_server = ThreadedTCPServer(("127.0.0.1", 0), NvimHandler)
|
||||
nvim_server_addr = "{addr[0]}:{addr[1]}".format(
|
||||
addr=_nvim_server.server_address)
|
||||
elif not has_unix:
|
||||
_nvim_server = ThreadedTCPServer(("127.0.0.1", 0), NvimHandler)
|
||||
nvim_server_addr = "{addr[0]}:{addr[1]}".format(
|
||||
addr=_nvim_server.server_address)
|
||||
else:
|
||||
nvim_server_addr = vim.eval('tempname()')
|
||||
_nvim_server = ThreadedUnixServer(nvim_server_addr, NvimHandler)
|
||||
_nvim_server.daemon_threads = True
|
||||
|
||||
# Start a thread with the server -- that thread will then start one
|
||||
# more thread for each request
|
||||
main_server_thread = threading.Thread(target=_vim_server.serve_forever)
|
||||
clients_server_thread = threading.Thread(target=_nvim_server.serve_forever)
|
||||
|
||||
# Exit the server thread when the main thread terminates
|
||||
main_server_thread.daemon = True
|
||||
main_server_thread.start()
|
||||
clients_server_thread.daemon = True
|
||||
clients_server_thread.start()
|
||||
|
||||
return [nvim_server_addr, vim_server_addr]
|
||||
|
||||
|
||||
def process_pending_requests():
|
||||
|
||||
logger.info("process_pending_requests")
|
||||
while True:
|
||||
|
||||
item = None
|
||||
try:
|
||||
|
||||
item = request_queue.get(False)
|
||||
|
||||
f, channel, msg = item
|
||||
|
||||
msg = neovim_rpc_protocol.from_client(msg)
|
||||
|
||||
logger.info("get msg from channel [%s]: %s", channel, msg)
|
||||
|
||||
# request format:
|
||||
# - msg[0] type, which is 0
|
||||
# - msg[1] request id
|
||||
# - msg[2] method
|
||||
# - msg[3] arguments
|
||||
|
||||
# notification format:
|
||||
# - msg[0] type, which is 2
|
||||
# - msg[1] method
|
||||
# - msg[2] arguments
|
||||
|
||||
if msg[0] == 0:
|
||||
# request
|
||||
|
||||
req_typed, req_id, method, args = msg
|
||||
|
||||
try:
|
||||
err = None
|
||||
result = _process_request(channel, method, args)
|
||||
except Exception as ex:
|
||||
logger.exception("process failed: %s", ex)
|
||||
# error uccor
|
||||
err = [1, str(ex)]
|
||||
result = None
|
||||
|
||||
result = [1, req_id, err, result]
|
||||
logger.info("sending result: %s", result)
|
||||
packed = msgpack.packb(neovim_rpc_protocol.to_client(result))
|
||||
f.write(packed)
|
||||
logger.info("sent")
|
||||
if msg[0] == 2:
|
||||
# notification
|
||||
req_typed, method, args = msg
|
||||
try:
|
||||
result = _process_request(channel, method, args)
|
||||
logger.info('notification process result: [%s]', result)
|
||||
except Exception as ex:
|
||||
logger.exception("process failed: %s", ex)
|
||||
|
||||
except QueueEmpty:
|
||||
pass
|
||||
except Exception as ex:
|
||||
logger.exception("exception during process: %s", ex)
|
||||
finally:
|
||||
if item:
|
||||
request_queue.task_done()
|
||||
else:
|
||||
# item==None means the queue is empty
|
||||
break
|
||||
|
||||
|
||||
def _process_request(channel, method, args):
|
||||
if hasattr(neovim_rpc_methods, method):
|
||||
return getattr(neovim_rpc_methods, method)(*args)
|
||||
elif method in ['vim_get_api_info', 'nvim_get_api_info']:
|
||||
# the first request sent by neovim python client
|
||||
return [channel, neovim_rpc_server_api_info.API_INFO]
|
||||
else:
|
||||
logger.error("method %s not implemented", method)
|
||||
vim_error(
|
||||
"rpc method [%s] not implemented in "
|
||||
"pythonx/neovim_rpc_methods.py. "
|
||||
"Please send PR or contact the mantainer." % method)
|
||||
raise Exception('%s not implemented' % method)
|
||||
|
||||
|
||||
def rpcnotify(channel, method, args):
|
||||
NvimHandler.notify(channel, method, args)
|
||||
|
||||
|
||||
def stop():
|
||||
|
||||
logger.info("stop begin")
|
||||
|
||||
# close tcp channel server
|
||||
_nvim_server.shutdown()
|
||||
_nvim_server.server_close()
|
||||
|
||||
# close the main channel
|
||||
try:
|
||||
vim.command('call ch_close(g:_neovim_rpc_main_channel)')
|
||||
except Exception as ex:
|
||||
logger.info("ch_close failed: %s", ex)
|
||||
|
||||
# remove all sockets
|
||||
NvimHandler.shutdown()
|
||||
|
||||
try:
|
||||
# stop the main channel
|
||||
_vim_server.shutdown()
|
||||
except Exception as ex:
|
||||
logger.info("_vim_server shutodwn failed: %s", ex)
|
||||
|
||||
try:
|
||||
_vim_server.server_close()
|
||||
except Exception as ex:
|
||||
logger.info("_vim_server close failed: %s", ex)
|
2372
bundle/vim-hug-neovim-rpc/pythonx/neovim_rpc_server_api_info.py
Normal file
2372
bundle/vim-hug-neovim-rpc/pythonx/neovim_rpc_server_api_info.py
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user