1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-23 10:20:05 +08:00
SpaceVim/bundle/deoplete.nvim/rplugin/python3/deoplete/util.py
2022-04-23 12:09:55 +08:00

312 lines
8.6 KiB
Python

# ============================================================================
# FILE: util.py
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
# License: MIT license
# ============================================================================
from os.path import expandvars
from pathlib import Path
from pynvim import Nvim
from pynvim.api import Buffer
import glob
import importlib.util
import re
import sys
import traceback
import typing
import unicodedata
UserContext = typing.Dict[str, typing.Any]
Candidate = typing.Dict[str, typing.Any]
Candidates = typing.List[Candidate]
def set_pattern(variable: typing.Dict[str, str],
keys: str, pattern: typing.Any) -> None:
for key in keys.split(','):
variable[key] = pattern
def convert2list(expr: typing.Any) -> typing.List[typing.Any]:
return (expr if isinstance(expr, list) else [expr])
def convert2candidates(li: typing.Any) -> Candidates:
ret = []
if li and isinstance(li, list):
for x in li:
if isinstance(x, str):
ret.append({'word': x})
else:
ret.append(x)
else:
ret = li
return ret
def globruntime(runtimepath: str, path: str) -> typing.List[str]:
ret: typing.List[str] = []
for rtp in runtimepath.split(','):
ret += glob.glob(rtp + '/' + path)
return ret
def import_plugin(path: str, source: str,
classname: str) -> typing.Optional[typing.Any]:
"""Import Deoplete plugin source class.
If the class exists, add its directory to sys.path.
"""
name = str(Path(path).name)[: -3]
module_name = 'deoplete.%s.%s' % (source, name)
spec = importlib.util.spec_from_file_location(module_name, path)
if not spec:
return None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) # type: ignore
cls = getattr(module, classname, None)
if not cls:
return None
dirname = str(Path(path).parent)
if dirname not in sys.path:
sys.path.insert(0, dirname)
return cls
def debug(vim: Nvim, expr: typing.Any) -> None:
if hasattr(vim, 'out_write'):
vim.out_write(f'[deoplete] {expr}\n')
else:
vim.call('deoplete#util#print_debug', expr)
def error(vim: Nvim, expr: typing.Any) -> None:
if hasattr(vim, 'err_write'):
vim.err_write(f'[deoplete] {expr}\n')
else:
vim.call('deoplete#util#print_error', expr)
def error_tb(vim: Nvim, msg: str) -> None:
lines: typing.List[str] = []
t, v, tb = sys.exc_info()
if t and v and tb:
lines += traceback.format_exc().splitlines()
lines += ['%s Use :messages / see above for error details.' % msg]
if hasattr(vim, 'err_write'):
vim.err_write('[deoplete] %s\n' % '\n'.join(lines))
else:
for line in lines:
vim.call('deoplete#util#print_error', line)
def error_vim(vim: Nvim, msg: str) -> None:
throwpoint = vim.eval('v:throwpoint')
if throwpoint != '':
error(vim, 'v:throwpoint = ' + throwpoint)
exception = vim.eval('v:exception')
if exception != '':
error(vim, 'v:exception = ' + exception)
error_tb(vim, msg)
def escape(expr: str) -> str:
return expr.replace("'", "''")
def charpos2bytepos(encoding: str, text: str, pos: int) -> int:
return len(bytes(text[: pos], encoding, errors='replace'))
def bytepos2charpos(encoding: str, text: str, pos: int) -> int:
return len(bytes(text, encoding, errors='replace')[: pos].decode(
encoding, errors='replace'))
def get_custom(custom: typing.Dict[str, typing.Any],
source_name: str, key: str,
default: typing.Any) -> typing.Any:
custom_source = custom['source']
if source_name not in custom_source:
return get_custom(custom, '_', key, default)
elif key in custom_source[source_name]:
return custom_source[source_name][key]
elif key in custom_source['_']:
return custom_source['_'][key]
else:
return default
def get_syn_names(vim: Nvim) -> typing.List[str]:
return list(vim.call('deoplete#util#get_syn_names'))
def parse_file_pattern(f: typing.Iterable[str],
pattern: str) -> typing.List[str]:
p = re.compile(pattern)
ret: typing.List[str] = []
for li in f:
ret += p.findall(li)
return list(set(ret))
def parse_buffer_pattern(b: Buffer, pattern: str) -> typing.List[str]:
return list(set(re.compile(pattern).findall('\n'.join(b))))
def fuzzy_escape(string: str, camelcase: bool) -> str:
# Escape string for python regexp.
p = re.sub(r'([a-zA-Z0-9_])', r'\1.*', re.escape(string))
if camelcase and re.search(r'[A-Z]', string):
p = re.sub(r'([a-z])', (lambda pat:
f'[{pat.group(1)}{pat.group(1).upper()}]'), p)
p = re.sub(r'([a-zA-Z0-9_])\.\*', r'\1[^\1]*', p)
return p
def load_external_module(base: str, module: str) -> None:
current = Path(base).parent.resolve()
module_dir = str(current.parent.joinpath(module))
if module_dir not in sys.path:
sys.path.insert(0, module_dir)
def truncate_skipping(string: str, max_width: int,
footer: str, footer_len: int) -> str:
if not string:
return ''
if len(string) <= max_width / 2:
return string
if strwidth(string) <= max_width:
return string
footer += string[
-len(truncate(string[::-1], footer_len)):]
return truncate(string, max_width - strwidth(footer)) + footer
def truncate(string: str, max_width: int) -> str:
if len(string) <= max_width / 2:
return string
if strwidth(string) <= max_width:
return string
width = 0
ret = ''
for c in string:
wc = charwidth(c)
if width + wc > max_width:
break
ret += c
width += wc
return ret
def strwidth(string: str) -> int:
width = 0
for c in string:
width += charwidth(c)
return width
def charwidth(c: str) -> int:
wc = unicodedata.east_asian_width(c)
return 2 if wc == 'F' or wc == 'W' else 1
def expand(path: str) -> str:
if path.startswith('~'):
try:
path = str(Path(path).expanduser())
except Exception:
pass
return expandvars(path)
def exists_path(path: str) -> bool:
try:
return Path(path).exists()
except Exception:
pass
return False
def getlines(vim: Nvim, start: int = 1,
end: typing.Union[int, str] = '$') -> typing.List[str]:
if end == '$':
end = len(vim.current.buffer)
max_len = min([int(end) - start, 5000])
lines: typing.List[str] = []
current = start
while current <= int(end):
# Skip very long lines
lines += [x for x in vim.call('getline', current, current + max_len)
if len(x) < 300]
current += max_len + 1
return lines
def binary_search_begin(li: typing.List[Candidates], prefix: str) -> int:
if not li:
return -1
if len(li) == 1:
word = li[0]['word'] # type: ignore
return 0 if word.lower().startswith(prefix) else -1
s = 0
e = len(li)
prefix = prefix.lower()
while s < e:
index = int((s + e) / 2)
word = li[index]['word'].lower() # type: ignore
if word.startswith(prefix):
if (index - 1) < 0:
return index
prev_word = li[index-1]['word'] # type: ignore
if not prev_word.lower().startswith(prefix):
return index
e = index
elif prefix < word:
e = index
else:
s = index + 1
return -1
def binary_search_end(li: typing.List[Candidates], prefix: str) -> int:
if not li:
return -1
if len(li) == 1:
word = li[0]['word'] # type: ignore
return 0 if word.lower().startswith(prefix) else -1
s = 0
e = len(li)
prefix = prefix.lower()
while s < e:
index = int((s + e) / 2)
word = li[index]['word'].lower() # type: ignore
if word.startswith(prefix):
if (index + 1) >= len(li):
return index
next_word = li[index+1]['word'] # type: ignore
if not next_word.lower().startswith(prefix):
return index
s = index + 1
elif prefix < word:
e = index
else:
s = index + 1
return -1
def uniq_list_dict(li: typing.List[typing.Any]) -> typing.List[typing.Any]:
# Uniq list of dictionaries
ret: typing.List[typing.Any] = []
for d in li:
if d not in ret:
ret.append(d)
return ret