# vim:fileencoding=utf-8:noet _log = [] _g = {} _window = 0 _mode = 'n' _buf_purge_events = set() _options = { 'paste': 0, 'ambiwidth': 'single', } _last_bufnr = 0 _highlights = {} _thread_id = None def _set_thread_id(): global _thread_id from threading import current_thread _thread_id = current_thread().ident # Assuming import is done from the main thread _set_thread_id() def _vim(func): from functools import wraps from threading import current_thread @wraps(func) def f(*args, **kwargs): global _thread_id if _thread_id != current_thread().ident: raise RuntimeError('Accessing vim from separate threads is not allowed') _log.append((func.__name__, args)) return func(*args, **kwargs) return f class _Buffers(object): @_vim def __init__(self): self.d = {} @_vim def __getitem__(self, item): return self.d[item] @_vim def __setitem__(self, item, value): self.d[item] = value @_vim def __contains__(self, item): return item in self.d @_vim def __nonzero__(self): return not not self.d @_vim def keys(self): return self.d.keys() @_vim def pop(self, *args, **kwargs): return self.d.pop(*args, **kwargs) buffers = _Buffers() class _Windows(object): @_vim def __init__(self): self.l = [] @_vim def __getitem__(self, item): return self.l[item] @_vim def __setitem__(self, item, value): self.l[item] = value @_vim def __len__(self): return len(self.l) @_vim def __iter__(self): return iter(self.l) @_vim def __nonzero__(self): return not not self.l @_vim def pop(self, *args, **kwargs): return self.l.pop(*args, **kwargs) @_vim def append(self, *args, **kwargs): return self.l.append(*args, **kwargs) @_vim def index(self, *args, **kwargs): return self.l.index(*args, **kwargs) windows = _Windows() @_vim def _buffer(): return windows[_window - 1].buffer.number def _construct_result(r): import sys if sys.version_info < (3,): return r else: if type(r) is str: return r.encode('utf-8') elif type(r) is dict or type(r) is list: raise NotImplementedError return r def _str_func(func): from functools import wraps @wraps(func) def f(*args, **kwargs): return _construct_result(func(*args, **kwargs)) return f def _log_print(): import sys for entry in _log: sys.stdout.write(repr(entry) + '\n') @_vim def command(cmd): if cmd.startswith('let g:'): import re varname, value = re.compile(r'^let g:(\w+)\s*=\s*(.*)').match(cmd).groups() _g[varname] = value elif cmd.startswith('hi '): sp = cmd.split() _highlights[sp[1]] = sp[2:] else: raise NotImplementedError @_vim def eval(expr): if expr.startswith('g:'): return _g[expr[2:]] elif expr.startswith('&'): return _options[expr[1:]] elif expr.startswith('PowerlineRegisterCachePurgerEvent'): _buf_purge_events.add(expr[expr.find('"') + 1:expr.rfind('"') - 1]) return "0" raise NotImplementedError @_vim def bindeval(expr): if expr == 'g:': return _g import re match = re.compile(r'^function\("([^"\\]+)"\)$').match(expr) if match: return globals()['_emul_' + match.group(1)] else: raise NotImplementedError @_vim @_str_func def _emul_mode(*args): if args and args[0]: return _mode else: return _mode[0] @_vim @_str_func def _emul_getbufvar(bufnr, varname): if varname[0] == '&': if bufnr not in buffers: return '' try: return _buf_options[bufnr][varname[1:]] except KeyError: try: return _options[varname[1:]] except KeyError: return '' raise NotImplementedError @_vim @_str_func def _emul_getwinvar(winnr, varname): return _win_scopes[winnr][varname] @_vim def _emul_setwinvar(winnr, varname, value): _win_scopes[winnr][varname] = value @_vim def _emul_virtcol(expr): if expr == '.': return windows[_window - 1].cursor[1] + 1 raise NotImplementedError @_vim @_str_func def _emul_fnamemodify(path, modstring): import os _modifiers = { '~': lambda path: path.replace(os.environ['HOME'], '~') if path.startswith(os.environ['HOME']) else path, '.': lambda path: (lambda tpath: path if tpath[:3] == '..' + os.sep else tpath)(os.path.relpath(path)), 't': lambda path: os.path.basename(path), 'h': lambda path: os.path.dirname(path), } for mods in modstring.split(':')[1:]: path = _modifiers[mods](path) return path @_vim @_str_func def _emul_expand(expr): if expr == '': return _buffer() raise NotImplementedError @_vim def _emul_bufnr(expr): if expr == '$': return _last_bufnr raise NotImplementedError @_vim def _emul_exists(varname): if varname.startswith('g:'): return varname[2:] in _g raise NotImplementedError @_vim def _emul_line2byte(line): buflines = _buf_lines[_buffer()] if line == len(buflines) + 1: return sum((len(s) for s in buflines)) + 1 raise NotImplementedError _window_ids = [None] _window_id = 0 _win_scopes = [None] _win_options = [None] class _Window(object): def __init__(self, buffer=None, cursor=(1, 0), width=80): global _window_id self.cursor = cursor self.width = width if buffer: if type(buffer) is _Buffer: self.buffer = buffer else: self.buffer = _Buffer(**buffer) else: self.buffer = _Buffer() windows.append(self) _window_id += 1 _window_ids.append(_window_id) _win_scopes.append({}) _win_options.append({}) def __repr__(self): return '' _buf_scopes = {} _buf_options = {} _buf_lines = {} _undostate = {} _undo_written = {} class _Buffer(object): def __init__(self, name=None): global _last_bufnr import os _last_bufnr += 1 bufnr = _last_bufnr self.number = bufnr self.name = os.path.abspath(name) if name else None _buf_scopes[bufnr] = {} _buf_options[bufnr] = { 'modified': 0, 'readonly': 0, 'fileformat': 'unix', 'filetype': '', 'buftype': '', 'fileencoding': 'utf-8', } _buf_lines[bufnr] = [''] from copy import copy _undostate[bufnr] = [copy(_buf_lines[bufnr])] _undo_written[bufnr] = len(_undostate[bufnr]) buffers[bufnr] = self def __getitem__(self, line): return _buf_lines[self.number][line] def __setitem__(self, line, value): _buf_options[self.number]['modified'] = 1 _buf_lines[self.number][line] = value from copy import copy _undostate[self.number].append(copy(_buf_lines[self.number])) def __setslice__(self, *args): _buf_options[self.number]['modified'] = 1 _buf_lines[self.number].__setslice__(*args) from copy import copy _undostate[self.number].append(copy(_buf_lines[self.number])) def __getslice__(self, *args): return _buf_lines[self.number].__getslice__(*args) def __len__(self): return len(_buf_lines[self.number]) def __repr__(self): return '' def __del__(self): bufnr = self.number if _buf_options: _buf_options.pop(bufnr) _buf_lines.pop(bufnr) _undostate.pop(bufnr) _undo_written.pop(bufnr) _buf_scopes.pop(bufnr) class _Current(object): @property def buffer(self): return buffers[_buffer()] current = _Current() _dict = None @_vim def _init(): global _dict if _dict: return _dict _dict = {} for varname, value in globals().items(): if varname[0] != '_': _dict[varname] = value _new() return _dict @_vim def _get_segment_info(): mode_translations = { chr(ord('V') - 0x40): '^V', chr(ord('S') - 0x40): '^S', } mode = _mode mode = mode_translations.get(mode, mode) return { 'window': windows[_window - 1], 'buffer': buffers[_buffer()], 'bufnr': _buffer(), 'window_id': _window_ids[_window], 'mode': mode, } @_vim def _launch_event(event): pass @_vim def _start_mode(mode): global _mode if mode == 'i': _launch_event('InsertEnter') elif _mode == 'i': _launch_event('InsertLeave') _mode = mode @_vim def _undo(): if len(_undostate[_buffer()]) == 1: return _undostate[_buffer()].pop(-1) _buf_lines[_buffer()] = _undostate[_buffer()][-1] if _undo_written[_buffer()] == len(_undostate[_buffer()]): _buf_options[_buffer()]['modified'] = 0 @_vim def _edit(name=None): global _last_bufnr if _buffer() and buffers[_buffer()].name is None: buf = buffers[_buffer()] buf.name = name else: buf = _Buffer(name) windows[_window - 1].buffer = buf @_vim def _new(name=None): global _window _Window(buffer={'name': name}) _window = len(windows) @_vim def _del_window(winnr): win = windows.pop(winnr - 1) _win_scopes.pop(winnr) _win_options.pop(winnr) _window_ids.pop(winnr) return win @_vim def _close(winnr, wipe=True): global _window win = _del_window(winnr) if _window == winnr: _window = len(windows) if wipe: for w in windows: if w.buffer.number == win.buffer.number: break else: _bw(win.buffer.number) if not windows: _Window() @_vim def _bw(bufnr=None): bufnr = bufnr or _buffer() winnr = 1 for win in windows: if win.buffer.number == bufnr: _close(winnr, wipe=False) winnr += 1 buffers.pop(bufnr) if not buffers: _Buffer() _b(max(buffers.keys())) @_vim def _b(bufnr): windows[_window - 1].buffer = buffers[bufnr] @_vim def _set_cursor(line, col): windows[_window - 1].cursor = (line, col) if _mode == 'n': _launch_event('CursorMoved') elif _mode == 'i': _launch_event('CursorMovedI') @_vim def _get_buffer(): return buffers[_buffer()] @_vim def _set_bufoption(option, value, bufnr=None): _buf_options[bufnr or _buffer()][option] = value if option == 'filetype': _launch_event('FileType') class _WithNewBuffer(object): def __init__(self, func, *args, **kwargs): self.call = lambda: func(*args, **kwargs) def __enter__(self): self.call() self.bufnr = _buffer() return _get_segment_info() def __exit__(self, *args): _bw(self.bufnr) @_vim def _set_dict(d, new, setfunc=None): if not setfunc: def setfunc(k, v): d[k] = v old = {} na = [] for k, v in new.items(): try: old[k] = d[k] except KeyError: na.append(k) setfunc(k, v) return old, na class _WithBufOption(object): def __init__(self, **new): self.new = new def __enter__(self): self.bufnr = _buffer() self.old = _set_dict(_buf_options[self.bufnr], self.new, _set_bufoption)[0] def __exit__(self, *args): _buf_options[self.bufnr].update(self.old) class _WithMode(object): def __init__(self, new): self.new = new def __enter__(self): self.old = _mode _start_mode(self.new) return _get_segment_info() def __exit__(self, *args): _start_mode(self.old) class _WithDict(object): def __init__(self, d, **new): self.new = new self.d = d def __enter__(self): self.old, self.na = _set_dict(self.d, self.new) def __exit__(self, *args): self.d.update(self.old) for k in self.na: self.d.pop(k) @_vim def _with(key, *args, **kwargs): if key == 'buffer': return _WithNewBuffer(_edit, *args, **kwargs) elif key == 'mode': return _WithMode(*args, **kwargs) elif key == 'bufoptions': return _WithBufOption(**kwargs) elif key == 'options': return _WithDict(_options, **kwargs) elif key == 'globals': return _WithDict(_g, **kwargs)