From e8a48909eabbfcc6693590fa6b42eade6431c2ca Mon Sep 17 00:00:00 2001 From: cathook Date: Wed, 19 Nov 2014 19:20:55 +0800 Subject: Rename the Shared_Vim to ShrVim --- server/src/cmd_ui.py | 10 +- server/src/shared_vim_server.py | 120 ----- server/src/shrvim_server.py | 120 +++++ vim/plugin/shared_vim.vim | 1039 --------------------------------------- vim/plugin/shrvim.vim | 1039 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 1164 insertions(+), 1164 deletions(-) delete mode 100755 server/src/shared_vim_server.py create mode 100755 server/src/shrvim_server.py delete mode 100644 vim/plugin/shared_vim.vim create mode 100644 vim/plugin/shrvim.vim diff --git a/server/src/cmd_ui.py b/server/src/cmd_ui.py index 94cbf61..dba21ba 100644 --- a/server/src/cmd_ui.py +++ b/server/src/cmd_ui.py @@ -25,26 +25,26 @@ class CmdUI(cmd.Cmd): # pylint: disable=R0904 Attributes: _users_text_manager: An instance of UsersTextManager. _tcp_server: An instance of TcpServer. - _shared_vim_server: An instance of SharedVimServer. + _shrvim_server: An instance of ShrVimServer. _exit_flag: Whether this UI should stop or not. _thread: Instance of Thread. _init_cmds: Initialize commands. """ def __init__(self, - init_cmds, users_text_manager, tcp_server, shared_vim_server): + init_cmds, users_text_manager, tcp_server, shrvim_server): """Constructor. Args: init_cmds: Lists of commands to run after startup. users_text_manager: An instance of UsersTextManager. tcp_server: An instance of TCPServer. - shared_vim_server: An instance of SharedVimServer. + shrvim_server: An instance of ShrVimServer. """ super(CmdUI, self).__init__() self.prompt = PROMPT self._users_text_manager = users_text_manager self._tcp_server = tcp_server - self._shared_vim_server = shared_vim_server + self._shrvim_server = shrvim_server self._stop_flag = False self._thread = None self._init_cmds = init_cmds @@ -173,7 +173,7 @@ class CmdUI(cmd.Cmd): # pylint: disable=R0904 """Exits the program.""" try: _split_text(text, 0) - self._shared_vim_server.stop() + self._shrvim_server.stop() except _SplitTextError: self.write('Format error!\n' '[usage] exit\n') diff --git a/server/src/shared_vim_server.py b/server/src/shared_vim_server.py deleted file mode 100755 index 9ec7fcd..0000000 --- a/server/src/shared_vim_server.py +++ /dev/null @@ -1,120 +0,0 @@ -#! /usr/bin/env python3 - -"""Main thread.""" - -import signal -import sys -import threading -import log - -from cmd_ui import CmdUI -from tcp_server import TCPServer -from users_text_manager import UsersTextManager - - -class _ArgsError(Exception): - """Exception raised by _Args.""" - pass - -class _Args(object): - """Arguments of this program. - - Attributes: - port: Port number. - user_list_filename: Default user list. - save_filename: Name of the file to save the text. - """ - DOCUMENT = '[usage] \n' - def __init__(self): - if len(sys.argv) != 4: - raise _ArgsError('Wrong length of arguments.') - try: - self.port = int(sys.argv[1]) - except ValueError as e: - raise _ArgsError(e) - self.user_list_filename = sys.argv[2] - self.saved_filename = sys.argv[3] - - -class _SharedVimServerError(Exception): - """Error raised by SharedVimServer.""" - pass - -class SharedVimServer(threading.Thread): - """Main class. - - Attributes: - _users_text_manager: Instance of UsersTextManager. - _tcp_server: Instance of TCPServer. - _cmd_ui: Instance of CmdUI. - """ - def __init__(self): - """Constructor.""" - super(SharedVimServer, self).__init__() - try: - self._args = _Args() - except _ArgsError as e: - raise _SharedVimServerError(str(e) + '\n' + _Args.DOCUMENT) - self._users_text_manager = UsersTextManager(self._args.saved_filename) - self._tcp_server = TCPServer(self._args.port, self._users_text_manager) - self._cmd_ui = CmdUI(['load %s' % self._args.user_list_filename], - self._users_text_manager, self._tcp_server, self) - log.info.interface = self._cmd_ui - log.error.interface = self._cmd_ui - - def run(self): - """Starts the program.""" - self._tcp_server.start() - self._cmd_ui.start() - self._cmd_ui.join() - self._tcp_server.join() - - def stop(self): - """Exits the program.""" - self._cmd_ui.stop() - self._tcp_server.stop() - - -class _SignalHandler(object): - """Single handler. - - It will handle below the signals: - SIGTERM, SIGINT - Exit the program. - - Attributes: - _shared_vim_server: Instance of SharedVimServer. - """ - def __init__(self, shared_vim_server): - """Constructor. - - Args: - shared_vim_server: Instance of SharedVimServer. - """ - self._shared_vim_server = shared_vim_server - signal.signal(signal.SIGTERM, self._handler) - signal.signal(signal.SIGINT, self._handler) - - def _handler(self, number, unused_frame): - """Signal handler function. - - Args: - number: The signal number to be handle. - """ - if number in (signal.SIGTERM, signal.SIGINT): - self._shared_vim_server.stop() - - -def main(): - """Program entry point.""" - try: - shared_vim_server = SharedVimServer() - _SignalHandler(shared_vim_server) - shared_vim_server.start() - shared_vim_server.join() - except _SharedVimServerError as e: - print(e) - sys.exit(1) - - -if __name__ == '__main__': - main() diff --git a/server/src/shrvim_server.py b/server/src/shrvim_server.py new file mode 100755 index 0000000..36ef111 --- /dev/null +++ b/server/src/shrvim_server.py @@ -0,0 +1,120 @@ +#! /usr/bin/env python3 + +"""Main thread.""" + +import signal +import sys +import threading +import log + +from cmd_ui import CmdUI +from tcp_server import TCPServer +from users_text_manager import UsersTextManager + + +class _ArgsError(Exception): + """Exception raised by _Args.""" + pass + +class _Args(object): + """Arguments of this program. + + Attributes: + port: Port number. + user_list_filename: Default user list. + save_filename: Name of the file to save the text. + """ + DOCUMENT = '[usage] \n' + def __init__(self): + if len(sys.argv) != 4: + raise _ArgsError('Wrong length of arguments.') + try: + self.port = int(sys.argv[1]) + except ValueError as e: + raise _ArgsError(e) + self.user_list_filename = sys.argv[2] + self.saved_filename = sys.argv[3] + + +class _ShrVimServerError(Exception): + """Error raised by ShrVimServer.""" + pass + +class ShrVimServer(threading.Thread): + """Main class. + + Attributes: + _users_text_manager: Instance of UsersTextManager. + _tcp_server: Instance of TCPServer. + _cmd_ui: Instance of CmdUI. + """ + def __init__(self): + """Constructor.""" + super(ShrVimServer, self).__init__() + try: + self._args = _Args() + except _ArgsError as e: + raise _ShrVimServerError(str(e) + '\n' + _Args.DOCUMENT) + self._users_text_manager = UsersTextManager(self._args.saved_filename) + self._tcp_server = TCPServer(self._args.port, self._users_text_manager) + self._cmd_ui = CmdUI(['load %s' % self._args.user_list_filename], + self._users_text_manager, self._tcp_server, self) + log.info.interface = self._cmd_ui + log.error.interface = self._cmd_ui + + def run(self): + """Starts the program.""" + self._tcp_server.start() + self._cmd_ui.start() + self._cmd_ui.join() + self._tcp_server.join() + + def stop(self): + """Exits the program.""" + self._cmd_ui.stop() + self._tcp_server.stop() + + +class _SignalHandler(object): + """Single handler. + + It will handle below the signals: + SIGTERM, SIGINT - Exit the program. + + Attributes: + _shrvim_server: Instance of ShrVimServer. + """ + def __init__(self, shrvim_server): + """Constructor. + + Args: + shrvim_server: Instance of ShrVimServer. + """ + self._shrvim_server = shrvim_server + signal.signal(signal.SIGTERM, self._handler) + signal.signal(signal.SIGINT, self._handler) + + def _handler(self, number, unused_frame): + """Signal handler function. + + Args: + number: The signal number to be handle. + """ + if number in (signal.SIGTERM, signal.SIGINT): + self._shrvim_server.stop() + + +def main(): + """Program entry point.""" + try: + shrvim_server = ShrVimServer() + _SignalHandler(shrvim_server) + shrvim_server.start() + shrvim_server.join() + except _ShrVimServerError as e: + print(e) + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/vim/plugin/shared_vim.vim b/vim/plugin/shared_vim.vim deleted file mode 100644 index d1fdf9d..0000000 --- a/vim/plugin/shared_vim.vim +++ /dev/null @@ -1,1039 +0,0 @@ -"""""""""""""""""""""""""""""""""""" Commands """""""""""""""""""""""""""""""""" -command! -nargs=0 SharedVimTryUsePython3 call _SharedVimTryUsePython3(1) -command! -nargs=0 SharedVimTryUsePython2 call _SharedVimTryUsePython2(1) -command! -nargs=+ SharedVimConnect call _SharedVimCallPythonFunc('connect', []) -command! -nargs=0 SharedVimDisconnect call _SharedVimCallPythonFunc('disconnect', []) -command! -nargs=0 SharedVimSync call _SharedVimCallPythonFunc('sync', []) -command! -nargs=0 SharedVimShowInfo call _SharedVimCallPythonFunc('show_info', []) -command! -nargs=1 SharedVimSetTimeout call _SharedVimCallPythonFunc('set_bvar', ['TIMEOUT', ]) -command! -nargs=1 SharedVimSetNumOfGroups call _SharedVimCallPythonFunc('set_bvar', ['NUM_GROUPS', ]) - - -""""""""""""""""""""""""""""""""""""" Setup """""""""""""""""""""""""""""""""""" -" Highlight for other users. -for i in range(0, 100) - exec 'hi SharedVimNor' . i . ' ctermbg=darkyellow' - exec 'hi SharedVimIns' . i . ' ctermbg=darkred' - exec 'hi SharedVimVbk' . i . ' ctermbg=darkblue' -endfor - - -let g:shared_vim_auto_sync_level = 3 - - -" Auto commands -autocmd! InsertLeave * call _SharedVimAutoSync(1) -autocmd! CursorMoved * call _SharedVimAutoSync(1) -autocmd! CursorHold * call _SharedVimAutoSync(1) -autocmd! InsertEnter * call _SharedVimAutoSync(2) -autocmd! CursorMovedI * call _SharedVimAutoSync(3) -autocmd! CursorHoldI * call _SharedVimAutoSync(3) - - -""""""""""""""""""""""""""""""""""" Functions """""""""""""""""""""""""""""""""" -function! _SharedVimTryUsePython3(show_err) - if has('python3') - command! -nargs=* SharedVimPython python3 - call _SharedVimSetup() - return 1 - else - if a:show_err - echoerr 'Sorry, :python3 is not supported in this version' - endif - return 0 - endif -endfunction - - -function! _SharedVimTryUsePython2(show_err) - if has('python') - command! -nargs=* SharedVimPython python - call _SharedVimSetup() - return 1 - else - if a:show_err - echoerr 'Sorry, :python is not supported in this version' - endif - return 0 - endif -endfunction - - -function! _SharedVimCallPythonFunc(func_name, args) - if exists('g:shared_vim_setupped') && g:shared_vim_setupped == 1 - if len(a:args) == 0 - exe 'SharedVimPython ' . a:func_name . '()' - else - let args_str = '"' . join(a:args, '", "') . '"' - exe 'SharedVimPython ' . a:func_name . '(' . args_str . ')' - endif - endif -endfunction - - -function! _SharedVimAutoSync(level) - let level = g:shared_vim_auto_sync_level - if exists('b:shared_vim_auto_sync_level') - let level = b:shared_vim_auto_sync_level - endif - if a:level <= level - SharedVimSync - endif -endfunction - - -function! _SharedVimSetup() -SharedVimPython << EOF -# python << EOF -# ^^ Force vim highlighting the python code below. -import json -import socket -import sys -import vim - -if sys.version_info[0] == 3: - unicode = str - -class CURSOR_MARK: # pylint:disable=W0232 - """Enumeration type of cursor marks.""" - CURRENT = '.' - V = 'v' - -class MATCH_PRI: # pylint:disable=W0232 - """Enumerations types of match priority.""" - NORMAL = 2 - INSERT = 3 - VISUAL = 1 - -class MODE: # pylint:disable=W0232 - """Enumeration type of mode.""" - NORMAL = 1 # normal mode. - INSERT = 2 # insert mode. - REPLACE = 3 # replace mode. - VISUAL = 4 # visual mode. - LINE_VISUAL = 5 # line visual mode. - BLOCK_VISUAL = 6 # block visual mode. - -class VARNAMES: # pylint: disable=W0232 - """Enumeration types of variable name in vim.""" - GROUP_NAME_PREFIX = 'gnp_' # Group name's prefix. - IDENTITY = 'identity' # Identity of the user. - INIT = 'init' # Initial or not. - LINES = 'lines' # Lines. - NUM_GROUPS = 'num_groups' # Number of groups. - SERVER_NAME = 'server_name' # Server name. - SERVER_PORT = 'port' # Server port. - TIMEOUT = 'timeout' # Timeout for TCPConnection. - USERS = 'users' # List of users. - -# Name of the normal cursor group. -NORMAL_CURSOR_GROUP = lambda x: 'SharedVimNor%d' % x - -# Name of the insert cursor group. -INSERT_CURSOR_GROUP = lambda x: 'SharedVimIns%d' % x - -# Name of the visual group. -VISUAL_GROUP = lambda x: 'SharedVimVbk%d' % x - - -DEFAULT_TIMEOUT = 1 -DEFAULT_NUM_GROUPS = 5 - - -class JSON_TOKEN: # pylint:disable=W0232 - """Enumeration the Ttken strings for json object.""" - BYE = 'bye' # Resets the user and do nothong. - CURSORS = 'cursors' # other users' cursor position - DIFF = 'diff' # Difference between this time and last time. - ERROR = 'error' # error string - IDENTITY = 'identity' # identity of myself - INIT = 'init' # initialize connect flag - MODE = 'mode' # vim mode. - NICKNAME = 'nickname' # nick name of the user. - OTHERS = 'others' # other users info. - - -############### Handler for Variable stores only in python ##################### -class ScopeVars(object): - """A scoped variables handler. - - Attributes: - _curr_scope: Current scope number. - _vars: A dict contains all scope's variables and the values. - """ - def __init__(self): - """Constructor.""" - self._curr_scope = None - self._vars = {} - - @property - def curr_scope(self): - """Gets the current scope number.""" - return self._curr_scope - - @curr_scope.setter - def curr_scope(self, value): - """Sets the current scope number.""" - self._curr_scope = value - self._vars.setdefault(self._curr_scope, {}) - - def get(self, variable_name, default=None): - """Gets the specified variable. - - Args: - variable_name: Name of the variable to get. - default: The default value to return if the variable is not exist. - - Return: - The value. - """ - return self._vars[self._curr_scope].get(variable_name, default) - - def __getitem__(self, variable_name): - """Gets the specified variable. - - Args: - variable_name: Name of the variable to get. - - Return: - The value. - """ - return self._vars[self._curr_scope][variable_name] - - def __setitem__(self, variable_name, value): - """Sets the specifiec variable. - - Args: - variable_name: Name of the variable to set. - value: The new value. - """ - self._vars[self._curr_scope][variable_name] = value - - def __delitem__(self, variable_name): - """Deletes the specifiec variable. - - Args: - variable_name: Name of the variable to delete. - """ - del self._vars[self._curr_scope][variable_name] - - def __contains__(self, variable_name): - """Checks whether the variable is exist or not. - - Args: - variable_name: Name of the variable to check. - - Return: - True if the variable exists; otherwise, False. - """ - return variable_name in self._vars[self._curr_scope] - - -# For scope=buffer -py_bvars = ScopeVars() - -# For scope=window -py_wvars = ScopeVars() - -############################### Interface to vim ############################### -class VimCursorsInfo(object): # pylint: disable=W0232 - """Gets/sets the cursor position in vim.""" - def __getitem__(self, mark): # pylint: disable=R0201 - """Gets the cursor position with specifying the mark. - - Args: - mark: Mark of the cursor to get. - - Return: - Cursor position. - """ - pos = [int(x) for x in vim.eval('getpos("%s")' % mark)] - row = pos[1] - 1 - line = VimInfo.transform_to_vim(VimInfo.lines[row]) - col = len(VimInfo.transform_to_py(line[ : pos[2] - 1])) - return (row, col) - - def __setitem__(self, mark, rc): # pylint: disable=R0201 - """Sets the cursor position with specifying the mark. - - Args: - mark: Mark of the cursor to set. - rc: The new cursor position. - """ - row, col = self.transform_to_vim(rc) - mark = mark if mark != CURSOR_MARK.V else CURSOR_MARK.CURRENT - vim.eval('setpos("%s", [0, %d, %d, 0])' % (mark, row + 1, col + 1)) - - def transform_to_vim(self, rc): # pylint: disable=R0201 - """Transform rc in utf-8 to rc in bytes. - - Args: - rc: Cursor position. - - Return: - Cursor position. - """ - row = rc[0] - line = VimInfo.lines[row] if row < len(VimInfo.lines) else '' - col = len(VimInfo.transform_to_vim(line[ : rc[1]])) - return (row, col) - - -class GroupInfo(object): - """Higilights informations about a user group. - - Attributes: - _normal_cursor_ranges: List of highlight block ranges in normal mode. - _insert_cursor_ranges: List of highlight block ranges in insert mode. - _visual_ranges: List of highlight block ranges in visual blocks. - """ - def __init__(self): - """Constructor.""" - self._normal_cursor_ranges = [] - self._insert_cursor_ranges = [] - self._visual_ranges = [] - - def set_mode_cursor(self, mode, rc): - """Sets the mode and the cursor. - - Args: - mode: The mode. - rc: The cursor position. - """ - if mode in (MODE.INSERT, MODE.REPLACE): - self._insert_cursor_ranges.append((rc[0], rc[1], rc[1] + 1)) - else: - self._normal_cursor_ranges.append((rc[0], rc[1], rc[1] + 1)) - - def add_visual(self, rang): - """Add a visual position. - - Args: - rang: The position. - """ - self._visual_ranges += [rang] - - @property - def normal_cursor_ranges(self): - """Gets the normal cursor ranges.""" - return self._normal_cursor_ranges - - @property - def insert_cursor_ranges(self): - """Gets the insert cursor ranges.""" - return self._insert_cursor_ranges - - @property - def visual_ranges(self): - """Gets the visual ranges.""" - return self._visual_ranges - - -class VimHighlightInfo(object): - """Highlight informations about users. - - Attributes: - _groups: A list of instance of GroupInfo. - _username_to_group: A dict for mapping the username to the instance of - GroupInfo. - """ - def __init__(self): - """Constructor.""" - self._groups = None - self._username_to_group = {} - - def __getitem__(self, name): - """Gets the group (a instance of GroupInfo) by nickname. - - Args: - name: Nick name. - - Return: - The group in which the user is. - """ - return self._username_to_group[name] - - def reset(self, nicknames): - """Reset the users. - - Args: - nicknames: A list of nickname. - """ - self._groups = [GroupInfo() for _ in range(self.num_of_groups())] - self._username_to_group = {name : self._groups[self._get_group_id(name)] - for name in nicknames} - - def render(self): - """Render the highlight to vim.""" - for index in range(self.num_of_groups()): - VimInfo.match(NORMAL_CURSOR_GROUP(index), MATCH_PRI.NORMAL, - self._groups[index].normal_cursor_ranges) - VimInfo.match(INSERT_CURSOR_GROUP(index), MATCH_PRI.INSERT, - self._groups[index].insert_cursor_ranges) - VimInfo.match(VISUAL_GROUP(index), MATCH_PRI.VISUAL, - self._groups[index].visual_ranges) - - def _get_group_id(self, string): - """Transform the gived string to a valid group index. - - Args: - string: The gived string. - - Return: - The index in [0, number of groups) - """ - x = 0 - for c in string: - x = (x * 23 + ord(c)) % self.num_of_groups() - return x - - @staticmethod - def num_of_groups(): - """Gets the number of groups.""" - return py_bvars.get(VARNAMES.NUM_GROUPS, DEFAULT_NUM_GROUPS) - - -class VimLinesInfo(object): - """An interface for accessing the vim's buffer. - - Attributes: - _lines: An list of lines, which is encoded text got from vim.buffer. - """ - def __init__(self): - """Constructor.""" - self._lines = [VimInfo.transform_to_py(line) - for line in vim.current.buffer] - - def __getitem__(self, index): - """Get a specified line. - - Args: - index: The line number. - """ - return self._lines[index] - - def __setitem__(self, index, text): - """Sets a specified line. - - Args: - index: The line number. - text: The new text. - """ - self._lines[index] = text - if isinstance(index, slice): - lines = [VimInfo.transform_to_vim(line) for line in text] - if index.start is not None and index.stop is not None: - vim.current.buffer[index.start : index.stop] = lines - elif index.start is None and index.stop is not None: - vim.current.buffer[ : index.stop] = lines - elif index.start is not None and index.stop is None: - vim.current.buffer[index.start : ] = lines - else: - vim.current.buffer[:] = lines - else: - vim.current.buffer[index] = VimInfo.transform_to_vim(text) - - def __len__(self): - """Gets the number of rows.""" - return len(self._lines) - - def gen_patch(self, orig_lines): - """Creates a patch from an old one. - - Args: - orig_lines: Original lines of the text. - - Return: - A list of replacing information. - """ - orig_rows, new_rows = len(orig_lines), len(self._lines) - for first in range(min(orig_rows, new_rows)): - if orig_lines[first] != self._lines[first]: - break - else: - if orig_rows < new_rows: - return [(orig_rows, orig_rows, self._lines[orig_rows : ])] - elif orig_rows > new_rows: - return [(first + 1, orig_rows, [])] - else: - return [] - delta = new_rows - orig_rows - for last in range(orig_rows - 1, first - 1, -1): - if orig_lines[last] != self._lines[last + delta]: - break - return [(first, last + 1, self._lines[first : last + delta + 1])] - - def apply_patch(self, patch_info): - """Applies a patch. - - Args: - patch_info: A list of replacing information. - - Return: - A list of text. - """ - offset = 0 - for beg, end, lines in patch_info: - self.__setitem__(slice(beg + offset, end + offset), lines) - offset += len(lines) - (end - beg) - - -class VimInfoMeta(type): - """An interface for accessing the vim's vars, buffer, cursors, etc. - - Static attributes: - cursors: An instance of VimCursorsInfo, for accessing the cursor - information in vim. - highlight: An instance of VimHighlightInfo, for accessing the - information about highlight in vim. - ENCODING: vim's encoding. - """ - cursors = VimCursorsInfo() - highlight = VimHighlightInfo() - ENCODING = vim.eval('&encoding') - - def __init__(self, *args): - """Constructor.""" - self._mode = None - self._lines = None - - def init(self): - """Initialize informations for this time.""" - self._lines = VimLinesInfo() - - @property - def lines(self): - """Gets list of lines in the buffer.""" - return self._lines - - @property - def mode(self): - """Gets the current mode.""" - mode_str = vim.eval('mode()') - if mode_str == 'i': - self._mode = MODE.INSERT - elif mode_str == 'R': - self._mode = MODE.REPLACE - elif mode_str == 'v': - self._mode = MODE.VISUAL - elif mode_str == 'V': - self._mode = MODE.LINE_VISUAL - elif len(mode_str) == 1 and ord(mode_str) == 22: - self._mode = MODE.BLOCK_VISUAL - else: - self._mode = MODE.NORMAL - return self._mode - - @mode.setter - def mode(self, value): - """Sets the current mode.""" - if self._mode != value: - if value == MODE.INSERT: - vim.command('startinsert') - elif value == MODE.REPLACE: - vim.command('startreplace') - elif value == MODE.VISUAL: - vim.command('exe "norm! v"') - elif value == MODE.LINE_VISUAL: - vim.command('exe "norm! V"') - elif value == MODE.BLOCK_VISUAL: - vim.command('exe "norm! %c"' % 22) - else: - vim.command('exe "norm! \\"') - self._mode = value - - @staticmethod - def match(group_name, priority, positions): - """Set the match informations. - - Args: - group_name: Group name. - priority: Priority for the vim function matchadd(). - positions: List of row-column position. - """ - last_id = py_wvars.get(VARNAMES.GROUP_NAME_PREFIX + group_name, None) - if last_id is not None and last_id > 0: - vim.eval('matchdelete(%d)' % last_id) - del py_wvars[VARNAMES.GROUP_NAME_PREFIX + group_name] - if positions: - rcs = [] - for row, col_beg, col_end in positions: - col_beg = VimInfo.cursors.transform_to_vim((row, col_beg))[1] - col_end = VimInfo.cursors.transform_to_vim((row, col_end))[1] - rcs += [(row, col) for col in range(col_beg, col_end)] - patterns = '\\|'.join(['\\%%%dl\\%%%dc' % (rc[0] + 1, rc[1] + 1) - for rc in rcs]) - mid = int(vim.eval("matchadd('%s', '%s', %d)" % - (group_name, patterns, priority))) - if mid != -1: - py_wvars[VARNAMES.GROUP_NAME_PREFIX + group_name] = mid - - @staticmethod - def transform_to_py(data): - """Transforms the data to python's data type if needs. - - In python, we always use unicode(python2)/str(python3) instead of bytes. - - Args: - data: The data to be transform. - - Return: - Data which are transformed. - """ - return (data if not isinstance(data, bytes) - else data.decode(VimInfo.ENCODING)) - - @staticmethod - def transform_to_vim(data): - """Transforms the data to vim's data type if needs. - - Args: - data: The data to be transform. - - Return: - Data which are transformed. - """ - return (data if not isinstance(data, unicode) - else data.encode(VimInfo.ENCODING)) - - @staticmethod - def display_width(data): - """Gets the width for the data to be display. - - Args: - data: The data. - - Return: - A number. - """ - return vim.strwidth(VimInfo.transform_to_vim(data)) - - @staticmethod - def confirm(prompt, options=None, default=None): - """Confirm something from the user. - - Args: - prompt: Prompt string. - options: List of options. - default: Default option. - """ - if options is None: - ret = vim.eval('confirm("%s")' % prompt) - elif default is None: - ret = vim.eval('confirm("%s", "%s")' % - (prompt, '\\n'.join('&' + o for o in options))) - else: - ret = vim.eval('confirm("%s", "%s", "%s")' % - (prompt, '\\n'.join('&' + o for o in options), - default)) - return int(ret) - 1 - -# Copy from https://bitbucket.org/gutworth/six/src/c17477e81e482d34bf3cda043b2eca643084e5fd/six.py -def with_metaclass(meta, *bases): - """Create a base class with a metaclass.""" - class metaclass(meta): # pylint: disable=W0232 - def __new__(cls, name, this_bases, d): - return meta(name, bases, d) - return type.__new__(metaclass, 'temporary_class', (), {}) - - -class VimInfo(with_metaclass(VimInfoMeta, object)): # pylint: disable=W0232 - """An interface for accessing the vim's vars, buffer, cursors, etc.""" - pass - - -########################### About connection to server ######################### -class JSONPackageError(Exception): - """Error raised by JSONPackage.""" - pass - -class JSONPackage(object): - """Send/receive json object by gived function. - - Attributes: - content: Content of the package body. - - Static attributes: - _ENCODING: Encoding of the package. - _HEADER_LENGTH: Length of the header. - """ - _ENCODING = 'utf-8' - _HEADER_LENGTH = 10 - def __init__(self, content=None, recv_func=None): - """Constructor. - - If the receive_func is not None, it will grap the default content by - calling that function instead of by the argument "content". - - The detail of arguments/return values format see the method "recv_from". - - Args: - content: The default content of this package. - recv_func: A function for receive the default content. - """ - self.content = content - if recv_func is not None: - self.recv(recv_func) - - def send(self, send_func): - """Sends by calling the gived sending function. - - Args: - send_func: A function which will send the whole data gived. - Function format: - send_func(bytes_data): None - """ - try: - body = json.dumps(self.content) - header_str = ('%%0%dd' % JSONPackage._HEADER_LENGTH) % len(body) - send_func(header_str + body) - except TypeError as e: - raise JSONPackageError('json: %s' % str(e)) - except UnicodeError as e: - raise JSONPackageError('Cannot encode the string: %s.' % str(e)) - - def recv(self, recv_func): - """Receives a json object from a gived function. - - It will calls the give function like this: - recv_func() => bytes with length - - Args: - recv_func: A function to be called to get the serialize data. - """ - try: - header_str = unicode(recv_func(JSONPackage._HEADER_LENGTH), - JSONPackage._ENCODING) - body_str = unicode(recv_func(int(header_str)), - JSONPackage._ENCODING) - except UnicodeError as e: - raise JSONPackageError('Cannot decode the bytes: %r.' % e) - except ValueError as e: - raise JSONPackageError('Cannot get the body length %r' % e) - try: - self.content = json.loads(body_str) - except ValueError as e: - raise JSONPackageError('Cannot loads to the json object: %r' % e) - - -class TCPConnection(object): - """My custom tcp connection. - - Args: - _conn: The TCP-connection. - """ - def __init__(self, conn): - """Constructor. - - Args: - conn: TCP-connection. - """ - self._conn = conn - self._conn.settimeout(py_bvars.get(VARNAMES.TIMEOUT, DEFAULT_TIMEOUT)) - - def send_all(self, data): - """Sends the data until timeout or the socket closed. - - Args: - data: Data to be sent. - """ - self._conn.sendall(data) - - def recv_all(self, nbyte): - """Receives the data until timeout or the socket closed. - - Args: - nbyte: Bytes of data to receive. - - Return: - Bytes of data. - """ - ret = b'' - while nbyte > 0: - recv = self._conn.recv(nbyte) - if not recv: - raise socket.error('Connection die.') - ret += recv - nbyte -= len(recv) - return ret - - def close(self): - """Closes the connection.""" - self._conn.close() - - -class TCPClientError(Exception): - """Exception raised by TCPClient.""" - pass - -class TCPClient(object): - """TCP client. - - Attributes: - _conn: Connection. - - Static attributes: - _conns: A dict stores connections. - """ - _conns = {} - def __init__(self, server_name, port_name): - """Constructor, automatically connects to the server. - - Args: - server_name: Server name. - port_name: Port name. - """ - key = (server_name, port_name) - if key not in TCPClient._conns: - try: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.connect((server_name, port_name)) - TCPClient._conns[key] = TCPConnection(sock) - except TypeError as e: - raise TCPClientError('Cannot connect to server: %s' % str(e)) - except socket.error as e: - raise TCPClientError('Cannot connect to server: %s' % str(e)) - self._conn = TCPClient._conns[key] - - def request(self, req): - """Sends a request to server and get the response. - - Args: - req: An request. - - Return: - The response. - """ - try: - JSONPackage(req).send(self._conn.send_all) - return JSONPackage(recv_func=self._conn.recv_all).content - except socket.error as e: - self.close() - raise TCPClientError(e) - except JSONPackageError as e: - raise TCPClientError(e) - - def close(self): - """Closes the socket.""" - self._conn.close() - for key, conn in TCPClient._conns.items(): - if conn is self._conn: - del TCPClient._conns[key] - break - - -################################ Some operations ############################### -def init_for_this_time(): - py_bvars.curr_scope = vim.current.buffer - py_wvars.curr_scope = vim.current.window - VimInfo.init() - -def get_my_info(init): - """Gets my information for server. - - Return: - The information for server. - """ - return {JSON_TOKEN.IDENTITY : py_bvars[VARNAMES.IDENTITY], - JSON_TOKEN.INIT : init, - JSON_TOKEN.MODE : VimInfo.mode, - JSON_TOKEN.CURSORS : { - CURSOR_MARK.CURRENT : VimInfo.cursors[CURSOR_MARK.CURRENT], - CURSOR_MARK.V : VimInfo.cursors[CURSOR_MARK.V], - }, - JSON_TOKEN.DIFF : VimInfo.lines.gen_patch( - py_bvars.get(VARNAMES.LINES, ['']))} - - -def set_my_info(json_info): - """Sets my information gived by server. - - Args: - json_info: JSON information gived by server. - """ - VimInfo.lines.apply_patch(json_info[JSON_TOKEN.DIFF]) - py_bvars[VARNAMES.LINES] = VimInfo.lines[:] - mode = json_info[JSON_TOKEN.MODE] - VimInfo.mode = mode - if mode in (MODE.VISUAL, MODE.BLOCK_VISUAL, MODE.LINE_VISUAL): - old_mode, VimInfo.mode = mode, MODE.NORMAL - VimInfo.cursors[CURSOR_MARK.V] = \ - json_info[JSON_TOKEN.CURSORS][CURSOR_MARK.V] - VimInfo.mode = old_mode - VimInfo.cursors[CURSOR_MARK.CURRENT] = \ - json_info[JSON_TOKEN.CURSORS][CURSOR_MARK.CURRENT] - - -def set_others_info(json_info): - """Sets the informations about other user. - - Args: - json_info: JSON information gived by server. - """ - users = json_info[JSON_TOKEN.OTHERS] - VimInfo.highlight.reset([user[JSON_TOKEN.NICKNAME] for user in users]) - for user in users: - name, mode = user[JSON_TOKEN.NICKNAME], user[JSON_TOKEN.MODE] - cursors = user[JSON_TOKEN.CURSORS] - curr_rc = cursors[CURSOR_MARK.CURRENT] - VimInfo.highlight[name].set_mode_cursor(mode, curr_rc) - if mode in (MODE.VISUAL, MODE.LINE_VISUAL, MODE.BLOCK_VISUAL): - last_rc = cursors[CURSOR_MARK.V] - if last_rc[0] > curr_rc[0] or \ - (last_rc[0] == curr_rc[0] and last_rc[1] > curr_rc[1]): - last_rc, curr_rc = curr_rc, last_rc - set_other_visual(name, mode, last_rc, curr_rc) - VimInfo.highlight.render() - -def set_other_visual(name, mode, beg, end): - """Sets the other user's visual block. - - Args: - name: Name of this user. - mode: Mode of this user. - beg: The first row-column position of the range. - end: The last row-column position of the range. - """ - if mode == MODE.VISUAL: - for row in range(beg[0], end[0] + 1): - col_beg = 0 if row != beg[0] else beg[1] - col_end = (len(VimInfo.lines[row]) if row != end[0] else end[1]) + 1 - VimInfo.highlight[name].add_visual((row, col_beg, col_end)) - elif mode == MODE.LINE_VISUAL: - for row in range(beg[0], end[0] + 1): - VimInfo.highlight[name].add_visual( - (row, 0, len(VimInfo.lines[row]) + 1)) - elif mode == MODE.BLOCK_VISUAL: - set_other_block_visual(name, beg, end) - - -def set_other_block_visual(name, beg, end): - """Sets the other user's virtical-visual block. - - Args: - name: Name of this user. - beg: The first row-column position of the range. - end: The last row-column position of the range. - """ - w1 = VimInfo.display_width(VimInfo.lines[beg[0]][ : beg[1]]) - w2 = VimInfo.display_width(VimInfo.lines[end[0]][ : end[1]]) - left, right = min(w1, w2), max(w1, w2) - for row in range(beg[0], end[0] + 1): - for beg_col in range(len(VimInfo.lines[row]) + 1): - w = VimInfo.display_width(VimInfo.lines[row][ : beg_col]) - if left < w: - beg_col -= 1 - break - else: - continue - for end_col in range(beg_col, len(VimInfo.lines[row]) + 1): - w = VimInfo.display_width(VimInfo.lines[row][ : end_col]) - if right < w: - break - VimInfo.highlight[name].add_visual((row, beg_col, end_col)) - - -############################## Supported operations ############################ -def connect(server_name, server_port, identity): - """Connects to the server. - - Args: - server_name: Server name. - server_port: Server port. - identity: Identity string of this user. - """ - init_for_this_time() - if len(VimInfo.lines) > 1 or len(VimInfo.lines[0]) > 0: - c = VimInfo.confirm( - 'This tool will rewrite the whole buffer, continue? ', - ['Yes', 'No'], 'No') - if c != 0: - return - VimInfo.lines[:] = [''] - py_bvars[VARNAMES.SERVER_NAME] = server_name - py_bvars[VARNAMES.SERVER_PORT] = int(server_port) - py_bvars[VARNAMES.IDENTITY] = identity - sync(init=True) - - -def sync(init=False): - """Sync with the server. - - Args: - init: Flag for whether it should tell the server to reset this user or - not. - """ - init_for_this_time() - if VARNAMES.SERVER_NAME in py_bvars: - try: - conn = TCPClient(py_bvars[VARNAMES.SERVER_NAME], - py_bvars[VARNAMES.SERVER_PORT]) - response = conn.request(get_my_info(init)) - except TCPClientError as e: - print(str(e)) - return - if JSON_TOKEN.ERROR in response: - print(response[JSON_TOKEN.ERROR]) - return - set_my_info(response) - set_others_info(response) - py_bvars[VARNAMES.USERS] = ', '.join( - [user[JSON_TOKEN.NICKNAME] for user in response[JSON_TOKEN.OTHERS]]) - - -def disconnect(): - """Disconnects with the server.""" - init_for_this_time() - if VARNAMES.SERVER_NAME in py_bvars: - VimInfo.highlight.reset([]) - VimInfo.highlight.render() - try: - conn = TCPClient(py_bvars[VARNAMES.SERVER_NAME], - py_bvars[VARNAMES.SERVER_PORT]) - conn.request({ - JSON_TOKEN.BYE : True, - JSON_TOKEN.IDENTITY : py_bvars[VARNAMES.IDENTITY]}) - except TCPClientError as e: - print(str(e)) - conn.close() - del py_bvars[VARNAMES.SERVER_NAME] - del py_bvars[VARNAMES.SERVER_PORT] - del py_bvars[VARNAMES.IDENTITY] - print('bye') - - -def show_info(): - """Shows the informations.""" - init_for_this_time() - print('Highlight information:') - print('Groups of normal cursor position:') - for index in range(VimInfo.highlight.num_of_groups()): - vim.command('hi %s' % NORMAL_CURSOR_GROUP(index)) - print('Groups of insert cursor position:') - for index in range(VimInfo.highlight.num_of_groups()): - vim.command('hi %s' % INSERT_CURSOR_GROUP(index)) - print('Groups of selection area:') - for index in range(VimInfo.highlight.num_of_groups()): - vim.command('hi %s' % VISUAL_GROUP(index)) - print('Users: %r' % py_bvars[VARNAMES.USERS]) - - -def set_bvar(variable_name, value): - """Sets the variable of the current buffer. - - Args: - variable_name: Variable name. - value: Value. - """ - init_for_this_time() - py_bvars[getattr(VARNAMES, variable_name)] = value - print('bvars[%s] = %s' % (getattr(VARNAMES, variable_name), value)) - -EOF -endfunction - -"""""""""""""""""""""""""""""""" Initialize """""""""""""""""""""""""""""""""""" - -if _SharedVimTryUsePython3(0) || _SharedVimTryUsePython2(0) - let g:shared_vim_setupped = 1 -endif diff --git a/vim/plugin/shrvim.vim b/vim/plugin/shrvim.vim new file mode 100644 index 0000000..0d2b4fe --- /dev/null +++ b/vim/plugin/shrvim.vim @@ -0,0 +1,1039 @@ +"""""""""""""""""""""""""""""""""""" Commands """""""""""""""""""""""""""""""""" +command! -nargs=0 ShrVimTryUsePython3 call _ShrVimTryUsePython3(1) +command! -nargs=0 ShrVimTryUsePython2 call _ShrVimTryUsePython2(1) +command! -nargs=+ ShrVimConnect call _ShrVimCallPythonFunc('connect', []) +command! -nargs=0 ShrVimDisconnect call _ShrVimCallPythonFunc('disconnect', []) +command! -nargs=0 ShrVimSync call _ShrVimCallPythonFunc('sync', []) +command! -nargs=0 ShrVimShowInfo call _ShrVimCallPythonFunc('show_info', []) +command! -nargs=1 ShrVimSetTimeout call _ShrVimCallPythonFunc('set_bvar', ['TIMEOUT', ]) +command! -nargs=1 ShrVimSetNumOfGroups call _ShrVimCallPythonFunc('set_bvar', ['NUM_GROUPS', ]) + + +""""""""""""""""""""""""""""""""""""" Setup """""""""""""""""""""""""""""""""""" +" Highlight for other users. +for i in range(0, 100) + exec 'hi ShrVimNor' . i . ' ctermbg=darkyellow' + exec 'hi ShrVimIns' . i . ' ctermbg=darkred' + exec 'hi ShrVimVbk' . i . ' ctermbg=darkblue' +endfor + + +let g:shrvim_auto_sync_level = 3 + + +" Auto commands +autocmd! InsertLeave * call _ShrVimAutoSync(1) +autocmd! CursorMoved * call _ShrVimAutoSync(1) +autocmd! CursorHold * call _ShrVimAutoSync(1) +autocmd! InsertEnter * call _ShrVimAutoSync(2) +autocmd! CursorMovedI * call _ShrVimAutoSync(3) +autocmd! CursorHoldI * call _ShrVimAutoSync(3) + + +""""""""""""""""""""""""""""""""""" Functions """""""""""""""""""""""""""""""""" +function! _ShrVimTryUsePython3(show_err) + if has('python3') + command! -nargs=* ShrVimPython python3 + call _ShrVimSetup() + return 1 + else + if a:show_err + echoerr 'Sorry, :python3 is not supported in this version' + endif + return 0 + endif +endfunction + + +function! _ShrVimTryUsePython2(show_err) + if has('python') + command! -nargs=* ShrVimPython python + call _ShrVimSetup() + return 1 + else + if a:show_err + echoerr 'Sorry, :python is not supported in this version' + endif + return 0 + endif +endfunction + + +function! _ShrVimCallPythonFunc(func_name, args) + if exists('g:shrvim_setupped') && g:shrvim_setupped == 1 + if len(a:args) == 0 + exe 'ShrVimPython ' . a:func_name . '()' + else + let args_str = '"' . join(a:args, '", "') . '"' + exe 'ShrVimPython ' . a:func_name . '(' . args_str . ')' + endif + endif +endfunction + + +function! _ShrVimAutoSync(level) + let level = g:shrvim_auto_sync_level + if exists('b:shrvim_auto_sync_level') + let level = b:shrvim_auto_sync_level + endif + if a:level <= level + ShrVimSync + endif +endfunction + + +function! _ShrVimSetup() +ShrVimPython << EOF +# python << EOF +# ^^ Force vim highlighting the python code below. +import json +import socket +import sys +import vim + +if sys.version_info[0] == 3: + unicode = str + +class CURSOR_MARK: # pylint:disable=W0232 + """Enumeration type of cursor marks.""" + CURRENT = '.' + V = 'v' + +class MATCH_PRI: # pylint:disable=W0232 + """Enumerations types of match priority.""" + NORMAL = 2 + INSERT = 3 + VISUAL = 1 + +class MODE: # pylint:disable=W0232 + """Enumeration type of mode.""" + NORMAL = 1 # normal mode. + INSERT = 2 # insert mode. + REPLACE = 3 # replace mode. + VISUAL = 4 # visual mode. + LINE_VISUAL = 5 # line visual mode. + BLOCK_VISUAL = 6 # block visual mode. + +class VARNAMES: # pylint: disable=W0232 + """Enumeration types of variable name in vim.""" + GROUP_NAME_PREFIX = 'gnp_' # Group name's prefix. + IDENTITY = 'identity' # Identity of the user. + INIT = 'init' # Initial or not. + LINES = 'lines' # Lines. + NUM_GROUPS = 'num_groups' # Number of groups. + SERVER_NAME = 'server_name' # Server name. + SERVER_PORT = 'port' # Server port. + TIMEOUT = 'timeout' # Timeout for TCPConnection. + USERS = 'users' # List of users. + +# Name of the normal cursor group. +NORMAL_CURSOR_GROUP = lambda x: 'ShrVimNor%d' % x + +# Name of the insert cursor group. +INSERT_CURSOR_GROUP = lambda x: 'ShrVimIns%d' % x + +# Name of the visual group. +VISUAL_GROUP = lambda x: 'ShrVimVbk%d' % x + + +DEFAULT_TIMEOUT = 1 +DEFAULT_NUM_GROUPS = 5 + + +class JSON_TOKEN: # pylint:disable=W0232 + """Enumeration the Ttken strings for json object.""" + BYE = 'bye' # Resets the user and do nothong. + CURSORS = 'cursors' # other users' cursor position + DIFF = 'diff' # Difference between this time and last time. + ERROR = 'error' # error string + IDENTITY = 'identity' # identity of myself + INIT = 'init' # initialize connect flag + MODE = 'mode' # vim mode. + NICKNAME = 'nickname' # nick name of the user. + OTHERS = 'others' # other users info. + + +############### Handler for Variable stores only in python ##################### +class ScopeVars(object): + """A scoped variables handler. + + Attributes: + _curr_scope: Current scope number. + _vars: A dict contains all scope's variables and the values. + """ + def __init__(self): + """Constructor.""" + self._curr_scope = None + self._vars = {} + + @property + def curr_scope(self): + """Gets the current scope number.""" + return self._curr_scope + + @curr_scope.setter + def curr_scope(self, value): + """Sets the current scope number.""" + self._curr_scope = value + self._vars.setdefault(self._curr_scope, {}) + + def get(self, variable_name, default=None): + """Gets the specified variable. + + Args: + variable_name: Name of the variable to get. + default: The default value to return if the variable is not exist. + + Return: + The value. + """ + return self._vars[self._curr_scope].get(variable_name, default) + + def __getitem__(self, variable_name): + """Gets the specified variable. + + Args: + variable_name: Name of the variable to get. + + Return: + The value. + """ + return self._vars[self._curr_scope][variable_name] + + def __setitem__(self, variable_name, value): + """Sets the specifiec variable. + + Args: + variable_name: Name of the variable to set. + value: The new value. + """ + self._vars[self._curr_scope][variable_name] = value + + def __delitem__(self, variable_name): + """Deletes the specifiec variable. + + Args: + variable_name: Name of the variable to delete. + """ + del self._vars[self._curr_scope][variable_name] + + def __contains__(self, variable_name): + """Checks whether the variable is exist or not. + + Args: + variable_name: Name of the variable to check. + + Return: + True if the variable exists; otherwise, False. + """ + return variable_name in self._vars[self._curr_scope] + + +# For scope=buffer +py_bvars = ScopeVars() + +# For scope=window +py_wvars = ScopeVars() + +############################### Interface to vim ############################### +class VimCursorsInfo(object): # pylint: disable=W0232 + """Gets/sets the cursor position in vim.""" + def __getitem__(self, mark): # pylint: disable=R0201 + """Gets the cursor position with specifying the mark. + + Args: + mark: Mark of the cursor to get. + + Return: + Cursor position. + """ + pos = [int(x) for x in vim.eval('getpos("%s")' % mark)] + row = pos[1] - 1 + line = VimInfo.transform_to_vim(VimInfo.lines[row]) + col = len(VimInfo.transform_to_py(line[ : pos[2] - 1])) + return (row, col) + + def __setitem__(self, mark, rc): # pylint: disable=R0201 + """Sets the cursor position with specifying the mark. + + Args: + mark: Mark of the cursor to set. + rc: The new cursor position. + """ + row, col = self.transform_to_vim(rc) + mark = mark if mark != CURSOR_MARK.V else CURSOR_MARK.CURRENT + vim.eval('setpos("%s", [0, %d, %d, 0])' % (mark, row + 1, col + 1)) + + def transform_to_vim(self, rc): # pylint: disable=R0201 + """Transform rc in utf-8 to rc in bytes. + + Args: + rc: Cursor position. + + Return: + Cursor position. + """ + row = rc[0] + line = VimInfo.lines[row] if row < len(VimInfo.lines) else '' + col = len(VimInfo.transform_to_vim(line[ : rc[1]])) + return (row, col) + + +class GroupInfo(object): + """Higilights informations about a user group. + + Attributes: + _normal_cursor_ranges: List of highlight block ranges in normal mode. + _insert_cursor_ranges: List of highlight block ranges in insert mode. + _visual_ranges: List of highlight block ranges in visual blocks. + """ + def __init__(self): + """Constructor.""" + self._normal_cursor_ranges = [] + self._insert_cursor_ranges = [] + self._visual_ranges = [] + + def set_mode_cursor(self, mode, rc): + """Sets the mode and the cursor. + + Args: + mode: The mode. + rc: The cursor position. + """ + if mode in (MODE.INSERT, MODE.REPLACE): + self._insert_cursor_ranges.append((rc[0], rc[1], rc[1] + 1)) + else: + self._normal_cursor_ranges.append((rc[0], rc[1], rc[1] + 1)) + + def add_visual(self, rang): + """Add a visual position. + + Args: + rang: The position. + """ + self._visual_ranges += [rang] + + @property + def normal_cursor_ranges(self): + """Gets the normal cursor ranges.""" + return self._normal_cursor_ranges + + @property + def insert_cursor_ranges(self): + """Gets the insert cursor ranges.""" + return self._insert_cursor_ranges + + @property + def visual_ranges(self): + """Gets the visual ranges.""" + return self._visual_ranges + + +class VimHighlightInfo(object): + """Highlight informations about users. + + Attributes: + _groups: A list of instance of GroupInfo. + _username_to_group: A dict for mapping the username to the instance of + GroupInfo. + """ + def __init__(self): + """Constructor.""" + self._groups = None + self._username_to_group = {} + + def __getitem__(self, name): + """Gets the group (a instance of GroupInfo) by nickname. + + Args: + name: Nick name. + + Return: + The group in which the user is. + """ + return self._username_to_group[name] + + def reset(self, nicknames): + """Reset the users. + + Args: + nicknames: A list of nickname. + """ + self._groups = [GroupInfo() for _ in range(self.num_of_groups())] + self._username_to_group = {name : self._groups[self._get_group_id(name)] + for name in nicknames} + + def render(self): + """Render the highlight to vim.""" + for index in range(self.num_of_groups()): + VimInfo.match(NORMAL_CURSOR_GROUP(index), MATCH_PRI.NORMAL, + self._groups[index].normal_cursor_ranges) + VimInfo.match(INSERT_CURSOR_GROUP(index), MATCH_PRI.INSERT, + self._groups[index].insert_cursor_ranges) + VimInfo.match(VISUAL_GROUP(index), MATCH_PRI.VISUAL, + self._groups[index].visual_ranges) + + def _get_group_id(self, string): + """Transform the gived string to a valid group index. + + Args: + string: The gived string. + + Return: + The index in [0, number of groups) + """ + x = 0 + for c in string: + x = (x * 23 + ord(c)) % self.num_of_groups() + return x + + @staticmethod + def num_of_groups(): + """Gets the number of groups.""" + return py_bvars.get(VARNAMES.NUM_GROUPS, DEFAULT_NUM_GROUPS) + + +class VimLinesInfo(object): + """An interface for accessing the vim's buffer. + + Attributes: + _lines: An list of lines, which is encoded text got from vim.buffer. + """ + def __init__(self): + """Constructor.""" + self._lines = [VimInfo.transform_to_py(line) + for line in vim.current.buffer] + + def __getitem__(self, index): + """Get a specified line. + + Args: + index: The line number. + """ + return self._lines[index] + + def __setitem__(self, index, text): + """Sets a specified line. + + Args: + index: The line number. + text: The new text. + """ + self._lines[index] = text + if isinstance(index, slice): + lines = [VimInfo.transform_to_vim(line) for line in text] + if index.start is not None and index.stop is not None: + vim.current.buffer[index.start : index.stop] = lines + elif index.start is None and index.stop is not None: + vim.current.buffer[ : index.stop] = lines + elif index.start is not None and index.stop is None: + vim.current.buffer[index.start : ] = lines + else: + vim.current.buffer[:] = lines + else: + vim.current.buffer[index] = VimInfo.transform_to_vim(text) + + def __len__(self): + """Gets the number of rows.""" + return len(self._lines) + + def gen_patch(self, orig_lines): + """Creates a patch from an old one. + + Args: + orig_lines: Original lines of the text. + + Return: + A list of replacing information. + """ + orig_rows, new_rows = len(orig_lines), len(self._lines) + for first in range(min(orig_rows, new_rows)): + if orig_lines[first] != self._lines[first]: + break + else: + if orig_rows < new_rows: + return [(orig_rows, orig_rows, self._lines[orig_rows : ])] + elif orig_rows > new_rows: + return [(first + 1, orig_rows, [])] + else: + return [] + delta = new_rows - orig_rows + for last in range(orig_rows - 1, first - 1, -1): + if orig_lines[last] != self._lines[last + delta]: + break + return [(first, last + 1, self._lines[first : last + delta + 1])] + + def apply_patch(self, patch_info): + """Applies a patch. + + Args: + patch_info: A list of replacing information. + + Return: + A list of text. + """ + offset = 0 + for beg, end, lines in patch_info: + self.__setitem__(slice(beg + offset, end + offset), lines) + offset += len(lines) - (end - beg) + + +class VimInfoMeta(type): + """An interface for accessing the vim's vars, buffer, cursors, etc. + + Static attributes: + cursors: An instance of VimCursorsInfo, for accessing the cursor + information in vim. + highlight: An instance of VimHighlightInfo, for accessing the + information about highlight in vim. + ENCODING: vim's encoding. + """ + cursors = VimCursorsInfo() + highlight = VimHighlightInfo() + ENCODING = vim.eval('&encoding') + + def __init__(self, *args): + """Constructor.""" + self._mode = None + self._lines = None + + def init(self): + """Initialize informations for this time.""" + self._lines = VimLinesInfo() + + @property + def lines(self): + """Gets list of lines in the buffer.""" + return self._lines + + @property + def mode(self): + """Gets the current mode.""" + mode_str = vim.eval('mode()') + if mode_str == 'i': + self._mode = MODE.INSERT + elif mode_str == 'R': + self._mode = MODE.REPLACE + elif mode_str == 'v': + self._mode = MODE.VISUAL + elif mode_str == 'V': + self._mode = MODE.LINE_VISUAL + elif len(mode_str) == 1 and ord(mode_str) == 22: + self._mode = MODE.BLOCK_VISUAL + else: + self._mode = MODE.NORMAL + return self._mode + + @mode.setter + def mode(self, value): + """Sets the current mode.""" + if self._mode != value: + if value == MODE.INSERT: + vim.command('startinsert') + elif value == MODE.REPLACE: + vim.command('startreplace') + elif value == MODE.VISUAL: + vim.command('exe "norm! v"') + elif value == MODE.LINE_VISUAL: + vim.command('exe "norm! V"') + elif value == MODE.BLOCK_VISUAL: + vim.command('exe "norm! %c"' % 22) + else: + vim.command('exe "norm! \\"') + self._mode = value + + @staticmethod + def match(group_name, priority, positions): + """Set the match informations. + + Args: + group_name: Group name. + priority: Priority for the vim function matchadd(). + positions: List of row-column position. + """ + last_id = py_wvars.get(VARNAMES.GROUP_NAME_PREFIX + group_name, None) + if last_id is not None and last_id > 0: + vim.eval('matchdelete(%d)' % last_id) + del py_wvars[VARNAMES.GROUP_NAME_PREFIX + group_name] + if positions: + rcs = [] + for row, col_beg, col_end in positions: + col_beg = VimInfo.cursors.transform_to_vim((row, col_beg))[1] + col_end = VimInfo.cursors.transform_to_vim((row, col_end))[1] + rcs += [(row, col) for col in range(col_beg, col_end)] + patterns = '\\|'.join(['\\%%%dl\\%%%dc' % (rc[0] + 1, rc[1] + 1) + for rc in rcs]) + mid = int(vim.eval("matchadd('%s', '%s', %d)" % + (group_name, patterns, priority))) + if mid != -1: + py_wvars[VARNAMES.GROUP_NAME_PREFIX + group_name] = mid + + @staticmethod + def transform_to_py(data): + """Transforms the data to python's data type if needs. + + In python, we always use unicode(python2)/str(python3) instead of bytes. + + Args: + data: The data to be transform. + + Return: + Data which are transformed. + """ + return (data if not isinstance(data, bytes) + else data.decode(VimInfo.ENCODING)) + + @staticmethod + def transform_to_vim(data): + """Transforms the data to vim's data type if needs. + + Args: + data: The data to be transform. + + Return: + Data which are transformed. + """ + return (data if not isinstance(data, unicode) + else data.encode(VimInfo.ENCODING)) + + @staticmethod + def display_width(data): + """Gets the width for the data to be display. + + Args: + data: The data. + + Return: + A number. + """ + return vim.strwidth(VimInfo.transform_to_vim(data)) + + @staticmethod + def confirm(prompt, options=None, default=None): + """Confirm something from the user. + + Args: + prompt: Prompt string. + options: List of options. + default: Default option. + """ + if options is None: + ret = vim.eval('confirm("%s")' % prompt) + elif default is None: + ret = vim.eval('confirm("%s", "%s")' % + (prompt, '\\n'.join('&' + o for o in options))) + else: + ret = vim.eval('confirm("%s", "%s", "%s")' % + (prompt, '\\n'.join('&' + o for o in options), + default)) + return int(ret) - 1 + +# Copy from https://bitbucket.org/gutworth/six/src/c17477e81e482d34bf3cda043b2eca643084e5fd/six.py +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + class metaclass(meta): # pylint: disable=W0232 + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + return type.__new__(metaclass, 'temporary_class', (), {}) + + +class VimInfo(with_metaclass(VimInfoMeta, object)): # pylint: disable=W0232 + """An interface for accessing the vim's vars, buffer, cursors, etc.""" + pass + + +########################### About connection to server ######################### +class JSONPackageError(Exception): + """Error raised by JSONPackage.""" + pass + +class JSONPackage(object): + """Send/receive json object by gived function. + + Attributes: + content: Content of the package body. + + Static attributes: + _ENCODING: Encoding of the package. + _HEADER_LENGTH: Length of the header. + """ + _ENCODING = 'utf-8' + _HEADER_LENGTH = 10 + def __init__(self, content=None, recv_func=None): + """Constructor. + + If the receive_func is not None, it will grap the default content by + calling that function instead of by the argument "content". + + The detail of arguments/return values format see the method "recv_from". + + Args: + content: The default content of this package. + recv_func: A function for receive the default content. + """ + self.content = content + if recv_func is not None: + self.recv(recv_func) + + def send(self, send_func): + """Sends by calling the gived sending function. + + Args: + send_func: A function which will send the whole data gived. + Function format: + send_func(bytes_data): None + """ + try: + body = json.dumps(self.content) + header_str = ('%%0%dd' % JSONPackage._HEADER_LENGTH) % len(body) + send_func(header_str + body) + except TypeError as e: + raise JSONPackageError('json: %s' % str(e)) + except UnicodeError as e: + raise JSONPackageError('Cannot encode the string: %s.' % str(e)) + + def recv(self, recv_func): + """Receives a json object from a gived function. + + It will calls the give function like this: + recv_func() => bytes with length + + Args: + recv_func: A function to be called to get the serialize data. + """ + try: + header_str = unicode(recv_func(JSONPackage._HEADER_LENGTH), + JSONPackage._ENCODING) + body_str = unicode(recv_func(int(header_str)), + JSONPackage._ENCODING) + except UnicodeError as e: + raise JSONPackageError('Cannot decode the bytes: %r.' % e) + except ValueError as e: + raise JSONPackageError('Cannot get the body length %r' % e) + try: + self.content = json.loads(body_str) + except ValueError as e: + raise JSONPackageError('Cannot loads to the json object: %r' % e) + + +class TCPConnection(object): + """My custom tcp connection. + + Args: + _conn: The TCP-connection. + """ + def __init__(self, conn): + """Constructor. + + Args: + conn: TCP-connection. + """ + self._conn = conn + self._conn.settimeout(py_bvars.get(VARNAMES.TIMEOUT, DEFAULT_TIMEOUT)) + + def send_all(self, data): + """Sends the data until timeout or the socket closed. + + Args: + data: Data to be sent. + """ + self._conn.sendall(data) + + def recv_all(self, nbyte): + """Receives the data until timeout or the socket closed. + + Args: + nbyte: Bytes of data to receive. + + Return: + Bytes of data. + """ + ret = b'' + while nbyte > 0: + recv = self._conn.recv(nbyte) + if not recv: + raise socket.error('Connection die.') + ret += recv + nbyte -= len(recv) + return ret + + def close(self): + """Closes the connection.""" + self._conn.close() + + +class TCPClientError(Exception): + """Exception raised by TCPClient.""" + pass + +class TCPClient(object): + """TCP client. + + Attributes: + _conn: Connection. + + Static attributes: + _conns: A dict stores connections. + """ + _conns = {} + def __init__(self, server_name, port_name): + """Constructor, automatically connects to the server. + + Args: + server_name: Server name. + port_name: Port name. + """ + key = (server_name, port_name) + if key not in TCPClient._conns: + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((server_name, port_name)) + TCPClient._conns[key] = TCPConnection(sock) + except TypeError as e: + raise TCPClientError('Cannot connect to server: %s' % str(e)) + except socket.error as e: + raise TCPClientError('Cannot connect to server: %s' % str(e)) + self._conn = TCPClient._conns[key] + + def request(self, req): + """Sends a request to server and get the response. + + Args: + req: An request. + + Return: + The response. + """ + try: + JSONPackage(req).send(self._conn.send_all) + return JSONPackage(recv_func=self._conn.recv_all).content + except socket.error as e: + self.close() + raise TCPClientError(e) + except JSONPackageError as e: + raise TCPClientError(e) + + def close(self): + """Closes the socket.""" + self._conn.close() + for key, conn in TCPClient._conns.items(): + if conn is self._conn: + del TCPClient._conns[key] + break + + +################################ Some operations ############################### +def init_for_this_time(): + py_bvars.curr_scope = vim.current.buffer + py_wvars.curr_scope = vim.current.window + VimInfo.init() + +def get_my_info(init): + """Gets my information for server. + + Return: + The information for server. + """ + return {JSON_TOKEN.IDENTITY : py_bvars[VARNAMES.IDENTITY], + JSON_TOKEN.INIT : init, + JSON_TOKEN.MODE : VimInfo.mode, + JSON_TOKEN.CURSORS : { + CURSOR_MARK.CURRENT : VimInfo.cursors[CURSOR_MARK.CURRENT], + CURSOR_MARK.V : VimInfo.cursors[CURSOR_MARK.V], + }, + JSON_TOKEN.DIFF : VimInfo.lines.gen_patch( + py_bvars.get(VARNAMES.LINES, ['']))} + + +def set_my_info(json_info): + """Sets my information gived by server. + + Args: + json_info: JSON information gived by server. + """ + VimInfo.lines.apply_patch(json_info[JSON_TOKEN.DIFF]) + py_bvars[VARNAMES.LINES] = VimInfo.lines[:] + mode = json_info[JSON_TOKEN.MODE] + VimInfo.mode = mode + if mode in (MODE.VISUAL, MODE.BLOCK_VISUAL, MODE.LINE_VISUAL): + old_mode, VimInfo.mode = mode, MODE.NORMAL + VimInfo.cursors[CURSOR_MARK.V] = \ + json_info[JSON_TOKEN.CURSORS][CURSOR_MARK.V] + VimInfo.mode = old_mode + VimInfo.cursors[CURSOR_MARK.CURRENT] = \ + json_info[JSON_TOKEN.CURSORS][CURSOR_MARK.CURRENT] + + +def set_others_info(json_info): + """Sets the informations about other user. + + Args: + json_info: JSON information gived by server. + """ + users = json_info[JSON_TOKEN.OTHERS] + VimInfo.highlight.reset([user[JSON_TOKEN.NICKNAME] for user in users]) + for user in users: + name, mode = user[JSON_TOKEN.NICKNAME], user[JSON_TOKEN.MODE] + cursors = user[JSON_TOKEN.CURSORS] + curr_rc = cursors[CURSOR_MARK.CURRENT] + VimInfo.highlight[name].set_mode_cursor(mode, curr_rc) + if mode in (MODE.VISUAL, MODE.LINE_VISUAL, MODE.BLOCK_VISUAL): + last_rc = cursors[CURSOR_MARK.V] + if last_rc[0] > curr_rc[0] or \ + (last_rc[0] == curr_rc[0] and last_rc[1] > curr_rc[1]): + last_rc, curr_rc = curr_rc, last_rc + set_other_visual(name, mode, last_rc, curr_rc) + VimInfo.highlight.render() + +def set_other_visual(name, mode, beg, end): + """Sets the other user's visual block. + + Args: + name: Name of this user. + mode: Mode of this user. + beg: The first row-column position of the range. + end: The last row-column position of the range. + """ + if mode == MODE.VISUAL: + for row in range(beg[0], end[0] + 1): + col_beg = 0 if row != beg[0] else beg[1] + col_end = (len(VimInfo.lines[row]) if row != end[0] else end[1]) + 1 + VimInfo.highlight[name].add_visual((row, col_beg, col_end)) + elif mode == MODE.LINE_VISUAL: + for row in range(beg[0], end[0] + 1): + VimInfo.highlight[name].add_visual( + (row, 0, len(VimInfo.lines[row]) + 1)) + elif mode == MODE.BLOCK_VISUAL: + set_other_block_visual(name, beg, end) + + +def set_other_block_visual(name, beg, end): + """Sets the other user's virtical-visual block. + + Args: + name: Name of this user. + beg: The first row-column position of the range. + end: The last row-column position of the range. + """ + w1 = VimInfo.display_width(VimInfo.lines[beg[0]][ : beg[1]]) + w2 = VimInfo.display_width(VimInfo.lines[end[0]][ : end[1]]) + left, right = min(w1, w2), max(w1, w2) + for row in range(beg[0], end[0] + 1): + for beg_col in range(len(VimInfo.lines[row]) + 1): + w = VimInfo.display_width(VimInfo.lines[row][ : beg_col]) + if left < w: + beg_col -= 1 + break + else: + continue + for end_col in range(beg_col, len(VimInfo.lines[row]) + 1): + w = VimInfo.display_width(VimInfo.lines[row][ : end_col]) + if right < w: + break + VimInfo.highlight[name].add_visual((row, beg_col, end_col)) + + +############################## Supported operations ############################ +def connect(server_name, server_port, identity): + """Connects to the server. + + Args: + server_name: Server name. + server_port: Server port. + identity: Identity string of this user. + """ + init_for_this_time() + if len(VimInfo.lines) > 1 or len(VimInfo.lines[0]) > 0: + c = VimInfo.confirm( + 'This tool will rewrite the whole buffer, continue? ', + ['Yes', 'No'], 'No') + if c != 0: + return + VimInfo.lines[:] = [''] + py_bvars[VARNAMES.SERVER_NAME] = server_name + py_bvars[VARNAMES.SERVER_PORT] = int(server_port) + py_bvars[VARNAMES.IDENTITY] = identity + sync(init=True) + + +def sync(init=False): + """Sync with the server. + + Args: + init: Flag for whether it should tell the server to reset this user or + not. + """ + init_for_this_time() + if VARNAMES.SERVER_NAME in py_bvars: + try: + conn = TCPClient(py_bvars[VARNAMES.SERVER_NAME], + py_bvars[VARNAMES.SERVER_PORT]) + response = conn.request(get_my_info(init)) + except TCPClientError as e: + print(str(e)) + return + if JSON_TOKEN.ERROR in response: + print(response[JSON_TOKEN.ERROR]) + return + set_my_info(response) + set_others_info(response) + py_bvars[VARNAMES.USERS] = ', '.join( + [user[JSON_TOKEN.NICKNAME] for user in response[JSON_TOKEN.OTHERS]]) + + +def disconnect(): + """Disconnects with the server.""" + init_for_this_time() + if VARNAMES.SERVER_NAME in py_bvars: + VimInfo.highlight.reset([]) + VimInfo.highlight.render() + try: + conn = TCPClient(py_bvars[VARNAMES.SERVER_NAME], + py_bvars[VARNAMES.SERVER_PORT]) + conn.request({ + JSON_TOKEN.BYE : True, + JSON_TOKEN.IDENTITY : py_bvars[VARNAMES.IDENTITY]}) + except TCPClientError as e: + print(str(e)) + conn.close() + del py_bvars[VARNAMES.SERVER_NAME] + del py_bvars[VARNAMES.SERVER_PORT] + del py_bvars[VARNAMES.IDENTITY] + print('bye') + + +def show_info(): + """Shows the informations.""" + init_for_this_time() + print('Highlight information:') + print('Groups of normal cursor position:') + for index in range(VimInfo.highlight.num_of_groups()): + vim.command('hi %s' % NORMAL_CURSOR_GROUP(index)) + print('Groups of insert cursor position:') + for index in range(VimInfo.highlight.num_of_groups()): + vim.command('hi %s' % INSERT_CURSOR_GROUP(index)) + print('Groups of selection area:') + for index in range(VimInfo.highlight.num_of_groups()): + vim.command('hi %s' % VISUAL_GROUP(index)) + print('Users: %r' % py_bvars[VARNAMES.USERS]) + + +def set_bvar(variable_name, value): + """Sets the variable of the current buffer. + + Args: + variable_name: Variable name. + value: Value. + """ + init_for_this_time() + py_bvars[getattr(VARNAMES, variable_name)] = value + print('bvars[%s] = %s' % (getattr(VARNAMES, variable_name), value)) + +EOF +endfunction + +"""""""""""""""""""""""""""""""" Initialize """""""""""""""""""""""""""""""""""" + +if _ShrVimTryUsePython3(0) || _ShrVimTryUsePython2(0) + let g:shrvim_setupped = 1 +endif -- cgit