r"""
Place this script in your the vimrc to inspect attributes of the real vim
python module. This can be used to make this module more accurately reflect the
real vim object.
python << endpython
def attrinfo(basename, base, attrname):
attr = getattr(base, attrname)
print('\n------')
print('%s.%s: %r' % (basename, attrname, attr))
print('type(%s.%s): %r' % (basename, attrname, type(attr)))
print('dir(%s.%s): %r' % (basename, attrname, dir(attr)))
print('\n\n VIM BASE')
print('vim = %r' % (vim,))
print('type(vim) = %r' % (type(vim),))
print('dir(vim) = %r' % (dir(vim),))
print('\n\n VIM CURRENT')
attrinfo('vim', vim, 'current')
attrinfo('vim.current', vim.current, 'tabpage')
attrinfo('vim.current', vim.current, 'line')
attrinfo('vim.current', vim.current, 'window')
attrinfo('vim.current', vim.current, 'range')
attrinfo('vim.current', vim.current, 'buffer')
attrinfo('vim.current.buffer', vim.current.buffer, 'number')
attrinfo('vim.current.buffer', vim.current.buffer, 'name')
attrinfo('vim.current.buffer', vim.current.buffer, 'options')
attrinfo('vim.current.buffer', vim.current.buffer, 'range')
attrinfo('vim.current.buffer', vim.current.buffer, 'valid')
attrinfo('vim.current.buffer', vim.current.buffer, 'vars')
attrinfo('vim.current.buffer', vim.current.buffer, 'mark')
endpython
"""
[docs]
class VimErrorMock(Exception):
pass
[docs]
class LineMock(object):
pass
[docs]
def classname(obj):
return obj.__class__.__name__
import ubelt as ub # NOQA
[docs]
class BufferMock(ub.NiceRepr):
"""
Attributes of the REAL vim.buffer object
['__dir__', '__members__', 'append', 'mark', 'name', 'number', 'options',
'range', 'valid', 'vars']
"""
def __init__(self, text=None):
self._lines = None
self.name = ''
self.number = 1
self.range = RangeMock
self.valid = False
# self.options = OptionsMock
# self.vars = DictionaryMock()
# maps from mark chars to positions
self._marked_lines = {} # type : Dict[str: Tuple[int, int]]
self.setup_text(text)
def __nice__(self):
return self.name
[docs]
def _setmark(self, key, pos):
self._marked_lines[key] = pos
[docs]
def _visual_select(self, row1, row2, col1=0, col2=None):
"""
first and last line to select inclusive
"""
assert row1 > 0
assert row2 > 0
if col2 is None:
# col2 = len(self[row2 - 1]) - 1
import sys
col2 = sys.maxsize - 1
self._setmark('<', (row1, col1))
self._setmark('>', (row2, col2))
[docs]
def mark(self, key):
"""
Return the 1-based line number of a mark
"""
return self._marked_lines.get(key, None)
@property
def _text(self):
return '\n'.join(self._lines)
def __delitem__(self, key):
del self._lines[key]
def __getitem__(self, key):
# Note indexing into a buffer is zero indexed like normal
# However remember reported cursor positions are 1 indexed
if isinstance(key, slice):
return self._lines[key.start : key.stop : key.step]
if not isinstance(key, int):
raise TypeError("Index should be integer, not %s" % classname(key))
return self._lines[key]
def __setitem__(self, key, value):
if isinstance(key, slice):
self._lines[key.start : key.stop : key.step] = value
elif not isinstance(key, int):
raise TypeError("Indes should be integer, not %s" % classname(key))
self._lines[key] = value
def __len__(self):
return len(self._lines)
[docs]
def setup_text(self, text=None, name=''):
text = text or ''
self._lines = text.splitlines()
self.valid = True
self.name = name
[docs]
def open_file(self, filepath):
with open(filepath, 'r') as file_:
self._lines = file_.read().splitlines()
self.valid = True
self.name = filepath
[docs]
def append(self, other):
""" the vim buffer append is actually an extend call """
self._lines.extend(other)
[docs]
class WindowMock(object):
""""
RealObjectInfo:
vim.current.window: <window 0>
type(vim.current.window): <type 'vim.window'>
dir(vim.current.window): ['__dir__', '__members__', 'buffer', 'col',
'cursor', 'height', 'number', 'options', 'row', 'tabpage', 'valid',
'vars']
"""
def __init__(self, cursor=None):
# Note: weirdly rows are 1 indexed by columns are 0 indexed
self.cursor = cursor or (1, 0)
[docs]
class RangeMock(object):
"""
RealObjectInfo:
vim.current.range: <range (1:1)>
type(vim.current.range): <type 'vim.range'>
dir(vim.current.range): ['__dir__', '__members__', 'append', 'end', 'start']
"""
pass
[docs]
class TabPageMock(object):
"""
RealObjectInfo:
vim.current.tabpage: <tabpage 0>
type(vim.current.tabpage): <type 'vim.tabpage'>
dir(vim.current.tabpage): ['__dir__', '__members__', 'number', 'valid',
'vars', 'window', 'windows']
"""
pass
[docs]
class CurrentMock(object):
"""
RealObjectInfo:
vim.current: <vim.currentdata object at 0x8718a0>
type(vim.current): <type 'vim.currentdata'>
dir(vim.current): ['__dir__', '__members__', 'buffer', 'line', 'range', 'tabpage', 'window']
"""
def __init__(self, text=None):
self.line = LineMock()
self.buffer = BufferMock(text)
self.window = WindowMock()
self.range = RangeMock()
self.tabpage = TabPageMock()
[docs]
class VimMock(object):
"""
The real vim module is defined in the c source code (if_python.c,
if_python3.c, if_py_both.h, etc...) in the vim package. Therefore it is difficult to
replicate its behavior exactly.
vim = <module 'vim' (built-in)>
type(vim) = <type 'module'>
The Attributes of the REAL vim module are:
{
# Vim Types
'Buffer': vim.buffer,
'Dictionary': vim.dictionary,
'Function': vim.function,
'List': vim.list,
'Options': vim.options,
'Range': vim.range,
'TabPage': vim.tabpage,
'Window': vim.window,
'_Loader': vim.Loader,
# Members of numeric_constant
'VAR_DEF_SCOPE': 2,
'VAR_FIXED': 2,
'VAR_LOCKED': 1,
'VAR_SCOPE': 1,
'VIM_SPECIAL_PATH': '_vim_path_',
# Members of object_constant
'buffers': <vim.bufferlist object at 0x8718d0>,
'windows': <vim.windowlist object at 0x8718b0>,
'current': <vim.currentdata object at 0x8718a0>,
'tabpages': <vim.tabpagelist object at 0x871890>,
# The vim module definitions
'command': <built-in function command>,
'eval': <built-in function eval>,
'bindeval': <built-in function bindeval>,
'strwidth': <built-in function strwidth>,
'chdir': <built-in function chdir>,
'fchdir': <built-in function fchdir>,
'foreach_rtp': <built-in function foreach_rtp>,
'find_module': <built-in function find_module>,
'path_hook': <built-in function path_hook>,
'_get_paths': <built-in function _get_paths>,
# Python versions of redefined functions
'_chdir': <built-in function chdir>,
'_fchdir': <built-in function fchdir>,
'_find_module': <built-in function find_module>,
'_getcwd': <built-in function getcwd>,
'_load_module': <built-in function load_module>,
# Checked objects
'options': <vim.options object at 0x7fae81970c30>,
'vars': <vim.dictionary object at 0x7fae8196dc90>,
'vvars': <vim.dictionary object at 0x7fae8196dcc0>,
# Error definition
'error': vim.error,
'__doc__': None,
'__name__': 'vim',
'__package__': None,
# The module also defines these attributes, but they are just imprted from
# other places
'os': <module 'os' from '/usr/lib/python2.7/os.pyc'>,
}
"""
VAR_DEF_SCOPE = 2
VAR_FIXED = 2
VAR_LOCKED = 1
VAR_SCOPE = 1
VIM_SPECIAL_PATH = '_vim_path_'
Buffer = BufferMock
Range = RangeMock
TabPage = TabPageMock
Window = WindowMock
# Dictionary = vim.dictionary
# Function = vim.function
# List = vim.list
# Options = vim.options
# _Loader = vim.Loader
error = VimErrorMock
vim_mode_codes = {
'n' : 'Normal',
'no' : 'NOperatorPending',
'v' : 'Visual',
'V' : 'VLine',
#'^V' : 'VBlock',
's' : 'Select',
'S' : 'SLine',
#'^S' : 'SBlock',
'i' : 'Insert',
'R' : 'Replace',
'Rv' : 'VReplace',
'c' : 'Command',
'cv' : 'VimEx',
'ce' : 'Ex',
'r' : 'Prompt',
'rm' : 'More',
'r?' : 'Confirm',
'!' : 'Shell',
}
def __init__(self):
self.current = CurrentMock()
self.buffers = [self.current.buffer] # vim.bufferlist
self.windows = [self.current.window] # vim.bufferlist
self.tabpages = [self.current.tabpage] # vim.tabpagelist
self.global_variables = {}
self._function_stack = []
self._mode = 'n'
[docs]
def _push_function_stack(self, name, named={}, positional=[]):
"""
Simulate being inside a vim function
"""
stack_frame = {
'func_name': name,
# The args is put in a variable named "a"
# https://learnvim.irian.to/vimscript/vimscript_functions
# TODO: figure out and mock the exact behavior
# This it the a: scope
'args': {
'positional': positional,
'named': named,
}
}
self._function_stack.append(stack_frame)
[docs]
def setup_text(self, text, name=''):
"""
special mock-only function to put text into the buffer
"""
self.current.buffer.setup_text(text, name)
[docs]
def move_cursor(self, row, col=0):
"""
Move the cursor to a particular row / column
Note:
rows are 1 indexed but columns are 0 indexed
"""
if row <= 0:
raise Exception('rows are 1 indexed')
if col < 0:
raise Exception('cols are 0 indexed')
self.current.window.cursor = (row, col)
[docs]
def open_file(self, filepath, cursor=None):
"""
special mock-only function to put text into the buffer
"""
# Create a buffer and set it as the current buffer
new_buffer = BufferMock()
new_buffer.open_file(filepath)
self.buffers.append(new_buffer)
self.current.buffer = new_buffer
self.current.window.cursor = cursor or (0, 0)
[docs]
def command(self, command):
"""
Hack that pretends to "execute" a vim command
"""
if command == 'ESC':
# Switch to normal mode
self._mode = 'n'
else:
raise NotImplementedError(command)
[docs]
def eval(self, command):
"""
A very hack, and very specific implementation of vim eval for tests.
This only handles very specific commands.
"""
# print('command = {!r}'.format(command))
if command == '&ft':
from os.path import splitext
return splitext(self.current.buffer.name)[1].lstrip('.')
if command == 'mode()':
return self._mode
if command.startswith('let '):
return self._eval_assignment(command)
if command.startswith('exists(') and command.endswith(')'):
arg = command[8:-2]
return arg in self.global_variables
if command.startswith('get(') and command.endswith(')'):
inner = command[4:-1]
context, arg = inner.split(':, ')
varkey = arg.strip('"')
varname = '{}:{}'.format(context, varkey)
return self.global_variables[varname]
hard_coded_commands = {
'jedi#_vim_exceptions("&encoding", 1)': {'result': 'utf-8'},
'&encoding': 'utf-8',
':set nofoldenable': '',
}
if command in hard_coded_commands:
return hard_coded_commands[command]
if command.startswith('a:'):
# raise a vim.error if the variable does not exist
if not self._function_stack:
raise self.error('Vim:E121: Undefined variable: {}'.format(command))
# Probably trying to grab a function arg
# https://learnvimscriptthehardway.stevelosh.com/chapters/24.html
# https://learnvim.irian.to/vimscript/vimscript_variable_scopes#variable-scopes
suffix = command[2:]
try:
index = int(suffix)
except Exception:
index = NotImplemented
if isinstance(index, int):
stack_frame = self._function_stack[-1]
argv = stack_frame['args']['positional']
if index == 0:
return len(argv)
else:
return argv[index - 1]
else:
raise NotImplementedError('only positional for now')
raise NotImplementedError('eval not generally implemented for {}'.format(command))
# maybe :e will call open_file?
[docs]
def _eval_assignment(self, command):
"""
References:
* https://github.com/vim-jp/vim-vimlparser
* https://github.com/Vimjas/vint/blob/master/vint/ast/parsing.py
Ignore:
# TODO: use a real vim parser
from vint.ast import parsing
parser = parsing.Parser()
tree = parser.parse("let g:myvar = ['a', 'b', 'c']")
tree['body'][0]['right']['value']
command = "let g:myvar = ['a', 'b', 'c']"
"""
prefix = 'let '
assert command.startswith(prefix)
remain = command[len(prefix):]
lhs, rhs = remain.split('=', 1)
lhs = lhs.strip()
rhs = rhs.strip()
context = None
varname = lhs
if ':' in lhs:
context, key = varname.split(':', 1)
assert context == 'g', 'only globals supported for now'
import ast
varvalue = ast.literal_eval(rhs)
self.global_variables[varname] = varvalue