summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcathook <b01902109@csie.ntu.edu.tw>2014-11-19 11:27:36 +0800
committercathook <b01902109@csie.ntu.edu.tw>2014-11-19 11:27:36 +0800
commit9ab365e6b680abe7af6079b6030c8cc718a0a062 (patch)
treed99e5daa8bb838a59d23d42aefa865f96e192eb3
parent96378f825fc71d917d49b3577d523cfa6167393a (diff)
downloadvim-shrvim-9ab365e6b680abe7af6079b6030c8cc718a0a062.tar
vim-shrvim-9ab365e6b680abe7af6079b6030c8cc718a0a062.tar.gz
vim-shrvim-9ab365e6b680abe7af6079b6030c8cc718a0a062.tar.bz2
vim-shrvim-9ab365e6b680abe7af6079b6030c8cc718a0a062.tar.lz
vim-shrvim-9ab365e6b680abe7af6079b6030c8cc718a0a062.tar.xz
vim-shrvim-9ab365e6b680abe7af6079b6030c8cc718a0a062.tar.zst
vim-shrvim-9ab365e6b680abe7af6079b6030c8cc718a0a062.zip
big change...
-rw-r--r--server/src/authority_string_transformer.py6
-rw-r--r--server/src/cmd_ui.py10
-rw-r--r--server/src/log.py4
-rw-r--r--server/src/request_handler.py205
-rw-r--r--server/src/text_chain.py33
-rw-r--r--server/src/users_text_manager.py24
-rw-r--r--vim/plugin/shared_vim.vim373
7 files changed, 449 insertions, 206 deletions
diff --git a/server/src/authority_string_transformer.py b/server/src/authority_string_transformer.py
index f99daa1..2d58e6e 100644
--- a/server/src/authority_string_transformer.py
+++ b/server/src/authority_string_transformer.py
@@ -3,7 +3,7 @@
from users_text_manager import AUTHORITY
-_mappings = [
+_MAPPINGS = [
(AUTHORITY.READONLY, 'RO'),
(AUTHORITY.READWRITE, 'RW'),
]
@@ -23,7 +23,7 @@ def to_string(authority):
Return:
Corrosponding authority in string format.
"""
- for item in _mappings:
+ for item in _MAPPINGS:
if item[0] == authority:
return item[1]
raise Error('Invalid number.')
@@ -38,7 +38,7 @@ def to_number(string):
Return:
Corrosponding authority in number format.
"""
- for item in _mappings:
+ for item in _MAPPINGS:
if item[1] == string:
return item[0]
raise Error('Invalid string.')
diff --git a/server/src/cmd_ui.py b/server/src/cmd_ui.py
index b7079da..94cbf61 100644
--- a/server/src/cmd_ui.py
+++ b/server/src/cmd_ui.py
@@ -40,7 +40,7 @@ class CmdUI(cmd.Cmd): # pylint: disable=R0904
tcp_server: An instance of TCPServer.
shared_vim_server: An instance of SharedVimServer.
"""
- super(CmdUI, self).__init__(INTRO)
+ super(CmdUI, self).__init__()
self.prompt = PROMPT
self._users_text_manager = users_text_manager
self._tcp_server = tcp_server
@@ -182,6 +182,14 @@ class CmdUI(cmd.Cmd): # pylint: disable=R0904
"""Echo."""
print(text)
+ def do_eval(self, text):
+ """For debug, evaluate an peice of python code and prints the result."""
+ try:
+ result = eval(text) # pylint: disable=W0123
+ self.write('The result is %r\n' % result)
+ except Exception as e: # pylint: disable=W0703
+ self.write('Exception occured: %r' % e)
+
def do_help(self, text):
"""Prints the help document, [usage] help"""
try:
diff --git a/server/src/log.py b/server/src/log.py
index 93ec9bc..9873ade 100644
--- a/server/src/log.py
+++ b/server/src/log.py
@@ -14,7 +14,7 @@ def info(string):
string: String to be printed.
"""
with _lock:
- info.interface.write(string)
+ info.interface.write('info: ' + string)
info.interface.flush()
info.interface = sys.stdout # Interface of the info string to be printed at.
@@ -27,7 +27,7 @@ def error(string):
string: String to be printed.
"""
with _lock:
- error.interface.write(string)
+ error.interface.write('error: ' + string)
error.interface.flush()
error.interface = sys.stderr # Interface of the error string to be printed at.
diff --git a/server/src/request_handler.py b/server/src/request_handler.py
index 9d820e4..458c554 100644
--- a/server/src/request_handler.py
+++ b/server/src/request_handler.py
@@ -1,5 +1,7 @@
"""RequestHandler."""
+import bisect
+import difflib
import log
from users_text_manager import AUTHORITY
@@ -10,19 +12,145 @@ 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.
- TEXT = 'text' # text content in the buffer
+
+
+def apply_patch(orig_lines, patch_info):
+ """Applies a patch.
+
+ Args:
+ orig_lines: Original lines of the text.
+ patch_info: A list of replacing information.
+
+ Return:
+ A list of text.
+ """
+ new_lines, done_len = [], 0
+ for beg, end, lines in patch_info:
+ new_lines += orig_lines[done_len : beg]
+ new_lines += lines
+ done_len = end
+ return new_lines + orig_lines[done_len : ]
+
+
+def _squash_patch(patch_info):
+ """Squash list of replacing information to a smaller mount of information.
+
+ Args:
+ patch_info: Information of patches.
+
+ Return:
+ A list of replacing information.
+ """
+ ret, index = [], 0
+ while index < len(patch_info):
+ lines = patch_info[index][2]
+ index2 = index + 1
+ while index2 < len(patch_info) and \
+ patch_info[index2 - 1][1] >= patch_info[index2][0]:
+ lines += patch_info[index2][2]
+ index2 += 1
+ ret.append((patch_info[index][0], patch_info[index2 - 1][1], lines))
+ index = index2
+ return ret
+
+
+def gen_patch(orig_lines, new_lines):
+ """Creates a patch from two lines text.
+
+ Args:
+ orig_lines: Original lines of the text.
+ new_lines: New lines of the text.
+
+ Return:
+ A list of replacing information.
+ """
+ diff_result = list(difflib.Differ().compare(orig_lines, new_lines))
+ orig_index, ret = 0, []
+ for line in diff_result:
+ if line.startswith(' '):
+ orig_index += 1
+ elif line.startswith('+ '):
+ ret.append((orig_index, orig_index, [line[2 : ]]))
+ elif line.startswith('- '):
+ ret.append((orig_index, orig_index + 1, []))
+ orig_index += 1
+ return _squash_patch(ret)
+
+
+class _CursorTransformer(object):
+ """Transformer for format of the cursor position.
+
+ In vim, it use (row, col) to represent an cursor's position.
+ In UsersTextManager, it use numerical notation (byte distance between the
+ first byte in the text).
+
+ Attributes:
+ _sum_len: Sum of each row.
+ """
+ def __init__(self):
+ """Constructor."""
+ self._sum_len = [0]
+
+ def update_lines(self, lines):
+ """Update lines of text.
+
+ Args:
+ lines: Lines of text.
+ """
+ self._sum_len = [0]
+ for line in lines:
+ delta = len(line) + 1 # "+ 1" is for newline char
+ self._sum_len.append(self._sum_len[-1] + delta)
+
+ def rcs_to_nums(self, rcs):
+ """Transform row-col format's cursor position to numerical type.
+
+ Args:
+ lines: List of line text.
+ rcs: List of tuple of row-col format cursor postions
+
+ Return:
+ A list of numerical cursor positions.
+ """
+ ret = []
+ for rc in rcs:
+ base = self._sum_len[min(rc[0], len(self._sum_len) - 1)]
+ ret.append(min(base + rc[1], self._sum_len[-1]))
+ return ret
+
+
+ def nums_to_rcs(self, nums):
+ """Transform numerical format's cursor position to row-col format.
+
+ Args:
+ lines: List of line text.
+ nums: A list of numerical cursor positions.
+
+ Return:
+ List of tuple of row-col format cursor postions
+ """
+ ans, rmin = {}, 0
+ for num in sorted(nums):
+ row = bisect.bisect(self._sum_len, num, lo=rmin) - 1
+ col = (num - self._sum_len[row]) if row < len(self._sum_len) else 0
+ rmin = row + 1
+ ans[num] = (row, col)
+ return [ans[num] for num in nums]
+
class RequestHandler(object):
"""Handles all kinds of request.
Attributes:
_users_text_manager: An instance of UsersTextManager.
+ _cursor_transformer: An instance of _CursorTransformer.
"""
def __init__(self, users_text_manager):
"""Constructor.
@@ -32,6 +160,7 @@ class RequestHandler(object):
"""
super(RequestHandler, self).__init__()
self._users_text_manager = users_text_manager
+ self._cursor_transformer = _CursorTransformer()
def handle(self, request):
"""Handles the request and returns the response.
@@ -74,41 +203,68 @@ class RequestHandler(object):
identity: The identity of that user.
request: The request from that user.
"""
- if all(key in request for key in [JSON_TOKEN.INIT,
- JSON_TOKEN.TEXT,
- JSON_TOKEN.MODE,
- JSON_TOKEN.CURSORS]):
+ if all(key in request for key in [JSON_TOKEN.INIT, JSON_TOKEN.DIFF,
+ JSON_TOKEN.MODE, JSON_TOKEN.CURSORS]):
log.info('handle sync-request from %r\n' % identity)
self._check_init(identity, request)
self._check_authority(identity, request)
+ lines = apply_patch(
+ self._users_text_manager.get_user_text(identity).split('\n'),
+ request[JSON_TOKEN.DIFF])
+ self._cursor_transformer.update_lines(lines)
+ cursors = dict(zip(request[JSON_TOKEN.CURSORS].keys(),
+ self._cursor_transformer.rcs_to_nums(
+ request[JSON_TOKEN.CURSORS].values())))
new_user_info, new_text = self._users_text_manager.update_user_text(
identity,
- UserInfo(mode=request[JSON_TOKEN.MODE],
- cursors=request[JSON_TOKEN.CURSORS]),
- request[JSON_TOKEN.TEXT])
- return self._pack_sync_response(identity, new_user_info, new_text)
+ UserInfo(mode=request[JSON_TOKEN.MODE], cursors=cursors),
+ '\n'.join(lines))
+ return self._pack_sync_response(
+ identity, new_user_info, new_text.split('\n'), lines)
- def _pack_sync_response(self, identity, user_info, text):
+ def _pack_sync_response(self, identity, user_info, lines, old_lines):
"""Packs the response for the sync request by the result from manager.
Args:
identity: Identity of that user.
user_info: Informations of that user.
- text: New text.
+ lines: New lines of text.
+ old_lines: Old lines of text.
+
+ Return:
+ The response json object.
+ """
+ self._cursor_transformer.update_lines(lines)
+ return {
+ JSON_TOKEN.DIFF : gen_patch(old_lines, lines),
+ JSON_TOKEN.CURSORS : dict(zip(
+ user_info.cursors.keys(),
+ self._cursor_transformer.nums_to_rcs(
+ user_info.cursors.values()))),
+ JSON_TOKEN.MODE : user_info.mode,
+ JSON_TOKEN.OTHERS : self._pack_sync_others_response(identity)
+ }
+
+ def _pack_sync_others_response(self, identity):
+ """Packs the response information for other users.
+
+ Args:
+ identity: Identity of that user.
Return:
The response json object.
"""
- return {JSON_TOKEN.TEXT : text,
- JSON_TOKEN.CURSORS : user_info.cursors,
- JSON_TOKEN.MODE : user_info.mode,
- JSON_TOKEN.OTHERS : [
- {JSON_TOKEN.NICKNAME : other.nick_name,
- JSON_TOKEN.MODE : other.mode,
- JSON_TOKEN.CURSORS: other.cursors}
- for iden, other in self._users_text_manager.get_users_info(
- without=[identity], must_online=True).items()
- ]}
+ return [
+ {
+ JSON_TOKEN.NICKNAME : other.nick_name,
+ JSON_TOKEN.MODE : other.mode,
+ JSON_TOKEN.CURSORS: dict(zip(
+ other.cursors.keys(),
+ self._cursor_transformer.nums_to_rcs(
+ other.cursors.values())))
+ } for iden, other in self._users_text_manager.get_users_info(
+ without=[identity], must_online=True).items()
+ ]
def _check_init(self, identity, request):
"""Checks whether that user should be initialize or not.
@@ -122,9 +278,9 @@ class RequestHandler(object):
if request[JSON_TOKEN.INIT]:
log.info('Init the user %r\n' % identity)
self._users_text_manager.reset_user(identity)
- request[JSON_TOKEN.TEXT] = ''
+ request[JSON_TOKEN.DIFF] = []
for mark in request.get(JSON_TOKEN.CURSORS, []):
- request[JSON_TOKEN.CURSORS][mark] = 0
+ request[JSON_TOKEN.CURSORS][mark] = (0, 0)
def _check_authority(self, identity, request):
"""Checks the authroity and updates the request.
@@ -138,5 +294,4 @@ class RequestHandler(object):
"""
auth = self._users_text_manager.get_users_info()[identity].authority
if auth < AUTHORITY.READWRITE:
- old_text = self._users_text_manager.get_user_text(identity)
- request[JSON_TOKEN.TEXT] = old_text
+ request[JSON_TOKEN.DIFF] = []
diff --git a/server/src/text_chain.py b/server/src/text_chain.py
index d60ea02..df2c9ba 100644
--- a/server/src/text_chain.py
+++ b/server/src/text_chain.py
@@ -53,8 +53,10 @@ class TextChain(object):
for info in cursors_info:
info.apply_commits([cm[1] for cm in self._commits[old_index + 1 :]])
new_cursors = [cursor_info.position for cursor_info in cursors_info]
- self._commits.append((new_id, commit))
+ self._commits += [(new_id, commit),
+ (new_id + 1, _TextCommit(commit.text, commit.text))]
self.delete(orig_id)
+ self.delete(self._commits[-3][0])
self._save()
return new_id, commit.text, new_cursors
@@ -190,7 +192,7 @@ class _TextCommit(object):
@property
def increased_length(self):
"""Gets the increased length of this commit."""
- return sum([o.increased_length for o in self._opers])
+ return sum(o.increased_length for o in self._opers)
def copy(self):
"""Returns a copy of myself.
@@ -218,17 +220,9 @@ class _TextCommit(object):
def get_cursor_info(self, cursor_pos):
"""Gets the cursor information by gived cursor position.
- If the cursor position is in a place that will be modified at this
- commit, it will return _CursorInfo_OnNewCommit; Otherwise it will
- return _CursorInfo_OnOrigText.
- Ex:
- The original text with the only oper be "Chage [4, 9) to another
- string":
- 0 1 2 3 4 5 6 7 8 91011121314
- a b c d[e f g h i]j k l m n o
- ^ ^ ^ ^ | | | | | | ^ ^ ^ ^ ^ ^
- Then for the "^", they belone to _CursorInfo_OnOrigText;
- Otherwise they belone to _CursorInfo_OnNewCommit.
+ If the cursor position is on character which is inserted at this commit,
+ it will return _CursorInfo_OnNewCommit; Otherwise it will return
+ _CursorInfo_OnOrigText.
Args:
cursor_pos: Position of the cursor.
@@ -236,9 +230,12 @@ class _TextCommit(object):
Return:
A instance of _CursorInfo_OnNewCommit or _CursorInfo_OnOrigText.
"""
+ offset = 0
for oper in self._opers:
- if oper.begin <= cursor_pos <= oper.end:
- return _CursorInfo_OnNewCommit(oper, cursor_pos - oper.begin)
+ delta = cursor_pos - (oper.begin + offset)
+ if delta <= len(oper.new_text):
+ return _CursorInfo_OnNewCommit(oper, delta)
+ offset += oper.increased_length
return _CursorInfo_OnOrigText(cursor_pos)
def _rebase_text(self, new_orig_text):
@@ -293,10 +290,12 @@ class _CursorInfo_OnNewCommit(object):
def position(self):
"""Calculates and returns the final cursor position."""
dt = self._delta
+ offset = 0
for oper in self._opers:
- if oper.begin + dt <= oper.end:
- return oper.begin + dt
+ if dt <= len(oper.new_text):
+ return oper.begin + offset + dt
dt -= len(oper.new_text)
+ offset += oper.increased_length
class _CursorInfo_OnOrigText(object):
diff --git a/server/src/users_text_manager.py b/server/src/users_text_manager.py
index 93d9d94..cb86e9d 100644
--- a/server/src/users_text_manager.py
+++ b/server/src/users_text_manager.py
@@ -113,9 +113,9 @@ class UsersTextManager(object):
with self._rlock:
without = without if without is not None else []
online_check = lambda x: (x != UNKNOWN if must_online else True)
- return dict([pair for pair in self._users.items()
- if pair[0] not in without and \
- online_check(pair[1].mode)])
+ return dict(pair for pair in self._users.items()
+ if pair[0] not in without and \
+ online_check(pair[1].mode))
def update_user_text(self, identity, new_user_info, new_text):
"""Updates a user's information with new information and text.
@@ -134,14 +134,14 @@ class UsersTextManager(object):
self._users[identity].last_commit_id, new_text, curs)
self._users[identity].last_commit_id = new_commit_id
self._users[identity].mode = new_user_info.mode
- self._users[identity].cursors = _lists_to_dict(curmarks, new_curs)
+ self._users[identity].cursors = dict(zip(curmarks, new_curs))
for iden, user in self._users.items():
if iden == identity:
continue
curmarks = user.cursors.keys()
curs = [user.cursors[mark] for mark in curmarks]
new_curs = self._text_chain.update_cursors(curs)
- user.cursors = _lists_to_dict(curmarks, new_curs)
+ user.cursors = dict(zip(curmarks, new_curs))
return (self._users[identity], new_text)
def get_user_text(self, identity):
@@ -156,17 +156,3 @@ class UsersTextManager(object):
with self._rlock:
return self._text_chain.get_text(
self._users[identity].last_commit_id)
-
-
-def _lists_to_dict(list1, list2):
- """Combines each element in two lists.
-
- Args:
- list1: The first list.
- list2: The second list.
-
- Return:
- A list with each element be a tuple of two element in list1 and list2.
- """
- l1, l2 = list(list1), list(list2)
- return {l1[index] : l2[index] for index in range(len(l1))}
diff --git a/vim/plugin/shared_vim.vim b/vim/plugin/shared_vim.vim
index 27e825c..d1fdf9d 100644
--- a/vim/plugin/shared_vim.vim
+++ b/vim/plugin/shared_vim.vim
@@ -70,11 +70,13 @@ function! _SharedVimCallPythonFunc(func_name, args)
endif
endfunction
+
function! _SharedVimAutoSync(level)
- if !exists('b:shared_vim_auto_sync_level')
- let b:shared_vim_auto_sync_level = g:shared_vim_auto_sync_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 <= b:shared_vim_auto_sync_level
+ if a:level <= level
SharedVimSync
endif
endfunction
@@ -84,9 +86,7 @@ function! _SharedVimSetup()
SharedVimPython << EOF
# python << EOF
# ^^ Force vim highlighting the python code below.
-import bisect
import json
-import re
import socket
import sys
import vim
@@ -116,9 +116,10 @@ class MODE: # pylint:disable=W0232
class VARNAMES: # pylint: disable=W0232
"""Enumeration types of variable name in vim."""
- GROUP_NAME_PREFIX = 'gnp' # Group name's prefix.
+ 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.
@@ -141,15 +142,15 @@ DEFAULT_NUM_GROUPS = 5
class JSON_TOKEN: # pylint:disable=W0232
"""Enumeration the Ttken strings for json object."""
- BYE = 'bye' # Disconnects with the server.
+ 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.
- TEXT = 'text' # text content in the buffer
############### Handler for Variable stores only in python #####################
@@ -237,11 +238,7 @@ py_wvars = ScopeVars()
############################### Interface to vim ###############################
class VimCursorsInfo(object): # pylint: disable=W0232
"""Gets/sets the cursor position in vim."""
- def __init__(self, *_):
- """Constructor."""
- self._text_num_sum = []
-
- def __getitem__(self, mark):
+ def __getitem__(self, mark): # pylint: disable=R0201
"""Gets the cursor position with specifying the mark.
Args:
@@ -251,86 +248,50 @@ class VimCursorsInfo(object): # pylint: disable=W0232
Cursor position.
"""
pos = [int(x) for x in vim.eval('getpos("%s")' % mark)]
- return self.rc_to_num((pos[1] - 1, pos[2] - 1))
+ 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, value):
+ 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.num_to_rc(value)
+ 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 rc_to_num(self, rc):
- """Transforms row-column cursor position to bytes position.
-
- Args:
- rc: Row-column cursor position.
-
- Return:
- byte position.
- """
- base = self._text_num_sum[rc[0] - 1] if rc[0] > 0 else 0
- line = VimInfo.transform_to_vim(VimInfo.lines[rc[0]])
- return base + len(VimInfo.transform_to_py(line[ : rc[1]]))
-
- def num_to_rc(self, num, rmin=0):
- """Transforms byte position to row-column cursor position.
+ def transform_to_vim(self, rc): # pylint: disable=R0201
+ """Transform rc in utf-8 to rc in bytes.
Args:
- num: byte cursor position.
+ rc: Cursor position.
Return:
- List of row-column position.
- """
- row = bisect.bisect_right(self._text_num_sum, num, lo=rmin)
- col = num - (0 if row == 0 else self._text_num_sum[row - 1])
- line = VimInfo.lines[row][ : col] if row < len(VimInfo.lines) else ''
- return (row, len(VimInfo.transform_to_vim(line)))
-
- def nums_to_rcs(self, nums):
- """Transforms list of sorted byte positions.
-
- Args:
- nums: list of byte cursor positions.
-
- Return:
- List of row-column positions.
- """
- ret, last_r = [], 0
- for num in nums:
- my_rc = self.num_to_rc(num, rmin=last_r)
- ret += [my_rc]
- last_r = my_rc[0]
- return ret
-
- def update_text_lines(self):
- """Updates the content lines.
-
- Args:
- lines: List of content lines.
+ Cursor position.
"""
- self._text_num_sum, pre = [], 0
- for line in VimInfo.lines:
- self._text_num_sum += [pre + len(line) + 1]
- pre += len(line) + 1
+ 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_positions: List of cursor positions in normal mode.
- _insert_cursor_positions: List of cursor positions in insert mode.
- _visual_positions: List of positions in visual blocks.
+ _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_positions = []
- self._insert_cursor_positions = []
- self._visual_positions = []
+ self._normal_cursor_ranges = []
+ self._insert_cursor_ranges = []
+ self._visual_ranges = []
def set_mode_cursor(self, mode, rc):
"""Sets the mode and the cursor.
@@ -340,32 +301,32 @@ class GroupInfo(object):
rc: The cursor position.
"""
if mode in (MODE.INSERT, MODE.REPLACE):
- self._insert_cursor_positions += [rc]
+ self._insert_cursor_ranges.append((rc[0], rc[1], rc[1] + 1))
else:
- self._normal_cursor_positions += [rc]
+ self._normal_cursor_ranges.append((rc[0], rc[1], rc[1] + 1))
- def add_visual(self, rc):
+ def add_visual(self, rang):
"""Add a visual position.
Args:
- rc: The position.
+ rang: The position.
"""
- self._visual_positions += [rc]
+ self._visual_ranges += [rang]
@property
- def normal_cursor_positions(self):
- """Gets the normal cursor positions."""
- return self._normal_cursor_positions
+ def normal_cursor_ranges(self):
+ """Gets the normal cursor ranges."""
+ return self._normal_cursor_ranges
@property
- def insert_cursor_positions(self):
- """Gets the insert cursor positions."""
- return self._insert_cursor_positions
+ def insert_cursor_ranges(self):
+ """Gets the insert cursor ranges."""
+ return self._insert_cursor_ranges
@property
- def visual_positions(self):
- """Gets the visual positions."""
- return self._visual_positions
+ def visual_ranges(self):
+ """Gets the visual ranges."""
+ return self._visual_ranges
class VimHighlightInfo(object):
@@ -406,11 +367,11 @@ class VimHighlightInfo(object):
"""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_positions)
+ self._groups[index].normal_cursor_ranges)
VimInfo.match(INSERT_CURSOR_GROUP(index), MATCH_PRI.INSERT,
- self._groups[index].insert_cursor_positions)
+ self._groups[index].insert_cursor_ranges)
VimInfo.match(VISUAL_GROUP(index), MATCH_PRI.VISUAL,
- self._groups[index].visual_positions)
+ self._groups[index].visual_ranges)
def _get_group_id(self, string):
"""Transform the gived string to a valid group index.
@@ -432,6 +393,91 @@ class VimHighlightInfo(object):
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.
@@ -449,29 +495,16 @@ class VimInfoMeta(type):
def __init__(self, *args):
"""Constructor."""
self._mode = None
+ self._lines = None
- @property
- def lines(self): # pylint: disable=R0201
- """Gets list of lines in the buffer."""
- return [VimInfo.transform_to_py(line) for line in vim.current.buffer[:]]
-
- @lines.setter
- def lines(self, lines): # pylint: disable=R0201
- """Sets the buffer by list of lines."""
- tr = [VimInfo.transform_to_vim(line) for line in lines]
- vim.current.buffer[0 : len(vim.current.buffer)] = tr
+ def init(self):
+ """Initialize informations for this time."""
+ self._lines = VimLinesInfo()
@property
- def text(self):
- """Gets the buffer text."""
- return '\n'.join(self.lines)
-
- @text.setter
- def text(self, text):
- """Sets the buffer text."""
- lines = re.split('\n', text)
- self.lines = lines if lines else ['']
- VimInfo.cursors.update_text_lines()
+ def lines(self):
+ """Gets list of lines in the buffer."""
+ return self._lines
@property
def mode(self):
@@ -523,8 +556,13 @@ class VimInfoMeta(type):
vim.eval('matchdelete(%d)' % last_id)
del py_wvars[VARNAMES.GROUP_NAME_PREFIX + group_name]
if positions:
- rcs = [(rc[0] + 1, rc[1] + 1) for rc in positions]
- patterns = '\\|'.join(['\\%%%dl\\%%%dc' % rc for rc in rcs])
+ 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:
@@ -558,6 +596,37 @@ class VimInfoMeta(type):
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):
@@ -570,10 +639,7 @@ def with_metaclass(meta, *bases):
class VimInfo(with_metaclass(VimInfoMeta, object)): # pylint: disable=W0232
"""An interface for accessing the vim's vars, buffer, cursors, etc."""
- @staticmethod
- def init():
- """Initializes some settings."""
- VimInfo.cursors.update_text_lines()
+ pass
########################### About connection to server #########################
@@ -742,6 +808,7 @@ class TCPClient(object):
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)
@@ -756,9 +823,10 @@ class TCPClient(object):
################################ Some operations ###############################
-def set_scopes():
- py_bvars.curr_scope = vim.current.buffer.number
- py_wvars.curr_scope = vim.current.window.number
+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.
@@ -773,7 +841,8 @@ def get_my_info(init):
CURSOR_MARK.CURRENT : VimInfo.cursors[CURSOR_MARK.CURRENT],
CURSOR_MARK.V : VimInfo.cursors[CURSOR_MARK.V],
},
- JSON_TOKEN.TEXT : VimInfo.text}
+ JSON_TOKEN.DIFF : VimInfo.lines.gen_patch(
+ py_bvars.get(VARNAMES.LINES, ['']))}
def set_my_info(json_info):
@@ -782,7 +851,8 @@ def set_my_info(json_info):
Args:
json_info: JSON information gived by server.
"""
- VimInfo.text = json_info[JSON_TOKEN.TEXT]
+ 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):
@@ -805,10 +875,10 @@ def set_others_info(json_info):
for user in users:
name, mode = user[JSON_TOKEN.NICKNAME], user[JSON_TOKEN.MODE]
cursors = user[JSON_TOKEN.CURSORS]
- curr_rc = VimInfo.cursors.num_to_rc(cursors[CURSOR_MARK.CURRENT])
+ 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 = VimInfo.cursors.num_to_rc(cursors[CURSOR_MARK.V])
+ 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
@@ -826,19 +896,41 @@ def set_other_visual(name, mode, beg, end):
"""
if mode == MODE.VISUAL:
for row in range(beg[0], end[0] + 1):
- first = 0 if row != beg[0] else beg[1]
- last = len(VimInfo.lines[row]) if row != end[0] else end[1]
- for col in range(first, last + 1):
- VimInfo.highlight[name].add_visual((row, col))
+ 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):
- for col in range(0, len(VimInfo.lines[row])):
- VimInfo.highlight[name].add_visual((row, col))
+ VimInfo.highlight[name].add_visual(
+ (row, 0, len(VimInfo.lines[row]) + 1))
elif mode == MODE.BLOCK_VISUAL:
- left, right = min([beg[1], end[1]]), max([beg[1], end[1]])
- for row in range(beg[0], end[0] + 1):
- for col in range(left, right + 1):
- VimInfo.highlight[name].add_visual((row, col))
+ 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 ############################
@@ -850,7 +942,14 @@ def connect(server_name, server_port, identity):
server_port: Server port.
identity: Identity string of this user.
"""
- set_scopes()
+ 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
@@ -864,7 +963,7 @@ def sync(init=False):
init: Flag for whether it should tell the server to reset this user or
not.
"""
- set_scopes()
+ init_for_this_time()
if VARNAMES.SERVER_NAME in py_bvars:
try:
conn = TCPClient(py_bvars[VARNAMES.SERVER_NAME],
@@ -884,7 +983,7 @@ def sync(init=False):
def disconnect():
"""Disconnects with the server."""
- set_scopes()
+ init_for_this_time()
if VARNAMES.SERVER_NAME in py_bvars:
VimInfo.highlight.reset([])
VimInfo.highlight.render()
@@ -905,7 +1004,7 @@ def disconnect():
def show_info():
"""Shows the informations."""
- set_scopes()
+ init_for_this_time()
print('Highlight information:')
print('Groups of normal cursor position:')
for index in range(VimInfo.highlight.num_of_groups()):
@@ -926,14 +1025,10 @@ def set_bvar(variable_name, value):
variable_name: Variable name.
value: Value.
"""
- set_scopes()
+ init_for_this_time()
py_bvars[getattr(VARNAMES, variable_name)] = value
print('bvars[%s] = %s' % (getattr(VARNAMES, variable_name), value))
-
-################################## Initialize ##################################
-VimInfo.init()
-
EOF
endfunction