Fixed vim and zsh
This commit is contained in:
@ -0,0 +1,64 @@
|
||||
# ============================================================================
|
||||
# FILE: __init__.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from importlib import find_loader
|
||||
from deoplete.deoplete import Deoplete
|
||||
|
||||
|
||||
if find_loader('yarp'):
|
||||
import vim
|
||||
else:
|
||||
import neovim
|
||||
vim = neovim
|
||||
|
||||
if 'neovim' in locals() and hasattr(neovim, 'plugin'):
|
||||
# Neovim only
|
||||
|
||||
@neovim.plugin
|
||||
class DeopleteHandlers(object):
|
||||
|
||||
def __init__(self, vim):
|
||||
self._vim = vim
|
||||
|
||||
@neovim.function('_deoplete_init', sync=False)
|
||||
def init_channel(self, args):
|
||||
self._deoplete = Deoplete(self._vim)
|
||||
|
||||
@neovim.rpc_export('deoplete_enable_logging')
|
||||
def enable_logging(self, context):
|
||||
self._deoplete.enable_logging()
|
||||
|
||||
@neovim.rpc_export('deoplete_auto_completion_begin')
|
||||
def auto_completion_begin(self, context):
|
||||
self._deoplete.completion_begin(context)
|
||||
|
||||
@neovim.rpc_export('deoplete_manual_completion_begin')
|
||||
def manual_completion_begin(self, context):
|
||||
self._deoplete.completion_begin(context)
|
||||
|
||||
@neovim.rpc_export('deoplete_on_event')
|
||||
def on_event(self, context):
|
||||
self._deoplete.on_event(context)
|
||||
|
||||
|
||||
if find_loader('yarp'):
|
||||
|
||||
global_deoplete = Deoplete(vim)
|
||||
|
||||
def deoplete_init():
|
||||
pass
|
||||
|
||||
def deoplete_enable_logging(context):
|
||||
global_deoplete.enable_logging()
|
||||
|
||||
def deoplete_auto_completion_begin(context):
|
||||
global_deoplete.completion_begin(context)
|
||||
|
||||
def deoplete_manual_completion_begin(context):
|
||||
global_deoplete.completion_begin(context)
|
||||
|
||||
def deoplete_on_event(context):
|
||||
global_deoplete.on_event(context)
|
472
vim/plugins/deoplete.nvim/rplugin/python3/deoplete/child.py
Normal file
472
vim/plugins/deoplete.nvim/rplugin/python3/deoplete/child.py
Normal file
@ -0,0 +1,472 @@
|
||||
# ============================================================================
|
||||
# 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
|
||||
|
||||
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, uniq_list_dict)
|
||||
|
||||
|
||||
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._prev_results = {}
|
||||
self._unpacker = msgpack.Unpacker(
|
||||
encoding='utf-8',
|
||||
unicode_errors='surrogateescape')
|
||||
self._packer = msgpack.Packer(
|
||||
use_bin_type=True,
|
||||
encoding='utf-8',
|
||||
unicode_errors='surrogateescape')
|
||||
self._ignore_sources = []
|
||||
|
||||
def main_loop(self, stdout):
|
||||
while True:
|
||||
feed = sys.stdin.buffer.raw.read(102400)
|
||||
if feed is None:
|
||||
continue
|
||||
if feed == b'':
|
||||
# EOF
|
||||
return
|
||||
|
||||
self._unpacker.feed(feed)
|
||||
self.debug('_read: %d bytes', len(feed))
|
||||
|
||||
for child_in in self._unpacker:
|
||||
name = child_in['name']
|
||||
args = child_in['args']
|
||||
queue_id = child_in['queue_id']
|
||||
self.debug('main_loop: %s begin', name)
|
||||
|
||||
ret = self.main(name, args, queue_id)
|
||||
if ret:
|
||||
self._write(stdout, ret)
|
||||
|
||||
self.debug('main_loop: end')
|
||||
|
||||
def main(self, name, args, queue_id):
|
||||
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 == 'set_custom':
|
||||
self._set_custom(args[0])
|
||||
elif name == 'on_event':
|
||||
self._on_event(args[0])
|
||||
elif name == 'merge_results':
|
||||
ret = self._merge_results(args[0], queue_id)
|
||||
return ret
|
||||
|
||||
def _write(self, stdout, expr):
|
||||
stdout.buffer.write(self._packer.pack(expr))
|
||||
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):
|
||||
self.debug('merged_results: begin')
|
||||
results = self._gather_results(context)
|
||||
|
||||
merged_results = []
|
||||
for result in [x for x in results
|
||||
if not self._is_skip(x['context'], x['source'])]:
|
||||
if self._update_result(result, context['input']):
|
||||
rank = get_custom(self._custom,
|
||||
result['source'].name, 'rank',
|
||||
result['source'].rank)
|
||||
dup = bool(result['source'].filetypes)
|
||||
candidates = result['candidates']
|
||||
# Note: cannot use set() for dict
|
||||
if dup:
|
||||
# Remove duplicates
|
||||
candidates = uniq_list_dict(candidates)
|
||||
merged_results.append({
|
||||
'complete_position': result['complete_position'],
|
||||
'mark': result['source'].mark,
|
||||
'dup': dup,
|
||||
'candidates': candidates,
|
||||
'source_name': result['source'].name,
|
||||
'rank': rank,
|
||||
})
|
||||
|
||||
is_async = len([x for x in results if x['is_async']]) > 0
|
||||
|
||||
self.debug('merged_results: end')
|
||||
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._ignore_sources.append(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._ignore_sources.append(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:
|
||||
error_tb(self._vim, 'Errors from: %s' % f)
|
||||
|
||||
def _update_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(self._ignore_sources)
|
||||
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.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._ignore_sources.append(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 source.events is None or context['event'] in source.events:
|
||||
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))
|
212
vim/plugins/deoplete.nvim/rplugin/python3/deoplete/deoplete.py
Normal file
212
vim/plugins/deoplete.nvim/rplugin/python3/deoplete/deoplete.py
Normal file
@ -0,0 +1,212 @@
|
||||
# ============================================================================
|
||||
# FILE: deoplete.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from deoplete import logger
|
||||
from deoplete.parent import Parent
|
||||
from deoplete.util import (error_tb, find_rplugins, error)
|
||||
|
||||
|
||||
class Deoplete(logger.LoggingMixin):
|
||||
|
||||
def __init__(self, vim):
|
||||
self.name = 'core'
|
||||
|
||||
self._vim = vim
|
||||
self._runtimepath = ''
|
||||
self._custom = []
|
||||
self._loaded_paths = set()
|
||||
self._prev_merged_results = {}
|
||||
self._prev_pos = []
|
||||
|
||||
self._parents = []
|
||||
self._parent_count = 0
|
||||
self._max_parents = max(
|
||||
[1, self._vim.vars['deoplete#num_processes']])
|
||||
|
||||
if self._max_parents > 1 and not hasattr(self._vim, 'loop'):
|
||||
error(self._vim, 'neovim-python 0.2.4+ is required.')
|
||||
return
|
||||
|
||||
# Enable logging before "Init" 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()
|
||||
|
||||
# Init context
|
||||
context = self._vim.call('deoplete#init#_context', 'Init', [])
|
||||
context['rpc'] = 'deoplete_on_event'
|
||||
|
||||
# Init processes
|
||||
for n in range(0, self._max_parents):
|
||||
self._parents.append(Parent(vim, context))
|
||||
if self._vim.vars['deoplete#_logging']:
|
||||
for parent in self._parents:
|
||||
parent.enable_logging()
|
||||
|
||||
# on_init() call
|
||||
self.on_event(context)
|
||||
|
||||
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):
|
||||
logging = self._vim.vars['deoplete#_logging']
|
||||
logger.setup(self._vim, logging['level'], logging['logfile'])
|
||||
self.is_debug_enabled = True
|
||||
|
||||
def completion_begin(self, context):
|
||||
self.debug('completion_begin: %s', context['input'])
|
||||
|
||||
self.check_recache(context)
|
||||
|
||||
try:
|
||||
is_async, position, candidates = self.merge_results(context)
|
||||
except Exception:
|
||||
error_tb(self._vim, 'Error while gathering completions')
|
||||
|
||||
is_async = False
|
||||
position = -1
|
||||
candidates = []
|
||||
|
||||
if is_async:
|
||||
self._vim.call('deoplete#handler#_async_timer_start')
|
||||
else:
|
||||
self._vim.call('deoplete#handler#_async_timer_stop')
|
||||
|
||||
if not candidates and ('deoplete#_saved_completeopt'
|
||||
in context['vars']):
|
||||
self._vim.call('deoplete#mapping#_restore_completeopt')
|
||||
|
||||
# Async update is skipped if same.
|
||||
prev_completion = context['vars']['deoplete#_prev_completion']
|
||||
prev_candidates = prev_completion['candidates']
|
||||
prev_pos = prev_completion['complete_position']
|
||||
if (context['event'] == 'Async' and
|
||||
prev_pos == self._vim.call('getpos', '.') 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,
|
||||
}
|
||||
self._vim.call('deoplete#handler#_do_complete')
|
||||
|
||||
self.debug('completion_end: %s', context['input'])
|
||||
|
||||
def merge_results(self, context):
|
||||
use_prev = context['position'] == self._prev_pos
|
||||
if not use_prev:
|
||||
self._prev_merged_results = {}
|
||||
|
||||
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['complete_position']
|
||||
for x in merged_results])
|
||||
|
||||
all_candidates = []
|
||||
for result in sorted(merged_results,
|
||||
key=lambda x: x['rank'], reverse=True):
|
||||
candidates = result['candidates']
|
||||
prefix = context['input'][
|
||||
complete_position:result['complete_position']]
|
||||
|
||||
mark = result['mark'] + ' '
|
||||
for candidate in candidates:
|
||||
# Add prefix
|
||||
candidate['word'] = prefix + candidate['word']
|
||||
|
||||
# Set default menu and icase
|
||||
candidate['icase'] = 1
|
||||
if (mark != ' ' and
|
||||
candidate.get('menu', '').find(mark) != 0):
|
||||
candidate['menu'] = mark + candidate.get('menu', '')
|
||||
if result['dup']:
|
||||
candidate['dup'] = 1
|
||||
|
||||
all_candidates += candidates
|
||||
|
||||
# self.debug(candidates)
|
||||
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 load_sources(self, context):
|
||||
# Load sources from runtimepath
|
||||
for path in find_rplugins(context, 'source'):
|
||||
if path in self._loaded_paths:
|
||||
continue
|
||||
self._loaded_paths.add(path)
|
||||
|
||||
self._parents[self._parent_count].add_source(path)
|
||||
self.debug('Process %d: %s', self._parent_count, path)
|
||||
|
||||
self._parent_count += 1
|
||||
self._parent_count %= self._max_parents
|
||||
|
||||
self.set_source_attributes(context)
|
||||
self.set_custom(context)
|
||||
|
||||
def load_filters(self, context):
|
||||
# Load filters from runtimepath
|
||||
for path in find_rplugins(context, 'filter'):
|
||||
if path in self._loaded_paths:
|
||||
continue
|
||||
self._loaded_paths.add(path)
|
||||
|
||||
for parent in self._parents:
|
||||
parent.add_filter(path)
|
||||
|
||||
def set_source_attributes(self, context):
|
||||
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:
|
||||
self._runtimepath = context['runtimepath']
|
||||
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.set_custom(context)
|
||||
|
||||
def on_event(self, context):
|
||||
self.debug('on_event: %s', context['event'])
|
||||
self.check_recache(context)
|
||||
|
||||
for parent in self._parents:
|
||||
parent.on_event(context)
|
@ -0,0 +1,58 @@
|
||||
# ============================================================================
|
||||
# FILE: dp_main.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import sys
|
||||
import io
|
||||
|
||||
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:
|
||||
error_tb(vim, 'Error in child')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1])
|
@ -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.
|
||||
"""
|
@ -0,0 +1,20 @@
|
||||
# ============================================================================
|
||||
# FILE: base.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from abc import abstractmethod
|
||||
from deoplete.logger import LoggingMixin
|
||||
|
||||
|
||||
class Base(LoggingMixin):
|
||||
|
||||
def __init__(self, vim):
|
||||
self.vim = vim
|
||||
self.name = 'base'
|
||||
self.description = ''
|
||||
|
||||
@abstractmethod
|
||||
def filter(self, context):
|
||||
pass
|
@ -0,0 +1,32 @@
|
||||
# ============================================================================
|
||||
# FILE: converter_auto_delimiter.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from .base import Base
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'converter_auto_delimiter'
|
||||
self.description = 'auto delimiter converter'
|
||||
|
||||
def filter(self, context):
|
||||
delimiters = context['vars']['deoplete#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']
|
||||
|
||||
|
||||
def last_find(s, needles):
|
||||
for needle in needles:
|
||||
if len(s) >= len(needle) and s[-len(needle):] == needle:
|
||||
return needle
|
@ -0,0 +1,27 @@
|
||||
# ============================================================================
|
||||
# FILE: converter_auto_paren.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import re
|
||||
from .base import Base
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'converter_auto_paren'
|
||||
self.description = 'auto add parentheses converter'
|
||||
|
||||
def filter(self, context):
|
||||
p1 = re.compile('\(\)?$')
|
||||
p2 = re.compile('\(.*\)')
|
||||
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']
|
@ -0,0 +1,39 @@
|
||||
# ============================================================================
|
||||
# FILE: converter_remove_overlap.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import re
|
||||
from .base import Base
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'converter_remove_overlap'
|
||||
self.description = 'remove overlap converter'
|
||||
|
||||
def filter(self, context):
|
||||
if not context['next_input']:
|
||||
return context['candidates']
|
||||
m = re.match('\S+', context['next_input'])
|
||||
if not m:
|
||||
return context['candidates']
|
||||
next = m.group(0)
|
||||
for [overlap, candidate] in [
|
||||
[x, y] for x, y
|
||||
in [[overlap_length(x['word'], next), x]
|
||||
for x in context['candidates']] if x > 0]:
|
||||
if 'abbr' not in candidate:
|
||||
candidate['abbr'] = candidate['word']
|
||||
candidate['word'] = candidate['word'][: -overlap]
|
||||
return context['candidates']
|
||||
|
||||
|
||||
def overlap_length(left, right):
|
||||
pos = len(right)
|
||||
while pos > 0 and not left.endswith(right[: pos]):
|
||||
pos -= 1
|
||||
return pos
|
@ -0,0 +1,23 @@
|
||||
# ============================================================================
|
||||
# FILE: converter_remove_paren.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import re
|
||||
from .base import Base
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'converter_remove_paren'
|
||||
self.description = 'remove parentheses converter'
|
||||
|
||||
def filter(self, context):
|
||||
p = re.compile('\(\)?$')
|
||||
for candidate in [x for x in context['candidates']
|
||||
if p.search(x['word'])]:
|
||||
candidate['word'] = re.sub('\(\)?$', '', candidate['word'])
|
||||
return context['candidates']
|
@ -0,0 +1,28 @@
|
||||
# ============================================================================
|
||||
# FILE: converter_truncate_abbr.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from .base import Base
|
||||
from deoplete.util import truncate_skipping
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'converter_truncate_abbr'
|
||||
self.description = 'truncate abbr converter'
|
||||
|
||||
def filter(self, context):
|
||||
max_width = context['max_abbr_width']
|
||||
if max_width <= 0:
|
||||
return context['candidates']
|
||||
|
||||
footer_width = max_width / 3
|
||||
for candidate in context['candidates']:
|
||||
candidate['abbr'] = truncate_skipping(
|
||||
candidate.get('abbr', candidate['word']),
|
||||
max_width, '..', footer_width)
|
||||
return context['candidates']
|
@ -0,0 +1,29 @@
|
||||
# ============================================================================
|
||||
# FILE: converter_truncate_kind.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from .base import Base
|
||||
from deoplete.util import truncate_skipping
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'converter_truncate_kind'
|
||||
self.description = 'truncate kind converter'
|
||||
|
||||
def filter(self, context):
|
||||
max_width = context['max_kind_width']
|
||||
if not context['candidates'] or 'kind' not in context[
|
||||
'candidates'][0] or max_width <= 0:
|
||||
return context['candidates']
|
||||
|
||||
footer_width = max_width / 3
|
||||
for candidate in context['candidates']:
|
||||
candidate['kind'] = truncate_skipping(
|
||||
candidate.get('kind', ''),
|
||||
max_width, '..', footer_width)
|
||||
return context['candidates']
|
@ -0,0 +1,29 @@
|
||||
# ============================================================================
|
||||
# FILE: converter_truncate_menu.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from .base import Base
|
||||
from deoplete.util import truncate_skipping
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'converter_truncate_menu'
|
||||
self.description = 'truncate menu converter'
|
||||
|
||||
def filter(self, context):
|
||||
max_width = context['max_menu_width']
|
||||
if not context['candidates'] or 'menu' not in context[
|
||||
'candidates'][0] or max_width <= 0:
|
||||
return context['candidates']
|
||||
|
||||
footer_width = max_width / 3
|
||||
for candidate in context['candidates']:
|
||||
candidate['menu'] = truncate_skipping(
|
||||
candidate.get('menu', ''),
|
||||
max_width, '..', footer_width)
|
||||
return context['candidates']
|
@ -0,0 +1,54 @@
|
||||
# ============================================================================
|
||||
# FILE: matcher_cpsm.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from .base import Base
|
||||
import sys
|
||||
import os
|
||||
from deoplete.util import error, globruntime
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'matcher_cpsm'
|
||||
self.description = 'cpsm matcher'
|
||||
|
||||
self._initialized = False
|
||||
self._disabled = False
|
||||
|
||||
def filter(self, context):
|
||||
if not context['candidates'] or not context[
|
||||
'input'] or self._disabled:
|
||||
return context['candidates']
|
||||
|
||||
if not self._initialized:
|
||||
# cpsm installation check
|
||||
ext = '.pyd' if context['is_windows'] else '.so'
|
||||
if globruntime(context['runtimepath'], 'bin/cpsm_py' + ext):
|
||||
# Add path
|
||||
sys.path.append(os.path.dirname(
|
||||
globruntime(context['runtimepath'],
|
||||
'bin/cpsm_py' + ext)[0]))
|
||||
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
|
||||
return []
|
||||
|
||||
cpsm_result = self._get_cpsm_result(
|
||||
context['candidates'], context['complete_str'])
|
||||
return [x for x in context['candidates']
|
||||
if x['word'] in sorted(cpsm_result, key=cpsm_result.index)]
|
||||
|
||||
def _get_cpsm_result(self, candidates, pattern):
|
||||
import cpsm_py
|
||||
return cpsm_py.ctrlp_match((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 .base import Base
|
||||
from deoplete.util import fuzzy_escape
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'matcher_full_fuzzy'
|
||||
self.description = 'full fuzzy matcher'
|
||||
|
||||
def filter(self, context):
|
||||
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,41 @@
|
||||
# ============================================================================
|
||||
# FILE: matcher_fuzzy.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import re
|
||||
from .base import Base
|
||||
from deoplete.util import (
|
||||
fuzzy_escape, binary_search_begin, binary_search_end)
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'matcher_fuzzy'
|
||||
self.description = 'fuzzy matcher'
|
||||
|
||||
def filter(self, context):
|
||||
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[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,43 @@
|
||||
# ============================================================================
|
||||
# FILE: matcher_head.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from .base import Base
|
||||
from deoplete.util import binary_search_begin, binary_search_end
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'matcher_head'
|
||||
self.description = 'head matcher'
|
||||
|
||||
def filter(self, context):
|
||||
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
|
||||
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,21 @@
|
||||
# ============================================================================
|
||||
# FILE: matcher_length.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from .base import Base
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'matcher_length'
|
||||
self.description = 'length matcher'
|
||||
|
||||
def filter(self, context):
|
||||
input_len = len(context['complete_str'])
|
||||
return [x for x in context['candidates']
|
||||
if len(x['word']) > input_len]
|
@ -0,0 +1,26 @@
|
||||
# ============================================================================
|
||||
# FILE: sorter_rank.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from .base import Base
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'sorter_rank'
|
||||
self.description = 'rank sorter'
|
||||
|
||||
def filter(self, context):
|
||||
rank = context['vars']['deoplete#_rank']
|
||||
complete_str = context['complete_str'].lower()
|
||||
input_len = len(complete_str)
|
||||
return sorted(context['candidates'],
|
||||
key=lambda x: -1 * rank[x['word']]
|
||||
if x['word'] in rank
|
||||
else abs(x['word'].lower().find(
|
||||
complete_str, 0, input_len)))
|
@ -0,0 +1,20 @@
|
||||
# ============================================================================
|
||||
# FILE: sorter_word.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from .base import Base
|
||||
|
||||
|
||||
class Filter(Base):
|
||||
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'sorter_word'
|
||||
self.description = 'word sorter'
|
||||
|
||||
def filter(self, context):
|
||||
return sorted(context['candidates'],
|
||||
key=lambda x: x['word'])
|
153
vim/plugins/deoplete.nvim/rplugin/python3/deoplete/logger.py
Normal file
153
vim/plugins/deoplete.nvim/rplugin/python3/deoplete/logger.py
Normal file
@ -0,0 +1,153 @@
|
||||
# ============================================================================
|
||||
# FILE: logger.py
|
||||
# AUTHOR: Tommy Allen <tommy at esdf.io>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
|
||||
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
|
||||
|
||||
|
||||
def getLogger(name):
|
||||
"""Get a logger that is a child of the 'root' logger.
|
||||
"""
|
||||
return root.getChild(name)
|
||||
|
||||
|
||||
def setup(vim, level, output_file=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
|
||||
|
||||
neovim_version = pkg_resources.get_distribution('neovim').version
|
||||
except ImportError:
|
||||
neovim_version = 'unknown'
|
||||
|
||||
log = getLogger('logging')
|
||||
log.info('--- Deoplete Log Start ---')
|
||||
log.info('%s, Python %s, neovim client %s',
|
||||
vim.call('deoplete#util#neovim_version'),
|
||||
'.'.join(map(str, sys.version_info[:3])),
|
||||
neovim_version)
|
||||
|
||||
if not vim.vars.get('deoplete#_logging_notified'):
|
||||
vim.vars['deoplete#_logging_notified'] = 1
|
||||
vim.call('deoplete#util#print_debug', 'Logging to %s' % (
|
||||
output_file))
|
||||
|
||||
|
||||
def logmethod(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, *args, **kwargs):
|
||||
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, *args, **kwargs):
|
||||
self._logger.debug(msg, *args, **kwargs)
|
||||
|
||||
@logmethod
|
||||
def info(self, msg, *args, **kwargs):
|
||||
self._logger.info(msg, *args, **kwargs)
|
||||
|
||||
@logmethod
|
||||
def warning(self, msg, *args, **kwargs):
|
||||
self._logger.warning(msg, *args, **kwargs)
|
||||
warn = warning
|
||||
|
||||
@logmethod
|
||||
def error(self, msg, *args, **kwargs):
|
||||
self._logger.error(msg, *args, **kwargs)
|
||||
|
||||
@logmethod
|
||||
def exception(self, msg, *args, **kwargs):
|
||||
# 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, *args, **kwargs):
|
||||
self._logger.critical(msg, *args, **kwargs)
|
||||
fatal = critical
|
||||
|
||||
|
||||
class DeopleteLogFilter(logging.Filter):
|
||||
def __init__(self, vim, name=''):
|
||||
self.vim = vim
|
||||
self.counter = defaultdict(int)
|
||||
self.last_message_time = 0
|
||||
self.last_message = None
|
||||
|
||||
def filter(self, record):
|
||||
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
|
137
vim/plugins/deoplete.nvim/rplugin/python3/deoplete/parent.py
Normal file
137
vim/plugins/deoplete.nvim/rplugin/python3/deoplete/parent.py
Normal file
@ -0,0 +1,137 @@
|
||||
# ============================================================================
|
||||
# FILE: parent.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import time
|
||||
import os
|
||||
import msgpack
|
||||
import subprocess
|
||||
from functools import partial
|
||||
from queue import Queue
|
||||
|
||||
from deoplete import logger
|
||||
from deoplete.process import Process
|
||||
from deoplete.util import error_tb, error
|
||||
|
||||
dp_main = os.path.join(os.path.dirname(__file__), 'dp_main.py')
|
||||
|
||||
|
||||
class Parent(logger.LoggingMixin):
|
||||
|
||||
def __init__(self, vim, context):
|
||||
self.name = 'parent'
|
||||
|
||||
self._vim = vim
|
||||
self._hnd = None
|
||||
self._stdin = None
|
||||
self._child = None
|
||||
self._queue_id = ''
|
||||
self._prev_pos = []
|
||||
self._queue_in = Queue()
|
||||
self._queue_out = Queue()
|
||||
self._packer = msgpack.Packer(
|
||||
use_bin_type=True,
|
||||
encoding='utf-8',
|
||||
unicode_errors='surrogateescape')
|
||||
self._unpacker = msgpack.Unpacker(
|
||||
encoding='utf-8',
|
||||
unicode_errors='surrogateescape')
|
||||
self._start_process(context)
|
||||
|
||||
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 self._child:
|
||||
results = self._put('merge_results', [context])
|
||||
else:
|
||||
if 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])
|
||||
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):
|
||||
self._put('on_event', [context])
|
||||
|
||||
def _start_process(self, context):
|
||||
if self._vim.vars['deoplete#num_processes'] > 1:
|
||||
# Parallel
|
||||
|
||||
startupinfo = None
|
||||
if os.name == 'nt':
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
|
||||
self._hnd = self._vim.loop.create_task(
|
||||
self._vim.loop.subprocess_exec(
|
||||
partial(Process, self),
|
||||
self._vim.vars.get('python3_host_prog', 'python3'),
|
||||
dp_main,
|
||||
self._vim.vars['deoplete#_serveraddr'],
|
||||
stderr=None, cwd=context['cwd'], startupinfo=startupinfo))
|
||||
else:
|
||||
# Serial
|
||||
from deoplete.child import Child
|
||||
self._child = Child(self._vim)
|
||||
|
||||
def _put(self, name, args):
|
||||
queue_id = str(time.time())
|
||||
|
||||
if self._child:
|
||||
return self._child.main(name, args, queue_id)
|
||||
|
||||
if not self._hnd:
|
||||
return None
|
||||
|
||||
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 as e:
|
||||
error_tb(self._vim, 'Crash in child process')
|
||||
error(self._vim, 'stderr=' + str(self._proc.read_error()))
|
||||
self._hnd = None
|
||||
return queue_id
|
||||
|
||||
def _get(self, queue_id):
|
||||
if not self._hnd:
|
||||
return []
|
||||
|
||||
outs = []
|
||||
while not self._queue_out.empty():
|
||||
outs.append(self._queue_out.get_nowait())
|
||||
return [x for x in outs if x['queue_id'] == queue_id]
|
@ -0,0 +1,26 @@
|
||||
# ============================================================================
|
||||
# FILE: process.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import asyncio
|
||||
|
||||
|
||||
class Process(asyncio.SubprocessProtocol):
|
||||
|
||||
def __init__(self, plugin):
|
||||
self._plugin = plugin
|
||||
self._vim = plugin._vim
|
||||
|
||||
def connection_made(self, transport):
|
||||
self._plugin._stdin = transport.get_pipe_transport(0)
|
||||
|
||||
def pipe_data_received(self, fd, data):
|
||||
unpacker = self._plugin._unpacker
|
||||
unpacker.feed(data)
|
||||
for child_out in unpacker:
|
||||
self._plugin._queue_out.put(child_out)
|
||||
|
||||
def process_exited(self):
|
||||
pass
|
@ -0,0 +1,52 @@
|
||||
# ============================================================================
|
||||
# FILE: around.py
|
||||
# AUTHOR: Khalidov Oleg <brooth at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import re
|
||||
|
||||
from .base import Base
|
||||
from deoplete.util import parse_buffer_pattern, getlines
|
||||
|
||||
LINES_ABOVE = 20
|
||||
LINES_BELOW = 20
|
||||
|
||||
|
||||
class Source(Base):
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
self.name = 'around'
|
||||
self.mark = '[~]'
|
||||
self.rank = 300
|
||||
|
||||
def gather_candidates(self, context):
|
||||
line = context['position'][1]
|
||||
candidates = []
|
||||
|
||||
# lines above
|
||||
words = parse_buffer_pattern(
|
||||
reversed(getlines(self.vim, max([1, line - LINES_ABOVE]), line)),
|
||||
context['keyword_patterns'])
|
||||
candidates += [{'word': x, 'menu': 'A'} 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]:] for x
|
||||
in self.vim.call(
|
||||
'execute', 'changes').split('\n')
|
||||
if p.search(x)]:
|
||||
if change_line and change_line != '-invalid-':
|
||||
lines.add(change_line)
|
||||
|
||||
words = parse_buffer_pattern(lines, context['keyword_patterns'])
|
||||
candidates += [{'word': x, 'menu': 'C'} for x in words]
|
||||
|
||||
# lines below
|
||||
words = parse_buffer_pattern(
|
||||
getlines(self.vim, line, line + LINES_BELOW),
|
||||
context['keyword_patterns'])
|
||||
candidates += [{'word': x, 'menu': 'B'} for x in words]
|
||||
|
||||
return candidates
|
@ -0,0 +1,56 @@
|
||||
# ============================================================================
|
||||
# FILE: base.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import re
|
||||
from abc import abstractmethod
|
||||
from deoplete.logger import LoggingMixin
|
||||
from deoplete.util import debug, error_vim
|
||||
|
||||
|
||||
class Base(LoggingMixin):
|
||||
|
||||
def __init__(self, vim):
|
||||
self.vim = vim
|
||||
self.description = ''
|
||||
self.mark = ''
|
||||
self.max_pattern_length = 80
|
||||
self.input_pattern = ''
|
||||
self.matchers = ['matcher_fuzzy']
|
||||
self.sorters = ['sorter_rank']
|
||||
self.converters = [
|
||||
'converter_remove_overlap',
|
||||
'converter_truncate_abbr',
|
||||
'converter_truncate_kind',
|
||||
'converter_truncate_menu']
|
||||
self.filetypes = []
|
||||
self.debug_enabled = False
|
||||
self.is_bytepos = False
|
||||
self.is_initialized = False
|
||||
self.is_volatile = False
|
||||
self.is_silent = False
|
||||
self.rank = 100
|
||||
self.disabled_syntaxes = []
|
||||
self.events = None
|
||||
|
||||
def get_complete_position(self, context):
|
||||
m = re.search('(?:' + context['keyword_patterns'] + ')$',
|
||||
context['input'])
|
||||
return m.start() if m else -1
|
||||
|
||||
def print(self, expr):
|
||||
if not self.is_silent:
|
||||
debug(self.vim, expr)
|
||||
|
||||
def print_error(self, expr):
|
||||
if not self.is_silent:
|
||||
error_vim(self.vim, expr)
|
||||
|
||||
@abstractmethod
|
||||
def gather_candidates(self, context):
|
||||
pass
|
||||
|
||||
def on_event(self, context):
|
||||
pass
|
@ -0,0 +1,63 @@
|
||||
# ============================================================================
|
||||
# FILE: buffer.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from .base import Base
|
||||
|
||||
from deoplete.util import parse_buffer_pattern, getlines
|
||||
|
||||
|
||||
class Source(Base):
|
||||
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'buffer'
|
||||
self.mark = '[B]'
|
||||
self.events = ['Init', 'InsertEnter', 'BufWritePost']
|
||||
|
||||
self._limit = 1000000
|
||||
self._buffers = {}
|
||||
self._max_lines = 5000
|
||||
|
||||
def on_event(self, context):
|
||||
if (context['bufnr'] not in self._buffers
|
||||
or context['event'] == 'BufWritePost'):
|
||||
self._make_cache(context)
|
||||
|
||||
def gather_candidates(self, context):
|
||||
self.on_event(context)
|
||||
|
||||
tab_bufnrs = self.vim.call('tabpagebuflist')
|
||||
same_filetype = context['vars'].get(
|
||||
'deoplete#buffer#require_same_filetype', True)
|
||||
return {'sorted_candidates': [
|
||||
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):
|
||||
# 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.vim.eval('&l:filetype'),
|
||||
'candidates': [
|
||||
{'word': x} for x in
|
||||
sorted(parse_buffer_pattern(getlines(self.vim),
|
||||
context['keyword_patterns']),
|
||||
key=str.lower)
|
||||
]
|
||||
}
|
||||
except UnicodeDecodeError:
|
||||
return []
|
@ -0,0 +1,51 @@
|
||||
# ============================================================================
|
||||
# FILE: dictionary.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from os.path import getmtime, exists
|
||||
from collections import namedtuple
|
||||
from .base import Base
|
||||
|
||||
DictCacheItem = namedtuple('DictCacheItem', 'mtime candidates')
|
||||
|
||||
|
||||
class Source(Base):
|
||||
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'dictionary'
|
||||
self.mark = '[D]'
|
||||
self.events = ['Init', 'InsertEnter']
|
||||
|
||||
self._cache = {}
|
||||
|
||||
def on_event(self, context):
|
||||
self._make_cache(context)
|
||||
|
||||
def gather_candidates(self, 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)
|
||||
return {'sorted_candidates': candidates}
|
||||
|
||||
def _make_cache(self, context):
|
||||
for filename in self._get_dictionaries(context):
|
||||
mtime = getmtime(filename)
|
||||
if filename in self._cache and self._cache[
|
||||
filename].mtime == mtime:
|
||||
continue
|
||||
with open(filename, 'r', errors='replace') as f:
|
||||
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):
|
||||
return [x for x in context['dict__dictionary'].split(',')
|
||||
if exists(x)]
|
@ -0,0 +1,86 @@
|
||||
# ============================================================================
|
||||
# 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
|
||||
from os.path import exists, dirname
|
||||
from .base import Base
|
||||
from deoplete.util import expand
|
||||
|
||||
|
||||
class Source(Base):
|
||||
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'file'
|
||||
self.mark = '[F]'
|
||||
self.min_pattern_length = 0
|
||||
self.rank = 150
|
||||
self.events = ['Init', 'InsertEnter']
|
||||
|
||||
self._isfname = ''
|
||||
|
||||
def on_init(self, context):
|
||||
self._buffer_path = context['vars'].get(
|
||||
'deoplete#file#enable_buffer_path', 0)
|
||||
|
||||
def on_event(self, context):
|
||||
self._isfname = self.vim.call(
|
||||
'deoplete#util#vimoption2python_not',
|
||||
self.vim.options['isfname'])
|
||||
|
||||
def get_complete_position(self, context):
|
||||
pos = context['input'].rfind('/')
|
||||
return pos if pos < 0 else pos + 1
|
||||
|
||||
def gather_candidates(self, context):
|
||||
if not self._isfname:
|
||||
self.on_event(context)
|
||||
|
||||
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) + '/')
|
||||
if not os.path.isdir(complete_str):
|
||||
return []
|
||||
hidden = context['complete_str'].find('.') == 0
|
||||
contents = [[], []]
|
||||
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, input_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 None
|
||||
|
||||
def _substitute_path(self, context, path):
|
||||
m = re.match(r'(\.{1,2})/+', path)
|
||||
if m:
|
||||
if self._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,62 @@
|
||||
# ============================================================================
|
||||
# FILE: member.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from .base import Base
|
||||
|
||||
import re
|
||||
from deoplete.util import (
|
||||
get_buffer_config, convert2list,
|
||||
parse_buffer_pattern, set_pattern, getlines)
|
||||
|
||||
|
||||
class Source(Base):
|
||||
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'member'
|
||||
self.mark = '[M]'
|
||||
self.min_pattern_length = 0
|
||||
|
||||
self._object_pattern = r'[a-zA-Z_]\w*(?:\(\)?)?'
|
||||
self._prefix = ''
|
||||
|
||||
self._prefix_patterns = {}
|
||||
set_pattern(self._prefix_patterns,
|
||||
'_', '\.')
|
||||
set_pattern(self._prefix_patterns,
|
||||
'c,objc', ['\.', '->'])
|
||||
set_pattern(self._prefix_patterns,
|
||||
'cpp,objcpp', ['\.', '->', '::'])
|
||||
set_pattern(self._prefix_patterns,
|
||||
'perl,php', ['->'])
|
||||
set_pattern(self._prefix_patterns,
|
||||
'ruby', ['\.', '::'])
|
||||
set_pattern(self._prefix_patterns,
|
||||
'lua', ['\.', ':'])
|
||||
|
||||
def get_complete_position(self, context):
|
||||
# Check member prefix pattern.
|
||||
for prefix_pattern in convert2list(
|
||||
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*$',
|
||||
context['input'])
|
||||
if m is None or prefix_pattern == '':
|
||||
continue
|
||||
self._prefix = re.sub(r'\w*$', '', m.group(0))
|
||||
return re.search(r'\w*$', context['input']).start()
|
||||
return -1
|
||||
|
||||
def gather_candidates(self, context):
|
||||
return [{'word': x} for x in
|
||||
parse_buffer_pattern(
|
||||
getlines(self.vim),
|
||||
r'(?<=' + re.escape(self._prefix) + r')\w+'
|
||||
)
|
||||
if x != context['complete_str']]
|
@ -0,0 +1,98 @@
|
||||
# ============================================================================
|
||||
# FILE: omni.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import re
|
||||
from .base import Base
|
||||
from deoplete.util import (
|
||||
get_buffer_config, convert2list, set_pattern, convert2candidates)
|
||||
|
||||
|
||||
class Source(Base):
|
||||
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'omni'
|
||||
self.mark = '[O]'
|
||||
self.rank = 500
|
||||
self.is_bytepos = True
|
||||
self.min_pattern_length = 0
|
||||
|
||||
self._input_patterns = {}
|
||||
set_pattern(self._input_patterns, 'css,less,scss,sass',
|
||||
[r'\w{2}', r'\w+[):;]?\s*\w*', r'[@!]'])
|
||||
set_pattern(self._input_patterns, 'lua',
|
||||
[r'\w+[.:]\w*', r'require\s*\(?["'']\w*'])
|
||||
|
||||
def get_complete_position(self, context):
|
||||
current_ft = self.vim.eval('&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, current_ft, filetype):
|
||||
for omnifunc in convert2list(
|
||||
get_buffer_config(context, filetype,
|
||||
'deoplete_omni_functions',
|
||||
'deoplete#omni#functions',
|
||||
{'_': ''})):
|
||||
if omnifunc == '' and (filetype == current_ft or
|
||||
filetype in ['css', 'javascript']):
|
||||
omnifunc = context['omni__omnifunc']
|
||||
if omnifunc == '':
|
||||
continue
|
||||
self._omnifunc = omnifunc
|
||||
for input_pattern in convert2list(
|
||||
get_buffer_config(context, filetype,
|
||||
'deoplete_omni_input_patterns',
|
||||
'deoplete#omni#input_patterns',
|
||||
self._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 filetype == current_ft and self._omnifunc in [
|
||||
'ccomplete#Complete',
|
||||
'htmlcomplete#CompleteTags',
|
||||
'LanguageClient#complete',
|
||||
'phpcomplete#CompletePHP']:
|
||||
# In the blacklist
|
||||
return -1
|
||||
try:
|
||||
complete_pos = self.vim.call(self._omnifunc, 1, '')
|
||||
except Exception as e:
|
||||
self.print_error('Error occurred calling omnifunction: ' +
|
||||
self._omnifunc)
|
||||
return -1
|
||||
return complete_pos
|
||||
return -1
|
||||
|
||||
def gather_candidates(self, context):
|
||||
try:
|
||||
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)
|
||||
candidates = []
|
||||
|
||||
candidates = convert2candidates(candidates)
|
||||
|
||||
for candidate in candidates:
|
||||
candidate['dup'] = 1
|
||||
|
||||
return candidates
|
156
vim/plugins/deoplete.nvim/rplugin/python3/deoplete/source/tag.py
Normal file
156
vim/plugins/deoplete.nvim/rplugin/python3/deoplete/source/tag.py
Normal file
@ -0,0 +1,156 @@
|
||||
# ============================================================================
|
||||
# FILE: tag.py
|
||||
# AUTHOR: Felipe Morales <hel.sheep at gmail.com>
|
||||
# Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# Roxma <roxma at qq.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
from .base import Base
|
||||
|
||||
import re
|
||||
import os
|
||||
from os.path import exists, getsize
|
||||
|
||||
|
||||
class Source(Base):
|
||||
|
||||
def __init__(self, vim):
|
||||
super().__init__(vim)
|
||||
|
||||
self.name = 'tag'
|
||||
self.mark = '[T]'
|
||||
|
||||
def on_init(self, context):
|
||||
self._limit = context['vars'].get(
|
||||
'deoplete#tag#cache_limit_size', 500000)
|
||||
|
||||
def gather_candidates(self, context):
|
||||
candidates = []
|
||||
for filename in self._get_tagfiles(context):
|
||||
for line in binary_search_lines_by_prefix(
|
||||
context['complete_str'], filename):
|
||||
candidate = self._make_candidate(line)
|
||||
if candidate:
|
||||
candidates.append(candidate)
|
||||
return candidates
|
||||
|
||||
def _make_candidate(self, line):
|
||||
cols = line.strip().split('\t', 2)
|
||||
if not cols or cols[0].startswith('!_'):
|
||||
return {}
|
||||
|
||||
tagfield = {}
|
||||
if ';"' in cols[-1]:
|
||||
cols[-1], fields = cols[-1].split(';"', 1)
|
||||
for pair in fields.split('\t'):
|
||||
if ':' not in pair:
|
||||
tagfield['kind'] = pair
|
||||
else:
|
||||
k, v = pair.split(':', 1)
|
||||
tagfield[k] = v
|
||||
|
||||
kind = tagfield.get('kind', '')
|
||||
if kind == 'f':
|
||||
i = cols[2].find('(')
|
||||
if i != -1 and cols[2].find(')', i+1) != -1:
|
||||
m = re.search(r'(\w+\(.*\))', cols[2])
|
||||
if m:
|
||||
return {'word': cols[0], 'abbr': m.group(1), 'kind': kind}
|
||||
return {'word': cols[0], 'kind': kind}
|
||||
|
||||
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]
|
||||
|
||||
|
||||
def binary_search_lines_by_prefix(prefix, filename):
|
||||
|
||||
with open(filename, 'r', errors='ignore') as f:
|
||||
# to properly keep bounds of this loop it's important to understand
|
||||
# *exactly* what our variables mean.
|
||||
#
|
||||
# to make sure we process only full lines, we are going to always seek
|
||||
# to file position (x - 1), then skip partial or full line found there.
|
||||
# except for position 0 which we know belongs to full 1st line.
|
||||
|
||||
# each line (except 1st one) will have multiple corresponding seeking
|
||||
# positions.
|
||||
#
|
||||
# we are interested in finding such a seeking position for the first
|
||||
# line in the file that matches our prefix. (let's call it target)
|
||||
|
||||
# begin - guaranteed not to exceed our target position
|
||||
# e.g. (begin <= target) at any time.
|
||||
begin = 0
|
||||
|
||||
# end - guaranteed to be higher then at least one seeking position for
|
||||
# the target. e.g. (target < end) at any time.
|
||||
# Note that this means it can be below the actual target line
|
||||
f.seek(0, os.SEEK_END)
|
||||
end = f.tell()
|
||||
|
||||
while end > begin + 1:
|
||||
|
||||
# pos - current seeking position
|
||||
pos = int((begin + end) / 2) - 1
|
||||
|
||||
if pos == 0:
|
||||
f.seek(0, os.SEEK_SET)
|
||||
else:
|
||||
f.seek(pos - 1, os.SEEK_SET)
|
||||
f.readline() # skip partial line
|
||||
|
||||
line = f.readline()
|
||||
|
||||
l2 = f.tell() # start of next line (or end of file)
|
||||
|
||||
if l2 == 1:
|
||||
# this is a corner case of a single empty first line.
|
||||
# we mast advance here or we'll have an endless loop
|
||||
begin = 1
|
||||
next
|
||||
|
||||
if line:
|
||||
key = line[:len(prefix)]
|
||||
|
||||
if key < prefix:
|
||||
# we are strictly before the target line.
|
||||
# so it starts at least from l2
|
||||
begin = max(begin, l2)
|
||||
|
||||
elif key > prefix:
|
||||
# we are strictly past the target line.
|
||||
# our target seeking position is less than current pos
|
||||
end = pos
|
||||
else:
|
||||
# current line is a possible target. it's reachable from
|
||||
# current 'pos', so `target pos is <= current pos`, or
|
||||
# `target post < current post + 1`
|
||||
end = min(end, pos + 1)
|
||||
|
||||
else:
|
||||
# we reached end of file. our current seeking position doesn't
|
||||
# correspond to any line
|
||||
end = min(end, pos)
|
||||
|
||||
# now we are at a *seeking position* for the target line. need to skip
|
||||
# to the actual line
|
||||
if begin == 0:
|
||||
f.seek(0, os.SEEK_SET)
|
||||
else:
|
||||
f.seek(begin - 1, os.SEEK_SET)
|
||||
f.readline()
|
||||
|
||||
while True:
|
||||
line = f.readline()
|
||||
if line.startswith(prefix):
|
||||
yield line
|
||||
else:
|
||||
break
|
||||
return
|
308
vim/plugins/deoplete.nvim/rplugin/python3/deoplete/util.py
Normal file
308
vim/plugins/deoplete.nvim/rplugin/python3/deoplete/util.py
Normal file
@ -0,0 +1,308 @@
|
||||
# ============================================================================
|
||||
# FILE: util.py
|
||||
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
||||
# License: MIT license
|
||||
# ============================================================================
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import glob
|
||||
import traceback
|
||||
import unicodedata
|
||||
|
||||
from importlib.machinery import SourceFileLoader
|
||||
|
||||
|
||||
def get_buffer_config(context, filetype, buffer_var, user_var, default_var):
|
||||
if buffer_var in context['bufvars']:
|
||||
return context['bufvars'][buffer_var]
|
||||
|
||||
ft = filetype if (filetype in context['vars'][user_var] or
|
||||
filetype in default_var) else '_'
|
||||
default = default_var.get(ft, '')
|
||||
return context['vars'][user_var].get(ft, default)
|
||||
|
||||
|
||||
def get_simple_buffer_config(context, buffer_var, user_var):
|
||||
return (context['bufvars'][buffer_var]
|
||||
if buffer_var in context['bufvars']
|
||||
else context['vars'][user_var])
|
||||
|
||||
|
||||
def set_pattern(variable, keys, pattern):
|
||||
for key in keys.split(','):
|
||||
variable[key] = pattern
|
||||
|
||||
|
||||
def set_list(vim, variable, keys, list):
|
||||
return vim.call('deoplete#util#set_pattern', variable, keys, list)
|
||||
|
||||
|
||||
def set_default(vim, var, val):
|
||||
return vim.call('deoplete#util#set_default', var, val)
|
||||
|
||||
|
||||
def convert2list(expr):
|
||||
return (expr if isinstance(expr, list) else [expr])
|
||||
|
||||
|
||||
def convert2candidates(l):
|
||||
return ([{'word': x} for x in l]
|
||||
if l and isinstance(l, list) and isinstance(l[0], str) else l)
|
||||
|
||||
|
||||
def globruntime(runtimepath, path):
|
||||
ret = []
|
||||
for rtp in re.split(',', runtimepath):
|
||||
ret += glob.glob(rtp + '/' + path)
|
||||
return ret
|
||||
|
||||
|
||||
def find_rplugins(context, source):
|
||||
"""Search for base.py or *.py
|
||||
|
||||
Searches $VIMRUNTIME/*/rplugin/python3/deoplete/$source[s]/
|
||||
"""
|
||||
rtp = context.get('runtimepath', '').split(',')
|
||||
if not rtp:
|
||||
return
|
||||
|
||||
sources = (
|
||||
os.path.join('rplugin/python3/deoplete', source, 'base.py'),
|
||||
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 rtp:
|
||||
yield from glob.iglob(os.path.join(path, src))
|
||||
|
||||
|
||||
def import_plugin(path, source, classname):
|
||||
"""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)
|
||||
|
||||
module = SourceFileLoader(module_name, path).load_module()
|
||||
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, expr):
|
||||
if hasattr(vim, 'out_write'):
|
||||
string = (expr if isinstance(expr, str) else str(expr))
|
||||
return vim.out_write('[deoplete] ' + string + '\n')
|
||||
else:
|
||||
vim.call('deoplete#util#print_debug', expr)
|
||||
|
||||
|
||||
def error(vim, expr):
|
||||
if hasattr(vim, 'err_write'):
|
||||
string = (expr if isinstance(expr, str) else str(expr))
|
||||
return vim.err_write('[deoplete] ' + string + '\n')
|
||||
else:
|
||||
vim.call('deoplete#util#print_error', expr)
|
||||
|
||||
|
||||
def error_tb(vim, msg):
|
||||
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, msg):
|
||||
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):
|
||||
return expr.replace("'", "''")
|
||||
|
||||
|
||||
def charpos2bytepos(encoding, input, pos):
|
||||
return len(bytes(input[: pos], encoding, errors='replace'))
|
||||
|
||||
|
||||
def bytepos2charpos(encoding, input, pos):
|
||||
return len(bytes(input, encoding, errors='replace')[: pos].decode(
|
||||
encoding, errors='replace'))
|
||||
|
||||
|
||||
def get_custom(custom, source_name, key, default):
|
||||
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):
|
||||
return vim.call('deoplete#util#get_syn_names')
|
||||
|
||||
|
||||
def parse_file_pattern(f, pattern):
|
||||
p = re.compile(pattern)
|
||||
ret = []
|
||||
for l in f:
|
||||
ret += p.findall(l)
|
||||
return list(set(ret))
|
||||
|
||||
|
||||
def parse_buffer_pattern(b, pattern):
|
||||
return list(set(re.compile(pattern).findall('\n'.join(b))))
|
||||
|
||||
|
||||
def fuzzy_escape(string, camelcase):
|
||||
# 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:
|
||||
'['+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(file, module):
|
||||
current = os.path.dirname(os.path.abspath(file))
|
||||
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, max_width, footer, footer_len):
|
||||
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, max_width):
|
||||
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):
|
||||
width = 0
|
||||
for c in string:
|
||||
width += charwidth(c)
|
||||
return width
|
||||
|
||||
|
||||
def charwidth(c):
|
||||
wc = unicodedata.east_asian_width(c)
|
||||
return 2 if wc == 'F' or wc == 'W' else 1
|
||||
|
||||
|
||||
def expand(path):
|
||||
return os.path.expanduser(os.path.expandvars(path))
|
||||
|
||||
|
||||
def getlines(vim, start=1, end='$'):
|
||||
if end == '$':
|
||||
end = len(vim.current.buffer)
|
||||
max_len = min([end - start, 5000])
|
||||
lines = []
|
||||
current = start
|
||||
while current <= end:
|
||||
lines += vim.call('getline', current, current + max_len)
|
||||
current += max_len + 1
|
||||
return lines
|
||||
|
||||
|
||||
def binary_search_begin(l, prefix):
|
||||
if not l:
|
||||
return -1
|
||||
if len(l) == 1:
|
||||
return 0 if l[0]['word'].lower().startswith(prefix) else -1
|
||||
|
||||
s = 0
|
||||
e = len(l)
|
||||
prefix = prefix.lower()
|
||||
while s < e:
|
||||
index = int((s + e) / 2)
|
||||
word = l[index]['word'].lower()
|
||||
if word.startswith(prefix):
|
||||
if (index - 1 < 0 or not
|
||||
l[index-1]['word'].lower().startswith(prefix)):
|
||||
return index
|
||||
e = index
|
||||
elif prefix < word:
|
||||
e = index
|
||||
else:
|
||||
s = index + 1
|
||||
return -1
|
||||
|
||||
|
||||
def binary_search_end(l, prefix):
|
||||
if not l:
|
||||
return -1
|
||||
if len(l) == 1:
|
||||
return 0 if l[0]['word'].lower().startswith(prefix) else -1
|
||||
|
||||
s = 0
|
||||
e = len(l)
|
||||
prefix = prefix.lower()
|
||||
while s < e:
|
||||
index = int((s + e) / 2)
|
||||
word = l[index]['word'].lower()
|
||||
if word.startswith(prefix):
|
||||
if ((index + 1) >= len(l) or not
|
||||
l[index+1]['word'].lower().startswith(prefix)):
|
||||
return index
|
||||
s = index + 1
|
||||
elif prefix < word:
|
||||
e = index
|
||||
else:
|
||||
s = index + 1
|
||||
return -1
|
||||
|
||||
|
||||
def uniq_list_dict(l):
|
||||
# Uniq list of dictionaries
|
||||
ret = []
|
||||
for d in l:
|
||||
if d not in ret:
|
||||
ret.append(d)
|
||||
return ret
|
Reference in New Issue
Block a user