310 lines
9.4 KiB
Python
310 lines
9.4 KiB
Python
|
#!/usr/bin/env python
|
||
|
# encoding: utf-8
|
||
|
|
||
|
"""Wrapper functionality around the functions we need from Vim."""
|
||
|
|
||
|
import re
|
||
|
|
||
|
import vim # pylint:disable=import-error
|
||
|
from vim import error # pylint:disable=import-error,unused-import
|
||
|
|
||
|
from UltiSnips.compatibility import col2byte, byte2col, \
|
||
|
as_unicode, as_vimencoding
|
||
|
from UltiSnips.position import Position
|
||
|
|
||
|
from contextlib import contextmanager
|
||
|
|
||
|
|
||
|
class VimBuffer(object):
|
||
|
|
||
|
"""Wrapper around the current Vim buffer."""
|
||
|
|
||
|
def __getitem__(self, idx):
|
||
|
if isinstance(idx, slice): # Py3
|
||
|
return self.__getslice__(idx.start, idx.stop)
|
||
|
rv = vim.current.buffer[idx]
|
||
|
return as_unicode(rv)
|
||
|
|
||
|
def __getslice__(self, i, j): # pylint:disable=no-self-use
|
||
|
rv = vim.current.buffer[i:j]
|
||
|
return [as_unicode(l) for l in rv]
|
||
|
|
||
|
def __setitem__(self, idx, text):
|
||
|
if isinstance(idx, slice): # Py3
|
||
|
return self.__setslice__(idx.start, idx.stop, text)
|
||
|
vim.current.buffer[idx] = as_vimencoding(text)
|
||
|
|
||
|
def __setslice__(self, i, j, text): # pylint:disable=no-self-use
|
||
|
vim.current.buffer[i:j] = [as_vimencoding(l) for l in text]
|
||
|
|
||
|
def __len__(self):
|
||
|
return len(vim.current.buffer)
|
||
|
|
||
|
@property
|
||
|
def line_till_cursor(self): # pylint:disable=no-self-use
|
||
|
"""Returns the text before the cursor."""
|
||
|
_, col = self.cursor
|
||
|
return as_unicode(vim.current.line)[:col]
|
||
|
|
||
|
@property
|
||
|
def number(self): # pylint:disable=no-self-use
|
||
|
"""The bufnr() of the current buffer."""
|
||
|
return vim.current.buffer.number
|
||
|
|
||
|
@property
|
||
|
def filetypes(self):
|
||
|
return [ft for ft in vim.eval('&filetype').split('.') if ft]
|
||
|
|
||
|
@property
|
||
|
def cursor(self): # pylint:disable=no-self-use
|
||
|
"""The current windows cursor.
|
||
|
|
||
|
Note that this is 0 based in col and 0 based in line which is
|
||
|
different from Vim's cursor.
|
||
|
|
||
|
"""
|
||
|
line, nbyte = vim.current.window.cursor
|
||
|
col = byte2col(line, nbyte)
|
||
|
return Position(line - 1, col)
|
||
|
|
||
|
@cursor.setter
|
||
|
def cursor(self, pos): # pylint:disable=no-self-use
|
||
|
"""See getter."""
|
||
|
nbyte = col2byte(pos.line + 1, pos.col)
|
||
|
vim.current.window.cursor = pos.line + 1, nbyte
|
||
|
buf = VimBuffer() # pylint:disable=invalid-name
|
||
|
|
||
|
@contextmanager
|
||
|
def toggle_opt(name, new_value):
|
||
|
old_value = eval('&' + name)
|
||
|
command('set {0}={1}'.format(name, new_value))
|
||
|
try:
|
||
|
yield
|
||
|
finally:
|
||
|
command('set {0}={1}'.format(name, old_value))
|
||
|
|
||
|
@contextmanager
|
||
|
def save_mark(name):
|
||
|
old_pos = get_mark_pos(name)
|
||
|
try:
|
||
|
yield
|
||
|
finally:
|
||
|
if _is_pos_zero(old_pos):
|
||
|
delete_mark(name)
|
||
|
else:
|
||
|
set_mark_from_pos(name, old_pos)
|
||
|
|
||
|
def escape(inp):
|
||
|
"""Creates a vim-friendly string from a group of
|
||
|
dicts, lists and strings."""
|
||
|
def conv(obj):
|
||
|
"""Convert obj."""
|
||
|
if isinstance(obj, list):
|
||
|
rv = as_unicode('[' + ','.join(conv(o) for o in obj) + ']')
|
||
|
elif isinstance(obj, dict):
|
||
|
rv = as_unicode('{' + ','.join([
|
||
|
'%s:%s' % (conv(key), conv(value))
|
||
|
for key, value in obj.iteritems()]) + '}')
|
||
|
else:
|
||
|
rv = as_unicode('"%s"') % as_unicode(obj).replace('"', '\\"')
|
||
|
return rv
|
||
|
return conv(inp)
|
||
|
|
||
|
|
||
|
def command(cmd):
|
||
|
"""Wraps vim.command."""
|
||
|
return as_unicode(vim.command(as_vimencoding(cmd)))
|
||
|
|
||
|
|
||
|
def eval(text):
|
||
|
"""Wraps vim.eval."""
|
||
|
rv = vim.eval(as_vimencoding(text))
|
||
|
if not isinstance(rv, (dict, list)):
|
||
|
return as_unicode(rv)
|
||
|
return rv
|
||
|
|
||
|
|
||
|
def bindeval(text):
|
||
|
"""Wraps vim.bindeval."""
|
||
|
rv = vim.bindeval(as_vimencoding(text))
|
||
|
if not isinstance(rv, (dict, list)):
|
||
|
return as_unicode(rv)
|
||
|
return rv
|
||
|
|
||
|
|
||
|
def feedkeys(keys, mode='n'):
|
||
|
"""Wrapper around vim's feedkeys function.
|
||
|
|
||
|
Mainly for convenience.
|
||
|
|
||
|
"""
|
||
|
if eval('mode()') == 'n':
|
||
|
if keys == 'a':
|
||
|
cursor_pos = get_cursor_pos()
|
||
|
cursor_pos[2] = int(cursor_pos[2]) + 1
|
||
|
set_cursor_from_pos(cursor_pos)
|
||
|
if keys in 'ai':
|
||
|
keys = 'startinsert'
|
||
|
|
||
|
if keys == 'startinsert':
|
||
|
command('startinsert')
|
||
|
else:
|
||
|
command(as_unicode(r'call feedkeys("%s", "%s")') % (keys, mode))
|
||
|
|
||
|
|
||
|
def new_scratch_buffer(text):
|
||
|
"""Create a new scratch buffer with the text given."""
|
||
|
vim.command('botright new')
|
||
|
vim.command('set ft=')
|
||
|
vim.command('set buftype=nofile')
|
||
|
|
||
|
vim.current.buffer[:] = text.splitlines()
|
||
|
|
||
|
feedkeys(r"\<Esc>")
|
||
|
|
||
|
|
||
|
def virtual_position(line, col):
|
||
|
"""Runs the position through virtcol() and returns the result."""
|
||
|
nbytes = col2byte(line, col)
|
||
|
return line, int(eval('virtcol([%d, %d])' % (line, nbytes)))
|
||
|
|
||
|
|
||
|
def select(start, end):
|
||
|
"""Select the span in Select mode."""
|
||
|
_unmap_select_mode_mapping()
|
||
|
|
||
|
selection = eval('&selection')
|
||
|
|
||
|
col = col2byte(start.line + 1, start.col)
|
||
|
vim.current.window.cursor = start.line + 1, col
|
||
|
|
||
|
mode = eval('mode()')
|
||
|
|
||
|
move_cmd = ''
|
||
|
if mode != 'n':
|
||
|
move_cmd += r"\<Esc>"
|
||
|
|
||
|
if start == end:
|
||
|
# Zero Length Tabstops, use 'i' or 'a'.
|
||
|
if col == 0 or mode not in 'i' and \
|
||
|
col < len(buf[start.line]):
|
||
|
move_cmd += 'i'
|
||
|
else:
|
||
|
move_cmd += 'a'
|
||
|
else:
|
||
|
# Non zero length, use Visual selection.
|
||
|
move_cmd += 'v'
|
||
|
if 'inclusive' in selection:
|
||
|
if end.col == 0:
|
||
|
move_cmd += '%iG$' % end.line
|
||
|
else:
|
||
|
move_cmd += '%iG%i|' % virtual_position(end.line + 1, end.col)
|
||
|
elif 'old' in selection:
|
||
|
move_cmd += '%iG%i|' % virtual_position(end.line + 1, end.col)
|
||
|
else:
|
||
|
move_cmd += '%iG%i|' % virtual_position(end.line + 1, end.col + 1)
|
||
|
move_cmd += 'o%iG%i|o\\<c-g>' % virtual_position(
|
||
|
start.line + 1, start.col + 1)
|
||
|
feedkeys(move_cmd)
|
||
|
|
||
|
def set_mark_from_pos(name, pos):
|
||
|
return _set_pos("'" + name, pos)
|
||
|
|
||
|
def get_mark_pos(name):
|
||
|
return _get_pos("'" + name)
|
||
|
|
||
|
def set_cursor_from_pos(pos):
|
||
|
return _set_pos('.', pos)
|
||
|
|
||
|
def get_cursor_pos():
|
||
|
return _get_pos('.')
|
||
|
|
||
|
def delete_mark(name):
|
||
|
try:
|
||
|
return command('delma ' + name)
|
||
|
except:
|
||
|
return False
|
||
|
|
||
|
def _set_pos(name, pos):
|
||
|
return eval("setpos(\"{0}\", {1})".format(name, pos))
|
||
|
|
||
|
def _get_pos(name):
|
||
|
return eval("getpos(\"{0}\")".format(name))
|
||
|
|
||
|
def _is_pos_zero(pos):
|
||
|
return ['0'] * 4 == pos or [0] == pos
|
||
|
|
||
|
def _unmap_select_mode_mapping():
|
||
|
"""This function unmaps select mode mappings if so wished by the user.
|
||
|
|
||
|
Removes select mode mappings that can actually be typed by the user
|
||
|
(ie, ignores things like <Plug>).
|
||
|
|
||
|
"""
|
||
|
if int(eval('g:UltiSnipsRemoveSelectModeMappings')):
|
||
|
ignores = eval('g:UltiSnipsMappingsToIgnore') + ['UltiSnips']
|
||
|
|
||
|
for option in ('<buffer>', ''):
|
||
|
# Put all smaps into a var, and then read the var
|
||
|
command(r"redir => _tmp_smaps | silent smap %s " % option +
|
||
|
'| redir END')
|
||
|
|
||
|
# Check if any mappings where found
|
||
|
if hasattr(vim, 'bindeval'):
|
||
|
# Safer to use bindeval, if it exists, because it can deal with
|
||
|
# non-UTF-8 characters in mappings; see GH #690.
|
||
|
all_maps = bindeval(r"_tmp_smaps")
|
||
|
else:
|
||
|
all_maps = eval(r"_tmp_smaps")
|
||
|
all_maps = list(filter(len, all_maps.splitlines()))
|
||
|
if len(all_maps) == 1 and all_maps[0][0] not in ' sv':
|
||
|
# "No maps found". String could be localized. Hopefully
|
||
|
# it doesn't start with any of these letters in any
|
||
|
# language
|
||
|
continue
|
||
|
|
||
|
# Only keep mappings that should not be ignored
|
||
|
maps = [m for m in all_maps if
|
||
|
not any(i in m for i in ignores) and len(m.strip())]
|
||
|
|
||
|
for map in maps:
|
||
|
# The first three chars are the modes, that might be listed.
|
||
|
# We are not interested in them here.
|
||
|
trig = map[3:].split()[0] if len(
|
||
|
map[3:].split()) != 0 else None
|
||
|
|
||
|
if trig is None:
|
||
|
continue
|
||
|
|
||
|
# The bar separates commands
|
||
|
if trig[-1] == '|':
|
||
|
trig = trig[:-1] + '<Bar>'
|
||
|
|
||
|
# Special ones
|
||
|
if trig[0] == '<':
|
||
|
add = False
|
||
|
# Only allow these
|
||
|
for valid in ['Tab', 'NL', 'CR', 'C-Tab', 'BS']:
|
||
|
if trig == '<%s>' % valid:
|
||
|
add = True
|
||
|
if not add:
|
||
|
continue
|
||
|
|
||
|
# UltiSnips remaps <BS>. Keep this around.
|
||
|
if trig == '<BS>':
|
||
|
continue
|
||
|
|
||
|
# Actually unmap it
|
||
|
try:
|
||
|
command('silent! sunmap %s %s' % (option, trig))
|
||
|
except: # pylint:disable=bare-except
|
||
|
# Bug 908139: ignore unmaps that fail because of
|
||
|
# unprintable characters. This is not ideal because we
|
||
|
# will not be able to unmap lhs with any unprintable
|
||
|
# character. If the lhs stats with a printable
|
||
|
# character this will leak to the user when he tries to
|
||
|
# type this character as a first in a selected tabstop.
|
||
|
# This case should be rare enough to not bother us
|
||
|
# though.
|
||
|
pass
|