diff --git a/README.md b/README.md index fcab7d3a..affb05a7 100644 --- a/README.md +++ b/README.md @@ -15,13 +15,8 @@ To view the current options, please consult the ## Installation **Note:** deoplete requires Neovim(latest is recommended) or Vim8 with Python3 and -timers(neovim ver.0.1.5+) enabled. See [requirements](#requirements) if you -aren't sure whether you have this. - -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` +timers enabled. See [requirements](#requirements) if you aren't sure whether +you have this. For vim-plug @@ -34,6 +29,7 @@ else Plug 'roxma/nvim-yarp' Plug 'roxma/vim-hug-neovim-rpc' endif +let g:deoplete#enable_at_startup = 1 ``` For dein.vim @@ -44,13 +40,22 @@ 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. +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: @@ -76,6 +81,7 @@ auto-completion. let g:deoplete#enable_at_startup = 1 ``` + ## Sources deoplete will display completions via `complete()` by default. @@ -122,5 +128,4 @@ https://www.youtube.com/watch?v=oanoPTpiSF4 [Register/Extract list completions](https://camo.githubusercontent.com/6a6df993ad0e05c014c72c8f8702447f9b34ad90/68747470733a2f2f692e696d6775722e636f6d2f5131663731744a2e676966) -### deoplete-fsharp sample ( Enjoy! ) ![FSharp completion using deopletefs](https://github.com/callmekohei/deoplete-fsharp/blob/master/pic/sample.gif) diff --git a/autoload/deoplete/handler.vim b/autoload/deoplete/handler.vim index 1db870a8..9553a5b8 100644 --- a/autoload/deoplete/handler.vim +++ b/autoload/deoplete/handler.vim @@ -51,15 +51,16 @@ function! s:do_complete(timer) abort return endif + let prev = g:deoplete#_prev_completion if context.event !=# 'Manual' - \ && s:prev_completion.complete_position == getpos('.') - \ && s:prev_completion.candidates ==# context.candidates + \ && prev.complete_position == getpos('.') + \ && prev.candidates ==# context.candidates return endif - let s:prev_completion.event = context.event - let s:prev_completion.candidates = context.candidates - let s:prev_completion.complete_position = getpos('.') + let prev.event = context.event + let prev.candidates = context.candidates + let prev.complete_position = getpos('.') if context.event ==# 'Manual' let context.event = '' @@ -83,11 +84,13 @@ function! deoplete#handler#_completion_timer_start() abort call s:completion_timer_stop() endif - let delay = max([50, g:deoplete#auto_complete_delay]) + let delay = max([20, g:deoplete#auto_complete_delay]) let s:completion_timer = timer_start(delay, function('s:do_complete')) - let s:prev_completion = { - \ 'complete_position': [], 'candidates': [], 'event': '' + let g:deoplete#_prev_completion = { + \ 'complete_position': [], + \ 'candidates': [], + \ 'event': '', \ } endfunction function! s:completion_timer_stop() abort @@ -106,7 +109,7 @@ function! deoplete#handler#_async_timer_start() abort let s:async_timer = { 'event': 'Async', 'changedtick': b:changedtick } let s:async_timer.id = timer_start( - \ max([50, g:deoplete#auto_refresh_delay]), + \ max([20, g:deoplete#auto_refresh_delay]), \ function('s:completion_async'), {'repeat': -1}) endfunction function! deoplete#handler#_async_timer_stop() abort @@ -132,7 +135,7 @@ function! s:completion_begin(event) abort return endif - if exists('s:prev_completion') && s:prev_completion.event !=# 'Manual' + if g:deoplete#_prev_completion.event !=# 'Manual' " Call omni completion for filetype in context.filetypes for pattern in deoplete#util#convert2list( diff --git a/autoload/deoplete/init.vim b/autoload/deoplete/init.vim index 80d02f63..43bb9e01 100644 --- a/autoload/deoplete/init.vim +++ b/autoload/deoplete/init.vim @@ -4,6 +4,9 @@ " License: MIT license "============================================================================= +let s:dp_main = fnamemodify(expand(''), ':h:h:h') + \ . '/rplugin/python3/deoplete/dp_main.py' + if !exists('s:is_enabled') let s:is_enabled = 0 endif @@ -100,6 +103,11 @@ function! deoplete#init#_disable() abort endfunction function! deoplete#init#_variables() abort + let g:deoplete#_prev_completion = { + \ 'complete_position': [], + \ 'candidates': [], + \ 'event': '', + \ } let g:deoplete#_context = {} let g:deoplete#_rank = {} if !exists('g:deoplete#_logging') @@ -135,7 +143,7 @@ function! deoplete#init#_variables() abort call deoplete#util#set_default( \ 'g:deoplete#auto_complete_delay', 50) call deoplete#util#set_default( - \ 'g:deoplete#auto_refresh_delay', 500) + \ 'g:deoplete#auto_refresh_delay', 50) call deoplete#util#set_default( \ 'g:deoplete#max_abbr_width', 80) call deoplete#util#set_default( @@ -144,6 +152,8 @@ function! deoplete#init#_variables() abort \ 'g:deoplete#skip_chars', []) call deoplete#util#set_default( \ 'g:deoplete#complete_method', 'complete') + call deoplete#util#set_default( + \ 'g:deoplete#max_processes', 4) call deoplete#util#set_default( \ 'g:deoplete#keyword_patterns', {}) @@ -221,6 +231,10 @@ function! deoplete#init#_context(event, sources) abort return { \ 'changedtick': b:changedtick, + \ 'serveraddr': (has('nvim') ? + \ v:servername : neovim_rpc#serveraddr()), + \ 'python3': get(g:, 'python3_host_prog', 'python3'), + \ 'dp_main': s:dp_main, \ 'event': event, \ 'input': input, \ 'is_windows': ((has('win32') || has('win64')) ? v:true : v:false), diff --git a/autoload/deoplete/util.vim b/autoload/deoplete/util.vim index 5cb8fddf..02fe2a99 100644 --- a/autoload/deoplete/util.vim +++ b/autoload/deoplete/util.vim @@ -218,6 +218,11 @@ function! deoplete#util#rpcnotify(event, context) abort return '' endif call s:notify(a:event, a:context) + if has('nvim') && a:event ==# 'deoplete_on_event' && a:context['event'] ==# 'VimLeavePre' + while !exists('g:deoplete#_process_stopped') + sleep 50m + endwhile + endif return '' endfunction diff --git a/doc/deoplete.txt b/doc/deoplete.txt index 1c748c68..50e9cda6 100644 --- a/doc/deoplete.txt +++ b/doc/deoplete.txt @@ -195,6 +195,14 @@ g:deoplete#max_list Default value: 100 + *g:deoplete#max_processes* +g:deoplete#max_processes + The max number of processes used for the asynchronous + completion. + If it is less than equal 1, this feature is disabled. + + Default value: 4 + *g:deoplete#max_abbr_width* g:deoplete#max_abbr_width If the candidate abbr length exceeds the length it will be cut @@ -222,7 +230,7 @@ g:deoplete#auto_complete_delay g:deoplete#auto_refresh_delay Delay the refresh when asynchronous. - Default value: 500 + Default value: 50 *g:deoplete#skip_chars* g:deoplete#skip_chars @@ -1281,9 +1289,9 @@ phpcd.vim: another PHP omnifunc. Faster. https://github.com/php-vim/phpcd.vim ============================================================================== -FREQUENTLY ASKED QUESTIONS (FAQ) *deoplete-faq* +FREQUENTLY ASKED QUESTIONS (FAQ) *deoplete-faq* - *deoplete-faq-trouble* + *deoplete-faq-trouble* 1. Troubleshooting~ Q: deoplete does not work. @@ -1370,7 +1378,7 @@ A: Please enable logging feature like this. > call deoplete#enable_logging('DEBUG', 'deoplete.log') call deoplete#custom#source('jedi', 'is_debug_enabled', 1) < - *deoplete-faq-config* + *deoplete-faq-config* 2. Configuration~ Q: I want to silence the |ins-completion-menu| messages in the command line @@ -1515,7 +1523,7 @@ Q: What does each part of a line on the pop up mean? For example I see: A: It is the source mark. [~] is from around source. A is from |deoplete-source-around|. - *deoplete-faq-ft-specific* + *deoplete-faq-ft-specific* 3. Filetype Specific Questions~ Q: I want to disable the auto completion for certain filetypes. @@ -1579,7 +1587,7 @@ Q: I want to complete AAA using deoplete. A: You can create the source for it. Why don't create the source if you need? - *deoplete-faq-general* + *deoplete-faq-general* 4. General Questions~ Q: How to donate money to you? diff --git a/rplugin/python3/deoplete/__init__.py b/rplugin/python3/deoplete/__init__.py index 0e5f4100..7f21f03b 100644 --- a/rplugin/python3/deoplete/__init__.py +++ b/rplugin/python3/deoplete/__init__.py @@ -28,7 +28,7 @@ def init_channel(self, args): self._deoplete = Deoplete(self._vim) @neovim.rpc_export('deoplete_enable_logging') - def enable_logging(self): + def enable_logging(self, context): self._deoplete.enable_logging() @neovim.rpc_export('deoplete_auto_completion_begin') @@ -51,7 +51,7 @@ def on_event(self, context): def deoplete_init(): pass - def deoplete_enable_logging(): + def deoplete_enable_logging(context): global_deoplete.enable_logging() def deoplete_auto_completion_begin(context): diff --git a/rplugin/python3/deoplete/child.py b/rplugin/python3/deoplete/child.py new file mode 100644 index 00000000..4246d508 --- /dev/null +++ b/rplugin/python3/deoplete/child.py @@ -0,0 +1,458 @@ +# ============================================================================ +# FILE: child.py +# AUTHOR: Shougo Matsushita +# License: MIT license +# ============================================================================ + +import copy +import os.path +import re +import sys +import time +import msgpack + +from collections import defaultdict + +import deoplete.source # noqa +import deoplete.filter # noqa + +from deoplete import logger +from deoplete.exceptions import SourceInitError +from deoplete.util import (bytepos2charpos, charpos2bytepos, error, error_tb, + import_plugin, + get_buffer_config, get_custom, + get_syn_names, convert2candidates) + + +class Child(logger.LoggingMixin): + + def __init__(self, vim): + self.name = 'child' + + self._vim = vim + self._filters = {} + self._sources = {} + self._custom = [] + self._profile_flag = None + self._profile_start_time = 0 + self._loaded_sources = {} + self._loaded_filters = {} + self._source_errors = defaultdict(int) + self._filter_errors = defaultdict(int) + self._prev_results = {} + self._unpacker = msgpack.Unpacker( + encoding='utf-8', + unicode_errors='surrogateescape') + self._packer = msgpack.Packer( + use_bin_type=True, + unicode_errors='surrogateescape') + + def main(self): + for child_in in self._read(): + self.debug('main_loop: begin') + name = child_in['name'] + args = child_in['args'] + queue_id = child_in['queue_id'] + self.debug('main_loop: %s', name) + + 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 == 'set_custom': + self._set_custom(args[0]) + elif name == 'on_event': + self._on_event(args[0]) + elif name == 'merge_results': + self._write(self._merge_results(args[0], queue_id)) + + def _read(self): + self._unpacker.feed(sys.stdin.buffer.read(1)) + return self._unpacker + + def _write(self, expr): + sys.stdout.buffer.write(self._packer.pack(expr)) + sys.stdout.flush() + + def _enable_logging(self): + logging = self._vim.vars['deoplete#_logging'] + logger.setup(self._vim, logging['level'], logging['logfile']) + self.is_debug_enabled = True + + def _add_source(self, path): + 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('Loaded Source: %s (%s)', source.name, path) + + def _add_filter(self, path): + 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('Loaded Filter: %s (%s)', f.name, path) + + def _merge_results(self, context, queue_id): + results = self._gather_results(context) + + merged_results = [] + for result in [x for x in results + if not self._is_skip(x['context'], x['source'])]: + source_result = self._source_result(result, context['input']) + if source_result: + rank = get_custom(self._custom, + result['source'].name, 'rank', + result['source'].rank) + merged_results.append({ + 'complete_position': source_result['complete_position'], + 'mark': result['source'].mark, + 'dup': bool(result['source'].filetypes), + 'candidates': result['candidates'], + 'source_name': result['source'].name, + 'rank': rank, + }) + + is_async = len([x for x in results if x['context']['is_async']]) > 0 + + return { + 'queue_id': queue_id, + 'is_async': is_async, + 'merged_results': merged_results, + } + + def _gather_results(self, context): + results = [] + + for source in [x[1] for x in self._itersource(context)]: + try: + 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 + continue + + if (source.name in self._prev_results and + self._use_previous_result( + context, self._prev_results[source.name], + source.is_volatile)): + results.append(self._prev_results[source.name]) + continue + + 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_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_menu_width'] > 0: + ctx['max_menu_width'] = max(10, ctx['max_menu_width']) + + # Gathering + self._profile_start(ctx, source.name) + ctx['candidates'] = source.gather_candidates(ctx) + self._profile_end(source.name) + + if ctx['candidates'] is None: + continue + + ctx['candidates'] = convert2candidates(ctx['candidates']) + + result = { + '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'], + } + self._prev_results[source.name] = result + results.append(result) + except Exception: + self._source_errors[source.name] += 1 + if source.is_silent: + continue + if self._source_errors[source.name] > 2: + error(self._vim, 'Too many errors from "%s". ' + 'This source is disabled until Neovim ' + 'is restarted.' % source.name) + self._sources.pop(source.name) + continue + error_tb(self._vim, 'Errors from: %s' % source.name) + + return results + + def _gather_async_results(self, result, source): + try: + context = result['context'] + context['is_refresh'] = False + async_candidates = source.gather_candidates(context) + result['is_async'] = context['is_async'] + if async_candidates is None: + return + context['candidates'] += convert2candidates(async_candidates) + except Exception: + self._source_errors[source.name] += 1 + if source.is_silent: + return + if self._source_errors[source.name] > 2: + error(self._vim, 'Too many errors from "%s". ' + 'This source is disabled until Neovim ' + 'is restarted.' % source.name) + self._sources.pop(source.name) + else: + error_tb(self._vim, 'Errors from: %s' % source.name) + + def _process_filter(self, f, context): + try: + self._profile_start(context, f.name) + if (isinstance(context['candidates'], dict) and + 'sorted_candidates' in context['candidates']): + context_candidates = [] + context['is_sorted'] = True + for candidates in context['candidates']['sorted_candidates']: + context['candidates'] = candidates + context_candidates += f.filter(context) + context['candidates'] = context_candidates + else: + context['candidates'] = f.filter(context) + self._profile_end(f.name) + except Exception: + self._filter_errors[f.name] += 1 + if self._source_errors[f.name] > 2: + error(self._vim, 'Too many errors from "%s". ' + 'This filter is disabled until Neovim ' + 'is restarted.' % f.name) + self._filters.pop(f.name) + return + error_tb(self._vim, 'Errors from: %s' % f) + + def _source_result(self, result, context_input): + 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.deepcopy(result['context']) + + ctx['input'] = context_input + ctx['complete_str'] = context_input[ctx['char_position']:] + ctx['is_sorted'] = False + + # Set ignorecase + case = ctx['smartcase'] or ctx['camelcase'] + if case and re.search(r'[A-Z]', ctx['complete_str']): + ctx['ignorecase'] = 0 + ignorecase = ctx['ignorecase'] + + # Filtering + for f in [self._filters[x] for x + in source.matchers + source.sorters + source.converters + if x in self._filters]: + self._process_filter(f, ctx) + + ctx['ignorecase'] = ignorecase + + # On post filter + if hasattr(source, 'on_post_filter'): + ctx['candidates'] = source.on_post_filter(ctx) + + result['candidates'] = ctx['candidates'] + return result if result['candidates'] else None + + def _itersource(self, context): + filetypes = context['filetypes'] + ignore_sources = set() + for ft in filetypes: + ignore_sources.update( + get_buffer_config(context, ft, + 'deoplete_ignore_sources', + 'deoplete#ignore_sources', + {})) + + for source_name, source in self._sources.items(): + if source.limit > 0 and context['bufsize'] > source.limit: + continue + 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: %s', source.name) + try: + source.on_init(context) + except Exception as exc: + if isinstance(exc, SourceInitError): + error(self._vim, + 'Error when loading source {}: {}. ' + 'Ignoring.'.format(source_name, exc)) + else: + error_tb(self._vim, + 'Error when loading source {}: {}. ' + 'Ignoring.'.format(source_name, exc)) + self._sources.pop(source_name) + continue + else: + source.is_initialized = True + yield source_name, source + + def _profile_start(self, context, name): + if self._profile_flag is 0 or not self.is_debug_enabled: + return + + if not self._profile_flag: + self._profile_flag = context['vars']['deoplete#enable_profile'] + if self._profile_flag: + return self._profile_start(context, name) + elif self._profile_flag: + self.debug('Profile Start: {0}'.format(name)) + self._profile_start_time = time.clock() + + def _profile_end(self, name): + if self._profile_start_time: + self.debug('Profile End : {0:<25} time={1:2.10f}'.format( + name, time.clock() - self._profile_start_time)) + + def _use_previous_result(self, context, result, is_volatile): + if context['position'][1] != result['prev_linenr']: + return False + if is_volatile: + return context['input'] == result['prev_input'] + else: + return (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, source): + if 'syntax_names' in context and source.disabled_syntaxes: + p = re.compile('(' + '|'.join(source.disabled_syntaxes) + ')$') + if next(filter(p.search, context['syntax_names']), None): + return True + if (source.input_pattern != '' and + re.search('(' + source.input_pattern + ')$', + context['input'])): + return False + if context['event'] == 'Manual': + return False + return not (source.min_pattern_length <= + len(context['complete_str']) <= source.max_pattern_length) + + def _set_source_attributes(self, context): + """Set source attributes from the context. + + Each item in `attrs` is the attribute name. If the default value is in + context['vars'] under a different name, use a tuple. + """ + attrs = ( + 'filetypes', + 'disabled_syntaxes', + 'input_pattern', + ('min_pattern_length', 'deoplete#auto_complete_start_length'), + 'max_pattern_length', + ('max_abbr_width', 'deoplete#max_abbr_width'), + ('max_kind_width', 'deoplete#max_menu_width'), + ('max_menu_width', 'deoplete#max_menu_width'), + 'matchers', + 'sorters', + 'converters', + 'mark', + 'is_debug_enabled', + 'is_silent', + ) + + for name, source in self._sources.items(): + for attr in attrs: + if isinstance(attr, tuple): + default_val = context['vars'][attr[1]] + attr = attr[0] + else: + default_val = None + source_attr = getattr(source, attr, default_val) + setattr(source, attr, get_custom(context['custom'], + name, attr, source_attr)) + + def _set_custom(self, custom): + self._custom = custom + + def _on_event(self, context): + for source_name, source in self._itersource(context): + if hasattr(source, 'on_event'): + self.debug('on_event: Source: %s', source_name) + try: + source.on_event(context) + except Exception as exc: + error_tb(self._vim, 'Exception during {}.on_event ' + 'for event {!r}: {}'.format( + source_name, context['event'], exc)) diff --git a/rplugin/python3/deoplete/deoplete.py b/rplugin/python3/deoplete/deoplete.py index ab583122..a56cb144 100644 --- a/rplugin/python3/deoplete/deoplete.py +++ b/rplugin/python3/deoplete/deoplete.py @@ -3,39 +3,36 @@ # AUTHOR: Shougo Matsushita # License: MIT license # ============================================================================ -import re -import copy -import time -import os.path - -from collections import defaultdict - -import deoplete.util # noqa -import deoplete.filter # noqa -import deoplete.source # noqa from deoplete import logger -from deoplete.exceptions import SourceInitError -from deoplete.util import (bytepos2charpos, charpos2bytepos, error, error_tb, - find_rplugins, get_buffer_config, get_custom, - get_syn_names, import_plugin, convert2candidates) +from deoplete.parent import Parent +from deoplete.util import (error_tb, find_rplugins) +# from deoplete.util import error class Deoplete(logger.LoggingMixin): def __init__(self, vim): + self.name = 'core' + self._vim = vim - self._filters = {} - self._sources = {} self._runtimepath = '' self._custom = [] - self._profile_flag = None - self._profile_start = 0 - self._source_errors = defaultdict(int) - self._filter_errors = defaultdict(int) - self.name = 'core' self._loaded_paths = set() - self._prev_results = {} + self._prev_merged_results = {} + self._prev_pos = [] + + self._parents = [] + self._parent_count = 0 + self._max_parents = max( + [1, self._vim.vars['deoplete#max_processes']]) + + # Init context + context = self._vim.call('deoplete#init#_context', 'Init', []) + context['rpc'] = 'deoplete_on_event' + + for n in range(0, self._max_parents): + self._parents.append(Parent(vim, context)) # Enable logging before "Init" for more information, and e.g. # deoplete-jedi picks up the log filename from deoplete's handler in @@ -44,8 +41,6 @@ def __init__(self, vim): self.enable_logging() # on_init() call - context = self._vim.call('deoplete#init#_context', 'Init', []) - context['rpc'] = 'deoplete_on_event' self.on_event(context) self._vim.vars['deoplete#_initialized'] = True @@ -56,19 +51,21 @@ def enable_logging(self): logging = self._vim.vars['deoplete#_logging'] logger.setup(self._vim, logging['level'], logging['logfile']) self.is_debug_enabled = True + for parent in self._parents: + parent.enable_logging() def completion_begin(self, context): + self.debug('completion_begin: %s', context['input']) + self.check_recache(context) try: - is_async, complete_position, candidates = self.merge_results( - self.gather_results(context), context['input']) - + is_async, position, candidates = self.merge_results(context) except Exception: error_tb(self._vim, 'Error while gathering completions') is_async = False - complete_position = -1 + position = -1 candidates = [] if is_async: @@ -80,283 +77,78 @@ def completion_begin(self, context): in context['vars']): self._vim.call('deoplete#mapping#_restore_completeopt') - # error(self._vim, context['input']) + # Check the previous completion + prev_candidates = context['vars'][ + 'deoplete#_prev_completion']['candidates'] + if context['event'] == 'Async' and candidates == prev_candidates: + return + # error(self._vim, candidates) self._vim.vars['deoplete#_context'] = { - 'complete_position': complete_position, + 'complete_position': position, 'candidates': candidates, 'event': context['event'], 'input': context['input'], + 'is_async': is_async, } self._vim.call('deoplete#handler#_completion_timer_start') - def gather_results(self, context): - results = [] - - for source in [x[1] for x in self.itersource(context)]: - try: - 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 - continue - - if (source.name in self._prev_results and - self.use_previous_result( - context, self._prev_results[source.name], - source.is_volatile)): - results.append(self._prev_results[source.name]) - continue - - 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_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_menu_width'] > 0: - ctx['max_menu_width'] = max(10, ctx['max_menu_width']) - - # Gathering - self.profile_start(ctx, source.name) - ctx['candidates'] = source.gather_candidates(ctx) - self.profile_end(source.name) - - if ctx['candidates'] is None: - continue - - ctx['candidates'] = convert2candidates(ctx['candidates']) - - result = { - 'name': source.name, - 'source': source, - 'context': ctx, - 'is_async': ctx['is_async'], - 'prev_linenr': ctx['position'][1], - 'prev_input': ctx['input'], - } - self._prev_results[source.name] = result - results.append(result) - except Exception: - self._source_errors[source.name] += 1 - if source.is_silent: - continue - if self._source_errors[source.name] > 2: - error(self._vim, 'Too many errors from "%s". ' - 'This source is disabled until Neovim ' - 'is restarted.' % source.name) - self._sources.pop(source.name) - continue - error_tb(self._vim, 'Errors from: %s' % source.name) - - return results - - def gather_async_results(self, result, source): - try: - result['context']['is_refresh'] = False - async_candidates = source.gather_candidates(result['context']) - result['is_async'] = result['context']['is_async'] - if async_candidates is None: - return - result['context']['candidates'] += convert2candidates( - async_candidates) - except Exception: - self._source_errors[source.name] += 1 - if source.is_silent: - return - if self._source_errors[source.name] > 2: - error(self._vim, 'Too many errors from "%s". ' - 'This source is disabled until Neovim ' - 'is restarted.' % source.name) - self._sources.pop(source.name) - else: - error_tb(self._vim, 'Errors from: %s' % source.name) + self.debug('completion_end: %s', context['input']) - def merge_results(self, results, context_input): - merged_results = [] - all_candidates = [] - for result in [x for x in results - if not self.is_skip(x['context'], x['source'])]: - source = result['source'] + def merge_results(self, context): + use_prev = context['position'] == self._prev_pos + if not use_prev: + self._prev_merged_results = {} - # Gather async results - if result['is_async']: - self.gather_async_results(result, source) - - if not result['context']['candidates']: - continue - - context = copy.deepcopy(result['context']) - - context['input'] = context_input - context['complete_str'] = context['input'][ - context['char_position']:] - context['is_sorted'] = False - - # Filtering - ignorecase = context['ignorecase'] - smartcase = context['smartcase'] - camelcase = context['camelcase'] - - # Set ignorecase - if (smartcase or camelcase) and re.search( - r'[A-Z]', context['complete_str']): - context['ignorecase'] = 0 - - for f in [self._filters[x] for x - in source.matchers + source.sorters + source.converters - if x in self._filters]: - try: - self.profile_start(context, f.name) - if (isinstance(context['candidates'], dict) and - 'sorted_candidates' in context['candidates']): - context_candidates = [] - sorted_candidates = context['candidates'][ - 'sorted_candidates'] - context['is_sorted'] = True - for candidates in sorted_candidates: - context['candidates'] = candidates - context_candidates += f.filter(context) - context['candidates'] = context_candidates - else: - context['candidates'] = f.filter(context) - self.profile_end(f.name) - except Exception: - self._filter_errors[f.name] += 1 - if self._source_errors[f.name] > 2: - error(self._vim, 'Too many errors from "%s". ' - 'This filter is disabled until Neovim ' - 'is restarted.' % f.name) - self._filters.pop(f.name) - continue - error_tb(self._vim, 'Errors from: %s' % f) - - context['ignorecase'] = ignorecase - - # On post filter - if hasattr(source, 'on_post_filter'): - context['candidates'] = source.on_post_filter(context) - - if context['candidates']: - merged_results.append([context['candidates'], result]) - - is_async = len([x for x in results if x['context']['is_async']]) > 0 + is_async = False + merged_results = [] + for cnt, parent in enumerate(self._parents): + if use_prev and cnt in self._prev_merged_results: + # Use previous result + merged_results += self._prev_merged_results[cnt] + else: + result = parent.merge_results(context) + is_async = is_async or result[0] + if not result[0]: + self._prev_merged_results[cnt] = result[1] + merged_results += result[1] + self._prev_pos = context['position'] if not merged_results: return (is_async, -1, []) - complete_position = min([x[1]['context']['complete_position'] + complete_position = min([x['complete_position'] for x in merged_results]) - for [candidates, result] in merged_results: - context = result['context'] - source = result['source'] + all_candidates = [] + for result in sorted(merged_results, + key=lambda x: x['rank'], reverse=True): + candidates = result['candidates'] prefix = context['input'][ - complete_position:context['complete_position']] + complete_position:result['complete_position']] - mark = source.mark + ' ' + mark = result['mark'] + ' ' for candidate in candidates: # Add prefix candidate['word'] = prefix + candidate['word'] # Set default menu and icase candidate['icase'] = 1 - if (source.mark != '' and + if (mark != ' ' and candidate.get('menu', '').find(mark) != 0): candidate['menu'] = mark + candidate.get('menu', '') - if source.filetypes: + if result['dup']: candidate['dup'] = 1 all_candidates += candidates # self.debug(candidates) - if context['vars']['deoplete#max_list'] > 0: - all_candidates = all_candidates[ - : context['vars']['deoplete#max_list']] + max_list = context['vars']['deoplete#max_list'] + if max_list > 0: + all_candidates = all_candidates[: max_list] return (is_async, complete_position, all_candidates) - def itersource(self, context): - sources = sorted(self._sources.items(), - key=lambda x: get_custom( - context['custom'], - x[1].name, 'rank', x[1].rank), - reverse=True) - filetypes = context['filetypes'] - ignore_sources = set() - for ft in filetypes: - ignore_sources.update( - get_buffer_config(context, ft, - 'deoplete_ignore_sources', - 'deoplete#ignore_sources', - {})) - - for source_name, source in sources: - if source.limit > 0 and context['bufsize'] > source.limit: - continue - 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: %s', source.name) - try: - source.on_init(context) - except Exception as exc: - if isinstance(exc, SourceInitError): - error(self._vim, - 'Error when loading source {}: {}. ' - 'Ignoring.'.format(source_name, exc)) - else: - error_tb(self._vim, - 'Error when loading source {}: {}. ' - 'Ignoring.'.format(source_name, exc)) - self._sources.pop(source_name) - continue - else: - source.is_initialized = True - yield source_name, source - - def profile_start(self, context, name): - if self._profile_flag is 0 or not self.is_debug_enabled: - return - - if not self._profile_flag: - self._profile_flag = context['vars']['deoplete#enable_profile'] - if self._profile_flag: - return self.profile_start(context, name) - elif self._profile_flag: - self.debug('Profile Start: {0}'.format(name)) - self._profile_start = time.clock() - - def profile_end(self, name): - if self._profile_start: - self.debug('Profile End : {0:<25} time={1:2.10f}'.format( - name, time.clock() - self._profile_start)) - def load_sources(self, context): # Load sources from runtimepath for path in find_rplugins(context, 'source'): @@ -364,26 +156,13 @@ def load_sources(self, context): continue self._loaded_paths.add(path) - name = os.path.splitext(os.path.basename(path))[0] - - source = None - try: - Source = import_plugin(path, 'source', 'Source') - if not Source: - continue + self._parents[self._parent_count].add_source(path) - source = Source(self._vim) - source.name = getattr(source, 'name', name) - source.path = path - except Exception: - error_tb(self._vim, 'Could not load source: %s' % name) - finally: - if source: - self._sources[source.name] = source - self.debug('Loaded Source: %s (%s)', source.name, path) + self._parent_count += 1 + self._parent_count %= self._max_parents self.set_source_attributes(context) - self._custom = context['custom'] + self.set_custom(context) def load_filters(self, context): # Load filters from runtimepath @@ -392,83 +171,17 @@ def load_filters(self, context): continue self._loaded_paths.add(path) - name = os.path.splitext(os.path.basename(path))[0] - - f = None - try: - Filter = import_plugin(path, 'filter', 'Filter') - if not Filter: - continue - - f = Filter(self._vim) - f.name = getattr(f, 'name', name) - f.path = path - self._filters[f.name] = f - except Exception: - # Exception occurred when loading a filter. Log stack trace. - error_tb(self._vim, 'Could not load filter: %s' % name) - finally: - if f: - self._filters[f.name] = f - self.debug('Loaded Filter: %s (%s)', f.name, path) + for parent in self._parents: + parent.add_filter(path) def set_source_attributes(self, context): - """Set source attributes from the context. - - Each item in `attrs` is the attribute name. If the default value is in - context['vars'] under a different name, use a tuple. - """ - attrs = ( - 'filetypes', - 'disabled_syntaxes', - 'input_pattern', - ('min_pattern_length', 'deoplete#auto_complete_start_length'), - 'max_pattern_length', - ('max_abbr_width', 'deoplete#max_abbr_width'), - ('max_kind_width', 'deoplete#max_menu_width'), - ('max_menu_width', 'deoplete#max_menu_width'), - 'matchers', - 'sorters', - 'converters', - 'mark', - 'is_debug_enabled', - 'is_silent', - ) - - for name, source in self._sources.items(): - for attr in attrs: - if isinstance(attr, tuple): - default_val = context['vars'][attr[1]] - attr = attr[0] - else: - default_val = None - source_attr = getattr(source, attr, default_val) - setattr(source, attr, get_custom(context['custom'], - name, attr, source_attr)) - - def use_previous_result(self, context, result, is_volatile): - if context['position'][1] != result['prev_linenr']: - return False - if is_volatile: - return context['input'] == result['prev_input'] - else: - return (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, source): - if 'syntax_names' in context and source.disabled_syntaxes: - p = re.compile('(' + '|'.join(source.disabled_syntaxes) + ')$') - if next(filter(p.search, context['syntax_names']), None): - return True - if (source.input_pattern != '' and - re.search('(' + source.input_pattern + ')$', - context['input'])): - return False - if context['event'] == 'Manual': - return False - return not (source.min_pattern_length <= - len(context['complete_str']) <= source.max_pattern_length) + for parent in self._parents: + parent.set_source_attributes(context) + + def set_custom(self, context): + self._custom = context['custom'] + for parent in self._parents: + parent.set_custom(self._custom) def check_recache(self, context): if context['runtimepath'] != self._runtimepath: @@ -480,18 +193,14 @@ def check_recache(self, context): self.on_event(context) elif context['custom'] != self._custom: self.set_source_attributes(context) - self._custom = context['custom'] + self.set_custom(context) def on_event(self, context): self.debug('on_event: %s', context['event']) self.check_recache(context) - for source_name, source in self.itersource(context): - if hasattr(source, 'on_event'): - self.debug('on_event: Source: %s', source_name) - try: - source.on_event(context) - except Exception as exc: - error_tb(self._vim, 'Exception during {}.on_event ' - 'for event {!r}: {}'.format( - source_name, context['event'], exc)) + for parent in self._parents: + parent.on_event(context) + + if context['event'] == 'VimLeavePre': + self._vim.vars['deoplete#_process_stopped'] = True diff --git a/rplugin/python3/deoplete/dp_main.py b/rplugin/python3/deoplete/dp_main.py new file mode 100644 index 00000000..1b04603d --- /dev/null +++ b/rplugin/python3/deoplete/dp_main.py @@ -0,0 +1,45 @@ +# ============================================================================ +# FILE: dp_main.py +# AUTHOR: Shougo Matsushita +# License: MIT license +# ============================================================================ + +import sys +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 + + +def main(serveraddr): + vim = attach_vim(serveraddr) + from deoplete.util import error_tb + child = None + try: + from deoplete.child import Child + child = Child(vim) + while 1: + child.main() + except Exception: + error_tb(vim, 'Error in child') + return + + +if __name__ == '__main__': + main(sys.argv[1]) diff --git a/rplugin/python3/deoplete/filter/matcher_cpsm.py b/rplugin/python3/deoplete/filter/matcher_cpsm.py index fc1a68f6..04172cc6 100644 --- a/rplugin/python3/deoplete/filter/matcher_cpsm.py +++ b/rplugin/python3/deoplete/filter/matcher_cpsm.py @@ -18,15 +18,15 @@ def __init__(self, vim): self.name = 'matcher_cpsm' self.description = 'cpsm matcher' - self.__initialized = False - self.__disabled = False + self._initialized = False + self._disabled = False def filter(self, context): if not context['candidates'] or not context[ - 'input'] or self.__disabled: + 'input'] or self._disabled: return context['candidates'] - if not self.__initialized: + if not self._initialized: # cpsm installation check ext = '.pyd' if context['is_windows'] else '.so' if globruntime(context['runtimepath'], 'bin/cpsm_py' + ext): @@ -34,13 +34,13 @@ def filter(self, context): sys.path.append(os.path.dirname( globruntime(context['runtimepath'], 'bin/cpsm_py' + ext)[0])) - self.__initialized = True + self._initialized = True else: error(self.vim, 'matcher_cpsm: bin/cpsm_py' + ext + ' is not found in your runtimepath.') error(self.vim, 'matcher_cpsm: You must install/build' + ' Python3 support enabled cpsm.') - self.__disabled = True + self._disabled = True return [] cpsm_result = self._get_cpsm_result( diff --git a/rplugin/python3/deoplete/parent.py b/rplugin/python3/deoplete/parent.py new file mode 100644 index 00000000..1c5bf8a8 --- /dev/null +++ b/rplugin/python3/deoplete/parent.py @@ -0,0 +1,97 @@ +# ============================================================================ +# FILE: parent.py +# AUTHOR: Shougo Matsushita +# License: MIT license +# ============================================================================ + +import time +import os + +from deoplete import logger +from deoplete.process import Process +from deoplete.util import error + + +class Parent(logger.LoggingMixin): + + def __init__(self, vim, context): + self.name = 'parent' + + self._vim = vim + self._proc = None + self._queue_id = '' + self._prev_pos = [] + self._start_process(context, context['serveraddr']) + + def enable_logging(self): + self._put('enable_logging', []) + self.is_debug_enabled = True + + def add_source(self, path): + self._put('add_source', [path]) + + def add_filter(self, path): + self._put('add_filter', [path]) + + def set_source_attributes(self, context): + self._put('set_source_attributes', [context]) + + def set_custom(self, custom): + self._put('set_custom', [custom]) + + def merge_results(self, context): + if context['position'] == self._prev_pos and ( + self._queue_id or context['event'] == 'Async'): + # Use previous id + queue_id = self._queue_id + else: + queue_id = self._put('merge_results', [context]) + if not queue_id: + return (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, []) + self._queue_id = '' + results = get[0] + return (results['is_async'], + results['merged_results']) if results else (False, []) + + def on_event(self, context): + if context['event'] == 'VimLeavePre': + self._stop_process() + self._put('on_event', [context]) + + def _start_process(self, context, serveraddr): + if self._proc: + return + + if not os.access(context['python3'], os.X_OK): + error(self._vim, str(context['python3']) + ' is not executble.') + error(self._vim, 'You need to set g:python3_host_prog.') + return + + self._proc = Process( + [context['python3'], context['dp_main'], serveraddr], + context, context['cwd']) + + def _stop_process(self): + if self._proc: + self._proc.kill() + self._proc = None + + def _put(self, name, args): + if not self._proc: + return None + + queue_id = str(time.time()) + + self._proc.write({'name': name, 'args': args, 'queue_id': queue_id}) + return queue_id + + def _get(self, queue_id): + return [x for x in self._proc.communicate(15) + if x['queue_id'] == queue_id] diff --git a/rplugin/python3/deoplete/process.py b/rplugin/python3/deoplete/process.py index 54b93cf0..ca880f88 100644 --- a/rplugin/python3/deoplete/process.py +++ b/rplugin/python3/deoplete/process.py @@ -5,10 +5,11 @@ # ============================================================================ import subprocess +import os +import msgpack from threading import Thread from queue import Queue -from time import time -import os +from time import time, sleep class Process(object): @@ -17,58 +18,57 @@ def __init__(self, commands, context, cwd): if os.name == 'nt': startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - self.__proc = subprocess.Popen(commands, - stdin=subprocess.DEVNULL, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - startupinfo=startupinfo, - cwd=cwd) - self.__eof = False - self.__context = context - self.__queue_out = Queue() - self.__thread = Thread(target=self.enqueue_output) - self.__thread.start() + self._proc = subprocess.Popen(commands, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + startupinfo=startupinfo, + cwd=cwd) + self._eof = False + self._context = context + self._packer = msgpack.Packer( + use_bin_type=True, + unicode_errors='surrogateescape') + self._unpacker = msgpack.Unpacker( + encoding='utf-8', + unicode_errors='surrogateescape') + self._queue_out = Queue() + self._thread = Thread(target=self.enqueue_output) + self._thread.start() def eof(self): - return self.__eof + return self._eof def kill(self): - if not self.__proc: + if not self._proc: return - self.__proc.kill() - self.__proc.wait() - self.__proc = None - self.__queue_out = None - self.__thread.join(1.0) - self.__thread = None + self._proc.kill() + self._proc.wait() + self._proc = None + self._queue_out = None + self._thread.join(1.0) + self._thread = None def enqueue_output(self): - for line in self.__proc.stdout: - if not self.__queue_out: - return - self.__queue_out.put( - line.decode(self.__context['encoding'], - errors='replace').strip('\r\n')) + while 1: + b = self._proc.stdout.read(1) + self._unpacker.feed(b) + for child_out in self._unpacker: + self._queue_out.put(child_out) def communicate(self, timeout): - if not self.__proc: - return ([], []) + if not self._proc: + return [] start = time() outs = [] - while not self.__queue_out.empty() and time() < start + timeout: - outs.append(self.__queue_out.get_nowait()) - - if self.__thread.is_alive() or not self.__queue_out.empty(): - return (outs, []) - - _, errs = self.__proc.communicate(timeout=timeout) - errs = errs.decode(self.__context['encoding'], - errors='replace').splitlines() - self.__eof = True - self.__proc = None - self.__thread = None - self.__queue = None + if self._queue_out.empty(): + sleep(timeout / 1000.0) + while not self._queue_out.empty() and time() < start + timeout: + outs.append(self._queue_out.get_nowait()) + return outs - return (outs, errs) + def write(self, expr): + self._proc.stdin.write(self._packer.pack(expr)) + self._proc.stdin.flush() diff --git a/rplugin/python3/deoplete/source/buffer.py b/rplugin/python3/deoplete/source/buffer.py index 06168a7e..cf5b196d 100644 --- a/rplugin/python3/deoplete/source/buffer.py +++ b/rplugin/python3/deoplete/source/buffer.py @@ -17,14 +17,14 @@ def __init__(self, vim): self.name = 'buffer' self.mark = '[B]' self.limit = 1000000 - self.__buffers = {} - self.__max_lines = 5000 + self._buffers = {} + self._max_lines = 5000 def on_event(self, context): bufnr = context['bufnr'] - if (bufnr not in self.__buffers or + if (bufnr not in self._buffers or context['event'] == 'BufWritePost'): - self.__make_cache(context, bufnr) + self._make_cache(context, bufnr) def gather_candidates(self, context): self.on_event(context) @@ -32,16 +32,16 @@ def gather_candidates(self, context): same_filetype = context['vars'].get( 'deoplete#buffer#require_same_filetype', True) return {'sorted_candidates': [ - x['candidates'] for x in self.__buffers.values() + 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, bufnr): + def _make_cache(self, context, bufnr): try: - self.__buffers[bufnr] = { + self._buffers[bufnr] = { 'bufnr': bufnr, 'filetype': self.vim.eval('&l:filetype'), 'candidates': [ diff --git a/rplugin/python3/deoplete/source/dictionary.py b/rplugin/python3/deoplete/source/dictionary.py index 78387b0c..73f661b8 100644 --- a/rplugin/python3/deoplete/source/dictionary.py +++ b/rplugin/python3/deoplete/source/dictionary.py @@ -19,32 +19,32 @@ def __init__(self, vim): self.name = 'dictionary' self.mark = '[D]' - self.__cache = {} + self._cache = {} def on_event(self, context): - self.__make_cache(context) + self._make_cache(context) def gather_candidates(self, context): - self.__make_cache(context) + self._make_cache(context) candidates = [] - for filename in [x for x in self.__get_dictionaries(context) - if x in self.__cache]: - candidates.append(self.__cache[filename].candidates) + for filename in [x for x in self._get_dictionaries(context) + if x in self._cache]: + candidates.append(self._cache[filename].candidates) return {'sorted_candidates': candidates} - def __make_cache(self, context): - for filename in self.__get_dictionaries(context): + def _make_cache(self, context): + for filename in self._get_dictionaries(context): mtime = getmtime(filename) - if filename in self.__cache and self.__cache[ + if filename in self._cache and self._cache[ filename].mtime == mtime: continue with open(filename, 'r', errors='replace') as f: - self.__cache[filename] = DictCacheItem( + self._cache[filename] = DictCacheItem( mtime, [{'word': x} for x in sorted( [x.strip() for x in f], key=str.lower)] ) - def __get_dictionaries(self, context): + def _get_dictionaries(self, context): return [x for x in context['dict__dictionary'].split(',') if exists(x)] diff --git a/rplugin/python3/deoplete/source/file.py b/rplugin/python3/deoplete/source/file.py index b5c6ca80..34bd36b7 100644 --- a/rplugin/python3/deoplete/source/file.py +++ b/rplugin/python3/deoplete/source/file.py @@ -21,14 +21,14 @@ def __init__(self, vim): self.mark = '[F]' self.min_pattern_length = 0 self.rank = 150 - self.__isfname = '' + self._isfname = '' def on_init(self, context): - self.__buffer_path = context['vars'].get( + self._buffer_path = context['vars'].get( 'deoplete#file#enable_buffer_path', 0) def on_event(self, context): - self.__isfname = self.vim.call( + self._isfname = self.vim.call( 'deoplete#util#vimoption2python_not', self.vim.options['isfname']) @@ -37,13 +37,13 @@ def get_complete_position(self, context): return pos if pos < 0 else pos + 1 def gather_candidates(self, context): - if not self.__isfname: + if not self._isfname: return [] - p = self.__longest_path_that_exists(context, context['input']) + p = self._longest_path_that_exists(context, context['input']) if p in (None, []) or p == '/' or re.search('//+$', p): return [] - complete_str = self.__substitute_path(context, dirname(p) + '/') + complete_str = self._substitute_path(context, dirname(p) + '/') if not os.path.isdir(complete_str): return [] hidden = context['complete_str'].find('.') == 0 @@ -60,19 +60,19 @@ def gather_candidates(self, context): return [{'word': x, 'abbr': x + '/'} for x in dirs ] + [{'word': x} for x in files] - def __longest_path_that_exists(self, context, input_str): + def _longest_path_that_exists(self, context, input_str): input_str = re.sub(r'[^/]*$', '', input_str) data = re.split(r'((?:%s+|(?:(?']) - set_pattern(self.__prefix_patterns, + set_pattern(self._prefix_patterns, 'cpp,objcpp', ['\.', '->', '::']) - set_pattern(self.__prefix_patterns, + set_pattern(self._prefix_patterns, 'perl,php', ['->']) - set_pattern(self.__prefix_patterns, + set_pattern(self._prefix_patterns, 'ruby', ['\.', '::']) - set_pattern(self.__prefix_patterns, + set_pattern(self._prefix_patterns, 'lua', ['\.', ':']) def get_complete_position(self, context): @@ -44,12 +44,12 @@ def get_complete_position(self, context): get_buffer_config(context, context['filetype'], 'deoplete_member_prefix_patterns', 'deoplete#member#prefix_patterns', - self.__prefix_patterns)): - m = re.search(self.__object_pattern + prefix_pattern + r'\w*$', + self._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)) + self._prefix = re.sub(r'\w*$', '', m.group(0)) return re.search(r'\w*$', context['input']).start() return -1 @@ -57,6 +57,6 @@ def gather_candidates(self, context): return [{'word': x} for x in parse_buffer_pattern( getlines(self.vim), - r'(?<=' + re.escape(self.__prefix) + r')\w+' + r'(?<=' + re.escape(self._prefix) + r')\w+' ) if x != context['complete_str']] diff --git a/rplugin/python3/deoplete/source/omni.py b/rplugin/python3/deoplete/source/omni.py index 3163c53e..fc48c6e7 100644 --- a/rplugin/python3/deoplete/source/omni.py +++ b/rplugin/python3/deoplete/source/omni.py @@ -48,7 +48,7 @@ def _get_complete_position(self, context, current_ft, filetype): omnifunc = context['omni__omnifunc'] if omnifunc == '': continue - self.__omnifunc = omnifunc + self._omnifunc = omnifunc for input_pattern in convert2list( get_buffer_config(context, filetype, 'deoplete_omni_input_patterns', @@ -62,7 +62,7 @@ def _get_complete_position(self, context, current_ft, filetype): 'Manual' and m is None): continue - if filetype == current_ft and self.__omnifunc in [ + if filetype == current_ft and self._omnifunc in [ 'ccomplete#Complete', 'htmlcomplete#CompleteTags', 'LanguageClient#complete', @@ -70,24 +70,24 @@ def _get_complete_position(self, context, current_ft, filetype): # In the blacklist return -1 try: - complete_pos = self.vim.call(self.__omnifunc, 1, '') + complete_pos = self.vim.call(self._omnifunc, 1, '') except Exception as e: self.print_error('Error occurred calling omnifunction: ' + - self.__omnifunc) + self._omnifunc) return -1 return complete_pos return -1 def gather_candidates(self, context): try: - candidates = self.vim.call(self.__omnifunc, 0, '') + candidates = self.vim.call(self._omnifunc, 0, '') if isinstance(candidates, dict): candidates = candidates['words'] elif isinstance(candidates, int): candidates = [] except Exception as e: self.print_error('Error occurred calling omnifunction: ' + - self.__omnifunc) + self._omnifunc) candidates = [] candidates = convert2candidates(candidates) diff --git a/rplugin/python3/deoplete/source/tag.py b/rplugin/python3/deoplete/source/tag.py index 510071f1..1dcec7f3 100644 --- a/rplugin/python3/deoplete/source/tag.py +++ b/rplugin/python3/deoplete/source/tag.py @@ -23,26 +23,26 @@ def __init__(self, vim): self.name = 'tag' self.mark = '[T]' - self.__cache = {} + self._cache = {} def on_init(self, context): - self.__limit = context['vars'].get( + self._limit = context['vars'].get( 'deoplete#tag#cache_limit_size', 500000) def on_event(self, context): - self.__make_cache(context) + self._make_cache(context) def gather_candidates(self, context): - self.__make_cache(context) + self._make_cache(context) candidates = [] - for c in self.__cache.values(): + for c in self._cache.values(): candidates.extend(c.candidates) return candidates - def __make_cache(self, context): - for filename in self.__get_tagfiles(context): + def _make_cache(self, context): + for filename in self._get_tagfiles(context): mtime = getmtime(filename) - if filename in self.__cache and self.__cache[ + if filename in self._cache and self._cache[ filename].mtime == mtime: continue @@ -66,14 +66,14 @@ def __make_cache(self, context): if not items: continue - self.__cache[filename] = TagsCacheItem( + self._cache[filename] = TagsCacheItem( mtime, sorted(items, key=lambda x: x['word'].lower())) - def __get_tagfiles(self, context): + def _get_tagfiles(self, context): include_files = self.vim.call( 'neoinclude#include#get_tag_files') if self.vim.call( 'exists', '*neoinclude#include#get_tag_files') else [] return [x for x in self.vim.call( 'map', self.vim.call('tagfiles') + include_files, 'fnamemodify(v:val, ":p")') - if exists(x) and getsize(x) < self.__limit] + if exists(x) and getsize(x) < self._limit]