# ============================================================================ # FILE: util.py # AUTHOR: Shougo Matsushita # 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