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

312 lines
8.6 KiB
Python
Raw Normal View History

2020-06-18 23:07:37 +08:00
# ============================================================================
# FILE: util.py
# AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
# License: MIT license
# ============================================================================
2022-04-23 12:09:55 +08:00
from os.path import expandvars
from pathlib import Path
from pynvim import Nvim
from pynvim.api import Buffer
2020-06-18 23:07:37 +08:00
import glob
import importlib.util
2022-04-23 12:09:55 +08:00
import re
import sys
2020-06-18 23:07:37 +08:00
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.
"""
2022-04-23 12:09:55 +08:00
name = str(Path(path).name)[: -3]
2020-06-18 23:07:37 +08:00
module_name = 'deoplete.%s.%s' % (source, name)
spec = importlib.util.spec_from_file_location(module_name, path)
2022-04-23 12:09:55 +08:00
if not spec:
return None
2020-06-18 23:07:37 +08:00
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) # type: ignore
cls = getattr(module, classname, None)
if not cls:
return None
2022-04-23 12:09:55 +08:00
dirname = str(Path(path).parent)
2020-06-18 23:07:37 +08:00
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()
2022-04-23 12:09:55 +08:00
lines += ['%s Use :messages / see above for error details.' % msg]
2020-06-18 23:07:37 +08:00
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
2022-04-23 12:09:55 +08:00
def get_syn_names(vim: Nvim) -> typing.List[str]:
return list(vim.call('deoplete#util#get_syn_names'))
2020-06-18 23:07:37 +08:00
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:
2022-04-23 12:09:55 +08:00
current = Path(base).parent.resolve()
module_dir = str(current.parent.joinpath(module))
2020-06-18 23:07:37 +08:00
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:
2022-04-23 12:09:55 +08:00
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
2020-06-18 23:07:37 +08:00
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):
2022-04-23 12:09:55 +08:00
# Skip very long lines
lines += [x for x in vim.call('getline', current, current + max_len)
if len(x) < 300]
2020-06-18 23:07:37 +08:00
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