You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

292 lines
8.8 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 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 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
all_maps = list(filter(len, eval(r"_tmp_smaps").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