summaryrefslogtreecommitdiffstats
path: root/vim/plugin/shrvim.vim
diff options
context:
space:
mode:
Diffstat (limited to 'vim/plugin/shrvim.vim')
-rw-r--r--vim/plugin/shrvim.vim1039
1 files changed, 1039 insertions, 0 deletions
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', [<f-args>])
+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', <f-args>])
+command! -nargs=1 ShrVimSetNumOfGroups call _ShrVimCallPythonFunc('set_bvar', ['NUM_GROUPS', <f-args>])
+
+
+""""""""""""""""""""""""""""""""""""" 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 <args>
+ 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 <args>
+ 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! \\<esc>"')
+ 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(<num_of_bytes>) => bytes with length <num_of_bytes>
+
+ 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