summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcathook <b01902109@csie.ntu.edu.tw>2014-11-16 11:55:34 +0800
committercathook <b01902109@csie.ntu.edu.tw>2014-11-16 11:57:45 +0800
commit96378f825fc71d917d49b3577d523cfa6167393a (patch)
treea734e0e2fd8ce1a0911da5af6adf796b4db81260
parent3d44f17459f1a55f4102dbe27000dd92de82bea3 (diff)
downloadvim-shrvim-96378f825fc71d917d49b3577d523cfa6167393a.tar
vim-shrvim-96378f825fc71d917d49b3577d523cfa6167393a.tar.gz
vim-shrvim-96378f825fc71d917d49b3577d523cfa6167393a.tar.bz2
vim-shrvim-96378f825fc71d917d49b3577d523cfa6167393a.tar.lz
vim-shrvim-96378f825fc71d917d49b3577d523cfa6167393a.tar.xz
vim-shrvim-96378f825fc71d917d49b3577d523cfa6167393a.tar.zst
vim-shrvim-96378f825fc71d917d49b3577d523cfa6167393a.zip
Big change:
Simplify the json_package. Simplify the authority_string_transformer. Remove the VimVar and use ScopeVars because all the variable now just needs to store in python. Add new module request_handler for simplifying the module tcp_server.
-rw-r--r--server/src/authority_string_transformer.py72
-rw-r--r--server/src/cmd_ui.py159
-rw-r--r--server/src/json_package.py129
-rw-r--r--server/src/request_handler.py142
-rwxr-xr-xserver/src/shared_vim_server.py5
-rw-r--r--server/src/tcp_server.py191
-rw-r--r--server/src/text_chain.py89
-rw-r--r--server/src/users_text_manager.py71
-rw-r--r--vim/plugin/shared_vim.vim575
9 files changed, 669 insertions, 764 deletions
diff --git a/server/src/authority_string_transformer.py b/server/src/authority_string_transformer.py
index 8db484b..f99daa1 100644
--- a/server/src/authority_string_transformer.py
+++ b/server/src/authority_string_transformer.py
@@ -1,42 +1,44 @@
-"""AuthroityStringTransformer."""
+"""For transformating the type of authroity."""
from users_text_manager import AUTHORITY
-class AuthorityStringTransformerError(Exception):
+_mappings = [
+ (AUTHORITY.READONLY, 'RO'),
+ (AUTHORITY.READWRITE, 'RW'),
+]
+
+
+class Error(Exception):
"""Error raised by AuthorityStringTransformer."""
pass
-class AuthorityStringTransformer: # pylint:disable=W0232
- """Transforms authority between number and strin format."""
- @staticmethod
- def to_string(authority):
- """Transform number authority value to string value.
-
- Args:
- authority: Authority in number format.
-
- Returns:
- authority: Corrosponding authority in string format.
- """
- if authority == AUTHORITY.READONLY:
- return 'RO'
- elif authority == AUTHORITY.READWRITE:
- return 'RW'
- raise AuthorityStringTransformerError('Invalid number.')
-
- @staticmethod
- def to_number(string):
- """Transform string authority value to number value.
-
- Args:
- authority: Authority in string format.
-
- Returns:
- authority: Corrosponding authority in number format.
- """
- if string == 'RO':
- return AUTHORITY.READONLY
- elif string == 'RW':
- return AUTHORITY.READWRITE
- raise AuthorityStringTransformerError('Invalid string.')
+
+def to_string(authority):
+ """Transform number authority value to string value.
+
+ Args:
+ authority: Authority in number format.
+
+ Return:
+ Corrosponding authority in string format.
+ """
+ for item in _mappings:
+ if item[0] == authority:
+ return item[1]
+ raise Error('Invalid number.')
+
+
+def to_number(string):
+ """Transform string authority value to number value.
+
+ Args:
+ authority: Authority in string format.
+
+ Return:
+ Corrosponding authority in number format.
+ """
+ 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 e45eb70..b7079da 100644
--- a/server/src/cmd_ui.py
+++ b/server/src/cmd_ui.py
@@ -1,33 +1,43 @@
-"""Command line user interface."""
+"""Command line user interface"""
import cmd
import re
import threading
-from authority_string_transformer import AuthorityStringTransformer
-from authority_string_transformer import AuthorityStringTransformerError
-from users_text_manager import UNKNOWN
-from users_text_manager import UsersTextManagerError
+import authority_string_transformer
-INTRO = ''
+INTRO = 'Type the command "help" for help document.'
PROMPT = '> '
class CmdUI(cmd.Cmd): # pylint: disable=R0904
"""Command line user interface.
+ It's a simple UI for doing operation on server, supplied commands:
+ - Add/delete/reset a user
+ - List (online) users
+ - Save/load the user list to/from a file.
+ - Exit.
+ - Prints the help document.
+
+ And it is also the main output interface of the whole program.
+
Attributes:
_users_text_manager: An instance of UsersTextManager.
_tcp_server: An instance of TcpServer.
_shared_vim_server: An instance of SharedVimServer.
_exit_flag: Whether this UI should stop or not.
_thread: Instance of Thread.
+ _init_cmds: Initialize commands.
"""
- def __init__(self, users_text_manager, tcp_server, shared_vim_server):
+ def __init__(self,
+ init_cmds, users_text_manager, tcp_server, shared_vim_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.
"""
super(CmdUI, self).__init__(INTRO)
@@ -37,126 +47,113 @@ class CmdUI(cmd.Cmd): # pylint: disable=R0904
self._shared_vim_server = shared_vim_server
self._stop_flag = False
self._thread = None
+ self._init_cmds = init_cmds
def do_add(self, text):
"""Adds a user, [usage] add <identity> <nickname> <authority>"""
try:
identity, nickname, authority_str = _split_text(text, 3)
- authority = AuthorityStringTransformer.to_number(authority_str)
+ if identity in self._users_text_manager.get_users_info():
+ self.write('The identity %r is already in used.\n' % identity)
+ return
+ authority = authority_string_transformer.to_number(authority_str)
self._users_text_manager.add_user(identity, nickname, authority)
- self.write('Done\n')
+ user_info = self._users_text_manager.get_users_info()[identity]
+ self.write('Added %s => %s\n' % (identity, str(user_info)))
except _SplitTextError:
self.write('Format error!\n' +
'[usage] add <identity> <nickname> <authority>\n')
- except AuthorityStringTransformerError as e:
- self.write('Fail: %r\n' % e)
- except UsersTextManagerError as e:
+ except authority_string_transformer.Error as e:
self.write('Fail: %r\n' % e)
def do_delete(self, text):
"""Deletes a user, [usage] delete <identity>"""
try:
identity = _split_text(text, 1)[0]
+ if identity not in self._users_text_manager.get_users_info():
+ self.write('The identity %r is not in used.\n' % identity)
+ return
self._users_text_manager.delete_user(identity)
self.write('Done\n')
except _SplitTextError:
self.write('Format error!\n' +
'[usage] delete <identity>\n')
- except UsersTextManagerError as e:
- self.write('Fail: %r\n' % e)
def do_deleteall(self, text):
"""Deletes all users, [usage] deleteall"""
try:
_split_text(text, 0)
- for identity in self._users_text_manager.get_users_info().keys():
+ for identity in self._users_text_manager.get_users_info():
self._users_text_manager.delete_user(identity)
self.write('Done\n')
except _SplitTextError:
self.write('Format error!\n' +
'[usage] deleteall\n')
- except UsersTextManagerError as e:
- self.write('Fail: %r\n' % e)
def do_reset(self, text):
"""Resets a user, [usage] reset <identity>"""
try:
iden = _split_text(text, 1)[0]
if iden not in self._users_text_manager.get_users_info():
- self.write('Fail: Identity %r not found\n' % iden)
- else:
- self._users_text_manager.reset_user(iden)
- self.write('Done\n')
+ self.write('The User with identity %r is not exist.\n' % iden)
+ return
+ self._users_text_manager.reset_user(iden)
+ user_info = self._users_text_manager.get_users_info()[iden]
+ self.write('Reseted %s ==> %s\n' % (iden, str(user_info)))
except _SplitTextError:
self.write('Format error!\n' +
'[usage] reset <identity>\n')
- except UsersTextManagerError as e:
- self.write('Fail: %r\n' % e)
def do_list(self, text):
"""Lists users, [usage] list"""
try:
_split_text(text, 0)
- for iden, user in self._users_text_manager.get_users_info().items():
- self.write('%r => %s' % (iden, user))
+ infos = self._users_text_manager.get_users_info().items()
+ for iden, user in sorted(infos, key=lambda x: x[0]):
+ self.write('%-10s => %s' % (iden, str(user)))
except _SplitTextError:
self.write('Format error!\n' +
'[usage] list\n')
- except UsersTextManagerError as e:
- self.write('Fail: %r\n' % e)
def do_online(self, text):
"""Lists online users, [usage] online"""
try:
_split_text(text, 0)
- for iden, user in self._users_text_manager.get_users_info().items():
- if user.mode == UNKNOWN:
- continue
- self.write('%r => %s' % (iden, user))
+ infos = self._users_text_manager.get_users_info(
+ must_online=True).items()
+ for iden, user in sorted(infos, key=lambda x: x[0]):
+ self.write('%-10s => %s' % (iden, str(user)))
except _SplitTextError:
self.write('Format error!\n' +
'[usage] online\n')
- except UsersTextManagerError as e:
- self.write('Fail: %r\n' % e)
def do_load(self, text):
"""Loads users from a file, [usage] load <filename>"""
try:
filename = _split_text(text, 1)[0]
with open(filename, 'r') as f:
- while True:
- line = f.readline()
- if line.endswith('\n'):
- line = line[:-1]
+ for line in f.readlines():
+ line = line if not line.endswith('\n') else line[ : -1]
if not line:
- break
- try:
- iden, nick, auth_str = _split_text(line, 3)
- auth = AuthorityStringTransformer.to_number(auth_str)
- self._users_text_manager.add_user(iden, nick, auth)
- self.write('Done %s %s %s' % (iden, nick, auth))
- except _SplitTextError:
- self.write('Error format in the file.')
- except AuthorityStringTransformerError as e:
- self.write('Fail: %r\n' % e)
- except UsersTextManagerError as e:
- self.write('Fail: %r\n' % e)
+ continue
+ self.do_add(line)
self.write('Done')
except _SplitTextError:
self.write('Format error!\n' +
'[usage] load <filename>\n')
except IOError as e:
- self.write('Cannot open file? %s' % str(e))
+ self.write('Error occured when opening the file: %r' % e)
def do_save(self, text):
"""Saves users list to file, [usage] save <filename>"""
try:
filename = _split_text(text, 1)[0]
with open(filename, 'w') as f:
- users_info = self._users_text_manager.get_users_info()
- for iden, user in users_info.items():
- auth = AuthorityStringTransformer.to_string(user.authority)
- f.write('%s %s %s\n' % (iden, user.nick_name, auth))
+ users_info = self._users_text_manager.get_users_info().items()
+ for iden, user in sorted(users_info, key=lambda x: x[0]):
+ auth_str = authority_string_transformer.to_string(
+ user.authority)
+ f.write('%s %s %s\n' % (iden, user.nick_name, auth_str))
except _SplitTextError:
self.write('Format error!\n' +
'[usage] save <filename>\n')
@@ -165,12 +162,21 @@ class CmdUI(cmd.Cmd): # pylint: disable=R0904
def do_port(self, text):
"""Print the server's port."""
- _split_text(text, 0)
- self.write('server port = %r' % self._tcp_server.port)
+ try:
+ _split_text(text, 0)
+ self.write('Server port = %r\n' % self._tcp_server.port)
+ except _SplitTextError:
+ self.write('Format error!\n' +
+ '[usage] port\n')
def do_exit(self, text):
"""Exits the program."""
- self._shared_vim_server.stop()
+ try:
+ _split_text(text, 0)
+ self._shared_vim_server.stop()
+ except _SplitTextError:
+ self.write('Format error!\n'
+ '[usage] exit\n')
def do_echo(self, text): # pylint: disable=R0201
"""Echo."""
@@ -178,9 +184,13 @@ class CmdUI(cmd.Cmd): # pylint: disable=R0904
def do_help(self, text):
"""Prints the help document, [usage] help"""
- commands = ['add', 'delete', 'list', 'load', 'save', 'exit', 'echo',
- 'help']
- self.write('commands: \n' + ' '.join(commands))
+ try:
+ _split_text(text, 0)
+ commands = [m[3 : ] for m in dir(self) if m.startswith('do_')]
+ self.write('Commands: \n' + ' '.join(commands))
+ except _SplitTextError:
+ self.write('Format error!\n' +
+ '[usage] help\n')
def do_EOF(self, text): # pylint: disable=C0103
"""Same as exit"""
@@ -202,25 +212,18 @@ class CmdUI(cmd.Cmd): # pylint: disable=R0904
Args:
text: String to be printed.
"""
- self.onecmd('echo ' + text)
+ for line in text.splitlines():
+ self.onecmd('echo ' + line)
- def start(self, init_cmds=None):
- """Starts this CmdUI.
+ def preloop(self):
+ for c in self._init_cmds:
+ self.onecmd(c)
- Args:
- init_cmds: Lists of commands to run after startup.
- """
- def run_cmdloop(cmd_ui):
- """Calls the method cmdloop()
-
- Args:
- cmd_ui: An instnace of CmdUI.
- """
- cmd_ui.cmdloop()
- self._thread = threading.Thread(target=run_cmdloop, args=(self,))
+ def start(self):
+ """Starts this CmdUI."""
+ self._thread = threading.Thread(target=self.cmdloop,
+ args=(INTRO,))
self._thread.start()
- for c in init_cmds if init_cmds else []:
- self.onecmd(c)
def stop(self):
"""Stops the command line UI."""
@@ -245,10 +248,10 @@ def _split_text(text, num):
Args:
text: The string to be splitted.
- num: Length of the tuple.
+ num: Number of elements in the result tuple.
Return:
- A num-tuple.
+ A <num>-tuple.
"""
words = [word for word in re.split(r'[ \t]', text) if word]
if len(words) != num:
diff --git a/server/src/json_package.py b/server/src/json_package.py
index f01e489..a4b4b5e 100644
--- a/server/src/json_package.py
+++ b/server/src/json_package.py
@@ -1,7 +1,6 @@
-"""Contains tcp package object."""
+"""JSONPackage"""
import json
-import zlib
class JSONPackageError(Exception):
@@ -9,106 +8,68 @@ class JSONPackageError(Exception):
pass
class JSONPackage(object):
- """Send/receive json by tcp connection.
+ """Send/receive json object by gived function.
- Attribute:
+ Attributes:
content: Content of the package body.
- """
- ENCODING = 'utf-8'
- COMPRESS_LEVEL = 2
- HEADER_LENGTH = 10
- def __init__(self):
- """Constructor."""
- self.content = None
-
- def send_to(self, fd):
- """Sends a string to the tcp-connection.
-
- Args:
- fd: Socket fd.
- """
- try:
- string = json.dumps(self.content)
- body = JSONPackage._create_body_from_string(string)
- header = JSONPackage._create_header_from_body(body)
- fd.send(header + body)
- except TypeError as e:
- raise JSONPackageError('json: %r' % e)
-
- def recv_from(self, fd):
- """Receives a string from the tcp-connection.
-
- Args:
- fd: Socket fd.
- """
- header = JSONPackage._recv_header_string(fd)
- body = JSONPackage._recv_body_string(fd, header)
- try:
- self.content = json.loads(body)
- except ValueError as e:
- raise JSONPackageError('Cannot loads to the json object: %r' % e)
- @staticmethod
- def _create_body_from_string(string):
- """Creates package body from data string.
+ 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.
- Args:
- string: Data string.
+ If the receive_func is not None, it will grap the default content by
+ calling that function instead of by the argument "content".
- Returns:
- Package body.
- """
- byte_string = string.encode(JSONPackage.ENCODING)
- return zlib.compress(byte_string, JSONPackage.COMPRESS_LEVEL)
-
- @staticmethod
- def _create_header_from_body(body):
- """Creates package header from package body.
+ The detail of arguments/return values format see the method "recv_from".
Args:
- body: Package body.
-
- Returns:
- Package header.
+ content: The default content of this package.
+ recv_func: A function for receive the default content.
"""
- header_string = ('%%0%dd' % JSONPackage.HEADER_LENGTH) % len(body)
- return header_string.encode(JSONPackage.ENCODING)
+ self.content = content
+ if recv_func is not None:
+ self.recv(recv_func)
- @staticmethod
- def _recv_header_string(conn):
- """Receives package header from specified tcp connection.
+ def send(self, send_func):
+ """Sends by calling the gived sending function.
Args:
- conn: The specified tcp connection.
-
- Returns:
- Package header.
+ send_func: A function which will send the whole data gived.
+ Function format:
+ send_func(bytes_data): None
"""
try:
- byte = conn.recv(JSONPackage.HEADER_LENGTH)
- return byte.decode(JSONPackage.ENCODING)
+ body = bytes(json.dumps(self.content), JSONPackage._ENCODING)
+ header_str = ('%%0%dd' % JSONPackage._HEADER_LENGTH) % len(body)
+ send_func(bytes(header_str, JSONPackage._ENCODING) + body)
+ except TypeError as e:
+ raise JSONPackageError('json: %r' % e)
except UnicodeError as e:
- raise JSONPackageError('Cannot decode the header string: %r.' % e)
+ raise JSONPackageError('Cannot encode the string: %r.' % e)
- @staticmethod
- def _recv_body_string(conn, header):
- """Receives package body from specified tcp connection and header.
+ def recv(self, recv_func):
+ """Receives a json object from a gived function.
- Args:
- conn: The specified tcp connection.
- header: The package header.
+ It will calls the give function like this:
+ recv_func(<num_of_bytes>) => bytes with length <num_of_bytes>
- Returns:
- Package body.
+ Args:
+ recv_func: A function to be called to get the serialize data.
"""
try:
- body_length = int(header)
- body = conn.recv(body_length)
- body_byte = zlib.decompress(body)
- return body_byte.decode(JSONPackage.ENCODING)
+ header_str = str(recv_func(JSONPackage._HEADER_LENGTH),
+ JSONPackage._ENCODING)
+ body_str = str(recv_func(int(header_str)), JSONPackage._ENCODING)
except UnicodeError as e:
- raise JSONPackageError('Cannot decode the body string: %r.' % e)
+ raise JSONPackageError('Cannot decode the bytes: %r.' % e)
except ValueError as e:
- raise JSONPackageError('Cannot get the body_length: %r' % e)
- except zlib.error as e:
- raise JSONPackageError('Cannot decompress the body: %r.' % 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)
diff --git a/server/src/request_handler.py b/server/src/request_handler.py
new file mode 100644
index 0000000..9d820e4
--- /dev/null
+++ b/server/src/request_handler.py
@@ -0,0 +1,142 @@
+"""RequestHandler."""
+
+import log
+
+from users_text_manager import AUTHORITY
+from users_text_manager import UserInfo
+
+
+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
+ 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
+
+class RequestHandler(object):
+ """Handles all kinds of request.
+
+ Attributes:
+ _users_text_manager: An instance of UsersTextManager.
+ """
+ def __init__(self, users_text_manager):
+ """Constructor.
+
+ Args:
+ users_text_manager: An instance of UsersTextManager.
+ """
+ super(RequestHandler, self).__init__()
+ self._users_text_manager = users_text_manager
+
+ def handle(self, request):
+ """Handles the request and returns the response.
+
+ Args:
+ request: The request.
+
+ Return
+ The respsonse.
+ """
+ if JSON_TOKEN.IDENTITY not in request:
+ return {JSON_TOKEN.ERROR : 'Bad request.'}
+ identity = request[JSON_TOKEN.IDENTITY]
+ if identity not in self._users_text_manager.get_users_info():
+ return {JSON_TOKEN.ERROR: 'Invalid identity.'}
+ for handler in [self._try_handle_leave,
+ self._try_handle_sync]:
+ response = handler(identity, request)
+ if response is not None:
+ break
+ else:
+ return {JSON_TOKEN.ERROR: 'Bad request.'}
+ return response
+
+ def _try_handle_leave(self, identity, request):
+ """Trying to handle the leaving operation if it is.
+
+ Args:
+ identity: The identity of that user.
+ request: The request from that user.
+ """
+ if JSON_TOKEN.BYE in request:
+ self._users_text_manager.reset_user(identity)
+ return {}
+
+ def _try_handle_sync(self, identity, request):
+ """Trying to handle the sync request if it is, otherwise return None.
+
+ Args:
+ 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]):
+ log.info('handle sync-request from %r\n' % identity)
+ self._check_init(identity, request)
+ self._check_authority(identity, request)
+ 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)
+
+ def _pack_sync_response(self, identity, user_info, text):
+ """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.
+
+ 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()
+ ]}
+
+ def _check_init(self, identity, request):
+ """Checks whether that user should be initialize or not.
+
+ If yes, it will reset that user and update the request.
+
+ Args:
+ identity: The identity of that user.
+ request: The request from that user.
+ """
+ if request[JSON_TOKEN.INIT]:
+ log.info('Init the user %r\n' % identity)
+ self._users_text_manager.reset_user(identity)
+ request[JSON_TOKEN.TEXT] = ''
+ for mark in request.get(JSON_TOKEN.CURSORS, []):
+ request[JSON_TOKEN.CURSORS][mark] = 0
+
+ def _check_authority(self, identity, request):
+ """Checks the authroity and updates the request.
+
+ If the user is not writeable, it will modify the request to let it looks
+ like that the user did nothing.
+
+ Args:
+ identity: The identity of that user.
+ request: The request from that user.
+ """
+ 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
diff --git a/server/src/shared_vim_server.py b/server/src/shared_vim_server.py
index a7f5201..9ec7fcd 100755
--- a/server/src/shared_vim_server.py
+++ b/server/src/shared_vim_server.py
@@ -57,14 +57,15 @@ class SharedVimServer(threading.Thread):
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(self._users_text_manager, self._tcp_server, self)
+ 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(['load %s' % self._args.user_list_filename])
+ self._cmd_ui.start()
self._cmd_ui.join()
self._tcp_server.join()
diff --git a/server/src/tcp_server.py b/server/src/tcp_server.py
index 28bf0fc..7060244 100644
--- a/server/src/tcp_server.py
+++ b/server/src/tcp_server.py
@@ -8,26 +8,11 @@ import time
from json_package import JSONPackage
from json_package import JSONPackageError
-from users_text_manager import AUTHORITY
-from users_text_manager import UserInfo
+from request_handler import RequestHandler
FREQUENCY = 8
-
-TIMEOUT = 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
- 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
+TIMEOUT = 1
class TCPServer(threading.Thread):
@@ -68,6 +53,7 @@ class TCPServer(threading.Thread):
"""Stops the thread."""
self._stop_flag = True
for thr in self._connection_handler_threads:
+ thr.stop()
thr.join()
def _build(self):
@@ -103,11 +89,53 @@ class TCPServer(threading.Thread):
self._connection_handler_threads += [thr]
+class _TCPConnectionHandler(threading.Thread):
+ """A thread to handle a connection.
+
+ Attributes:
+ _sock: The connection socket.
+ _users_text_manager: An instance of UsersTextManager.
+ _stop_flag: Stopping flag.
+ """
+ def __init__(self, conn, users_text_manager):
+ """Constructor.
+
+ Args:
+ conn: The connection.
+ users_text_manager: An instance of UsersTextManager.
+ """
+ super(_TCPConnectionHandler, self).__init__()
+ self._conn = TCPConnection(conn)
+ self._users_text_manager = users_text_manager
+ self._stop_flag = False
+ self._request_handler = RequestHandler(self._users_text_manager)
+
+ def run(self):
+ """Runs the thread."""
+ try:
+ while not self._stop_flag:
+ try:
+ request = JSONPackage(recv_func=self._conn.recv_all).content
+ response = self._request_handler.handle(request)
+ JSONPackage(response).send(self._conn.send_all)
+ except JSONPackageError as e:
+ log.error(str(e))
+ except socket.error as e:
+ log.error(str(e))
+ self._conn.close()
+
+ def stop(self):
+ """Stops the thread."""
+ self._stop_flag = True
+ self._conn.stop()
+
+
class TCPConnection(object):
"""My custom tcp connection.
Args:
_conn: The TCP-connection.
+ _stop_flag: Stopping flag.
"""
def __init__(self, conn):
"""Constructor.
@@ -117,16 +145,22 @@ class TCPConnection(object):
"""
self._conn = conn
self._conn.settimeout(TIMEOUT)
+ self._stop_flag = False
- def send(self, data):
+ def send_all(self, data):
"""Sends the data until timeout or the socket closed.
Args:
data: Data to be sent.
"""
- self._conn.sendall(data)
+ recvd_byte, total_byte = 0, len(data)
+ while recvd_byte < total_byte and not self._stop_flag:
+ try:
+ recvd_byte += self._conn.send(data[recvd_byte : ])
+ except socket.timeout:
+ continue
- def recv(self, nbyte):
+ def recv_all(self, nbyte):
"""Receives the data until timeout or the socket closed.
Args:
@@ -136,8 +170,11 @@ class TCPConnection(object):
Bytes of data.
"""
ret = b''
- while nbyte > 0:
- recv = self._conn.recv(nbyte)
+ while nbyte > 0 and not self._stop_flag:
+ try:
+ recv = self._conn.recv(nbyte)
+ except socket.timeout:
+ continue
if not recv:
raise socket.error('Connection die.')
ret += recv
@@ -148,110 +185,6 @@ class TCPConnection(object):
"""Closes the connection."""
self._conn.close()
-
-class _TCPConnectionHandler(threading.Thread):
- """A thread to handle a connection.
-
- Attributes:
- _sock: The connection socket.
- _users_text_manager: An instance of UsersTextManager.
- """
- def __init__(self, sock, users_text_manager):
- """Constructor.
-
- Args:
- sock: The connection socket.
- users_text_manager: An instance of UsersTextManager.
- """
- super(_TCPConnectionHandler, self).__init__()
- self._sock = TCPConnection(sock)
- self._users_text_manager = users_text_manager
-
- def run(self):
- """Runs the thread."""
- try:
- json_package = self._receive()
- json_info = self._sanitize(json_package.content)
- if json_info:
- self._handle(json_info)
- else:
- self._send({_JSON_TOKEN.ERROR : 'Invalid client.'})
- except JSONPackageError as e:
- log.error(str(e))
- except socket.error as e:
- log.error(str(e))
- self._sock.close()
-
- def _sanitize(self, request):
- """Sanitizes the request.
-
- Args:
- request: The request package.
-
- Return:
- Sanitized package.
- """
- identity = request[_JSON_TOKEN.IDENTITY]
- if identity not in self._users_text_manager.get_users_info():
- return None
- if request.get(_JSON_TOKEN.INIT) or request.get(_JSON_TOKEN.BYE, False):
- self._users_text_manager.reset_user(identity)
- request[_JSON_TOKEN.TEXT] = ''
- for mark in request.get(_JSON_TOKEN.CURSORS, []):
- request[_JSON_TOKEN.CURSORS][mark] = 0
- if _JSON_TOKEN.BYE in request:
- return None
- else:
- 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
- return request
-
- def _handle(self, request):
- """Handles the request.
-
- Args:
- request: The request package.
- """
- identity = request[_JSON_TOKEN.IDENTITY]
- text = request[_JSON_TOKEN.TEXT]
- user_info = UserInfo()
- user_info.mode = request[_JSON_TOKEN.MODE]
- user_info.cursors = request[_JSON_TOKEN.CURSORS]
- new_user_info, new_text = self._users_text_manager.update_user_text(
- identity, user_info, text)
- response = JSONPackage()
- response.content = {
- _JSON_TOKEN.TEXT : new_text,
- _JSON_TOKEN.CURSORS : new_user_info.cursors,
- _JSON_TOKEN.MODE : new_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()]
- }
- response.send_to(self._sock)
-
-
- def _receive(self):
- """Receive a request.
-
- Return:
- The request package.
- """
- request = JSONPackage()
- request.recv_from(self._sock)
- return request
-
- def _send(self, pkg):
- """Sends a response.
-
- Args:
- pkg: The package to be sent.
- """
- response = JSONPackage()
- response.content = pkg
- response.send_to(self._sock)
+ def stop(self):
+ """Stops."""
+ self._stop_flag = True
diff --git a/server/src/text_chain.py b/server/src/text_chain.py
index 59d65c2..d60ea02 100644
--- a/server/src/text_chain.py
+++ b/server/src/text_chain.py
@@ -47,13 +47,11 @@ class TextChain(object):
old_index = self._get_commit_index(orig_id)
commit = _TextCommit(self._commits[old_index][1].text, new_text)
cursors_info = [commit.get_cursor_info(cur) for cur in cursors]
- commit.apply_commits(
- [cmt[1] for cmt in self._commits[old_index + 1 : ]])
+ commit.apply_commits([cm[1] for cm in self._commits[old_index + 1 :]])
self._last_commit = commit.copy()
new_id = self._commits[-1][0] + 1
for info in cursors_info:
- info.apply_commits([cmt[1]
- for cmt in self._commits[old_index + 1 : ]])
+ 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.delete(orig_id)
@@ -217,89 +215,6 @@ class _TextCommit(object):
self._opers = _opers_apply_opers(self._opers, commit._opers)
self._rebase_text(commits[-1].text)
-# def squash_before(self, commit):
-# """Squash a commit in to myself which is commited just before me.
-#
-# Args:
-# commit: The commit to be squash.
-# """
-# chg_chars, beg_end_pos = commit._create_chg_info()
-# orig_len = len(commit.text) - commit.increased_length
-# offset = 0
-# # Adds another entry to beg_end_pos to prevent the key out of range
-# beg_end_pos_addend = beg_end_pos + [(orig_len, orig_len + 1)]
-# for oper in self._opers: # Applies my operations on the chg info.
-# beg = beg_end_pos_addend[oper.begin + offset][0]
-# end = beg_end_pos_addend[oper.end + offset][0]
-# length = len(oper.new_text)
-# chg_chars[oper.begin : oper.end] = list(oper.new_text)
-# beg_end_pos[oper.begin : oper.end] = [(beg, end)] * length
-# offset += oper.increased_length
-# self._recreate_opers_from_chg_info(orig_len, chg_chars, beg_end_pos)
-#
-# def _create_chg_info(self):
-# """Creates a list contains change informations.
-#
-# Return:
-# A 2-tuple for two list:
-# first list: List of changed char in the final text,
-# if that position does not change, it will be False.
-# second list: List of 2-tuple with:
-# first element: The begin position of the original text.
-# first element: The end position of the original text.
-# """
-# chg_chars, beg_end_pos = [], []
-# orig_end, orig_len = 0, len(self._text) - self.increased_length
-# for oper in self._opers + [_ChgTextOper(orig_len, orig_len, '')]:
-# chg_chars += [False] * (oper.begin - orig_end)
-# beg_end_pos += [(k, k + 1) for k in range(orig_end, oper.begin)]
-# chg_chars += list(oper.new_text)
-# beg_end_pos += [(oper.begin, oper.end)] * len(oper.new_text)
-# orig_end = oper.end
-# return chg_chars, beg_end_pos
-#
-# def _recreate_opers_from_chg_info(self, orig_len, chg_chars, beg_end_pos):
-# """Creates my operations by gived change information.
-#
-# Args:
-# chg_chars: ...
-# beg_end_pos: ...
-# """
-# self._opers = []
-# chg_chars = [False] + chg_chars + [False]
-# beg_end_pos = [(-1, 0)] + beg_end_pos + [(orig_len, orig_len + 1)]
-# index = 0
-# while index < len(chg_chars) - 1:
-# if chg_chars[index] is not False:
-# index_end = chg_chars.index(False, index) - 1
-# self._opers.append(_ChgTextOper(
-# beg_end_pos[index][0], beg_end_pos[index_end][1],
-# ''.join(chg_chars[index : index_end + 1])))
-# index = index_end
-# if beg_end_pos[index][1] < beg_end_pos[index + 1][0]:
-# self._opers.append(_ChgTextOper(
-# beg_end_pos[index][1], beg_end_pos[index + 1][0], ''))
-# index += 1
-# self._merge_connected_opers()
-#
-# def _merge_connected_opers(self):
-# """Merges the operations who are connected with each other.
-#
-# ex:
-# [ )
-# [ )
-# will become:
-# [ )
-# """
-# ind = 0
-# while ind < len(self._opers) - 1:
-# if self._opers[ind].end == self._opers[ind].begin:
-# self._opers[ind : ind + 1] = [_ChgTextOper(
-# self._opers[ind].begin, self._opers[ind + 1].end,
-# self._opers[ind].new_text + self._opers[ind + 1].new_text)]
-# else:
-# ind += 1
-#
def get_cursor_info(self, cursor_pos):
"""Gets the cursor information by gived cursor position.
diff --git a/server/src/users_text_manager.py b/server/src/users_text_manager.py
index e21b382..93d9d94 100644
--- a/server/src/users_text_manager.py
+++ b/server/src/users_text_manager.py
@@ -4,6 +4,7 @@ import threading
from text_chain import TextChain
+
UNKNOWN = -1
class AUTHORITY: # pylint:disable=W0232
@@ -13,16 +14,17 @@ class AUTHORITY: # pylint:disable=W0232
class UserInfo(object):
- """A pure structor for represent a user's information.
+ """A structure for storing a user's information.
Attributes:
authority: Authority of this user.
nick_name: Nick name.
mode: Vim mode.
- cursors: Cursor positions of each mark.
- last_commit_id: Text commit id.
+ cursors: A dict stores cursor positions of each mark.
+ last_commit_id: Last commit's id.
"""
- def __init__(self, authority=UNKNOWN, nick_name=''):
+ def __init__(self, authority=UNKNOWN, nick_name='', mode=UNKNOWN,
+ cursors=None):
"""Constructor.
Args:
@@ -31,18 +33,14 @@ class UserInfo(object):
"""
self.authority = authority
self.nick_name = nick_name
- self.mode = UNKNOWN
- self.cursors = {}
+ self.mode = mode
+ self.cursors = {} if cursors is None else cursors
self.last_commit_id = UNKNOWN
def __str__(self):
- return '%s(%r) %r %r' % (
- self.nick_name, self.authority, self.mode, self.last_commit_id)
-
+ return 'authorith = %r, nickname = %r, mode = %r, last_commit = %r' % (
+ self.authority, self.nick_name, self.mode, self.last_commit_id)
-class UsersTextManagerError(Exception):
- """Error raised by UsersTextManager."""
- pass
class UsersTextManager(object):
"""Handles query/operations about users and texts.
@@ -77,12 +75,8 @@ class UsersTextManager(object):
authority: Authority of this user.
"""
with self._rlock:
- if identity in self._users:
- raise UsersTextManagerError('User %r already exists.' %
- identity)
- new_user = UserInfo(authority, nick_name)
- new_user.last_commit_id = self._text_chain.new()
- self._users[identity] = new_user
+ self._users[identity] = UserInfo(authority, nick_name)
+ self._users[identity].last_commit_id = self._text_chain.new()
def delete_user(self, identity):
"""Deletes a user.
@@ -91,8 +85,6 @@ class UsersTextManager(object):
identity: Identity of this user.
"""
with self._rlock:
- if identity not in self._users:
- raise UsersTextManagerError('User %r not exists.' % identity)
self._text_chain.delete(self._users[identity].last_commit_id)
del self._users[identity]
@@ -136,23 +128,20 @@ class UsersTextManager(object):
A 2-tuple for a instance of UserInfo and a string.
"""
with self._rlock:
- curlist = list(new_user_info.cursors.items())
- new_commit_id, new_text, new_cursors = self._text_chain.commit(
- self._users[identity].last_commit_id, new_text,
- [pos for mark, pos in curlist])
- curlist = [(curlist[i][0], new_cursors[i])
- for i in range(len(curlist))]
+ curmarks = new_user_info.cursors.keys()
+ curs = [new_user_info.cursors[mark] for mark in curmarks]
+ new_commit_id, new_text, new_curs = self._text_chain.commit(
+ 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 = dict(curlist)
+ self._users[identity].cursors = _lists_to_dict(curmarks, new_curs)
for iden, user in self._users.items():
if iden == identity:
continue
- curs_info = list(user.cursors.items())
- new_curs = self._text_chain.update_cursors(
- [pos for mark, pos in curs_info])
- user.cursors = dict([(curs_info[i][0], new_curs[i])
- for i in range(len(new_curs))])
+ 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)
return (self._users[identity], new_text)
def get_user_text(self, identity):
@@ -165,5 +154,19 @@ class UsersTextManager(object):
The text.
"""
with self._rlock:
- return self._text_chain.get_text(self._users[identity].
- last_commit_id)
+ 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 085908f..27e825c 100644
--- a/vim/plugin/shared_vim.vim
+++ b/vim/plugin/shared_vim.vim
@@ -2,19 +2,11 @@
command! -nargs=0 SharedVimTryUsePython3 call _SharedVimTryUsePython3(1)
command! -nargs=0 SharedVimTryUsePython2 call _SharedVimTryUsePython2(1)
command! -nargs=+ SharedVimConnect call _SharedVimCallPythonFunc('connect', [<f-args>])
-command! -nargs=0 SharedVimDisconnect call _SharedVimCallPythonFunc('disconnect', [<f-args>])
-command! -nargs=0 SharedVimSync call _SharedVimCallPythonFunc('sync', [<f-args>])
-command! -nargs=0 SharedVimShowInfo call _SharedVimCallPythonFunc('show_info', [<f-args>])
-
-
-"""""""""""""""""""""""""" Global variable for settings """"""""""""""""""""""""
-if !exists('g:shared_vim_timeout')
- let g:shared_vim_timeout = 5
-endif
-
-if !exists('g:shared_vim_num_groups')
- let g:shared_vim_num_groups = 5
-endif
+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', <f-args>])
+command! -nargs=1 SharedVimSetNumOfGroups call _SharedVimCallPythonFunc('set_bvar', ['NUM_GROUPS', <f-args>])
""""""""""""""""""""""""""""""""""""" Setup """"""""""""""""""""""""""""""""""""
@@ -26,13 +18,16 @@ for i in range(0, 100)
endfor
+let g:shared_vim_auto_sync_level = 3
+
+
" Auto commands
-autocmd! CursorMoved * SharedVimSync
-autocmd! CursorMovedI * SharedVimSync
-autocmd! CursorHold * SharedVimSync
-autocmd! CursorHoldI * SharedVimSync
-autocmd! InsertEnter * SharedVimSync
-autocmd! InsertLeave * SharedVimSync
+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 """"""""""""""""""""""""""""""""""
@@ -73,7 +68,16 @@ function! _SharedVimCallPythonFunc(func_name, args)
exe 'SharedVimPython ' . a:func_name . '(' . args_str . ')'
endif
endif
-endfunctio
+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
+ endif
+ if a:level <= b:shared_vim_auto_sync_level
+ SharedVimSync
+ endif
+endfunction
function! _SharedVimSetup()
@@ -86,16 +90,10 @@ import re
import socket
import sys
import vim
-import zlib
if sys.version_info[0] == 3:
unicode = str
-class GOALS: # pylint:disable=W0232
- SYNC = 'sync' # Sync.
- DISCONNECT = 'disconnect' # Disconnect.
- SHOW_INFO = 'show_info' # Shows the users.
-
class CURSOR_MARK: # pylint:disable=W0232
"""Enumeration type of cursor marks."""
CURRENT = '.'
@@ -118,15 +116,14 @@ class MODE: # pylint:disable=W0232
class VARNAMES: # pylint: disable=W0232
"""Enumeration types of variable name in vim."""
- PREFIX = 'shared_vim_' # Shared prefix of the variable names.
- IDENTITY = PREFIX + 'identity' # Identity of the user.
- INIT = PREFIX + 'init' # Initial or not.
- NUM_GROUPS = PREFIX + 'num_groups' # Number of groups.
- SERVER_NAME = PREFIX + 'server_name' # Server name.
- SERVER_PORT = PREFIX + 'port' # Server port.
- TIMEOUT = PREFIX + 'timeout' # Timeout for TCPConnection.
- USERS = PREFIX + 'users' # List of users.
- GOAL = PREFIX + 'goal' # Goal of this python code.
+ GROUP_NAME_PREFIX = 'gnp' # Group name's prefix.
+ IDENTITY = 'identity' # Identity of the user.
+ INIT = 'init' # Initial or not.
+ 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
@@ -137,9 +134,11 @@ INSERT_CURSOR_GROUP = lambda x: 'SharedVimIns%d' % x
# Name of the visual group.
VISUAL_GROUP = lambda x: 'SharedVimVbk%d' % x
-DEFAULT_TIMEOUT = 5
+
+DEFAULT_TIMEOUT = 1
DEFAULT_NUM_GROUPS = 5
+
class JSON_TOKEN: # pylint:disable=W0232
"""Enumeration the Ttken strings for json object."""
BYE = 'bye' # Disconnects with the server.
@@ -153,255 +152,89 @@ class JSON_TOKEN: # pylint:disable=W0232
TEXT = 'text' # text content in the buffer
-if hasattr(vim, 'options'):
- vim_options = vim.options
-else:
- class SimpleVimOptions(object):
- """An alternative implement of vim.options when it is not exists."""
- def __getitem__(self, option_name):
- """Gets the specified vim option.
-
- Args:
- option_name: Name of the option to get.
-
- Return:
- The value in string.
- """
- return vim.eval('&' + option_name)
-
- vim_options = SimpleVimOptions()
-
-
-if not hasattr(vim, 'vars') or not hasattr(vim.current.buffer, 'vars') or True:
- class SimpleVimVars(object):
- """An alternative implement of vim.vars/vim.current.buffer.vars.
-
- Attributes:
- _prefix: The prefix of the variable name. For example, "b:" for
- buffer's variable, "g:" for global variable.
- """
- _STRING_NOTATION = 's'
- _NUMBER_NOTATION = 'n'
-
- def __init__(self, prefix):
- """Constructor.
-
- Args:
- prefix: The prefix of the variable name.
- """
- self._prefix = prefix
-
- def __getitem__(self, variable_name):
- """Gets the specified vim variable.
-
- Args:
- variable_name: Name of the variable to get.
-
- Return:
- The value.
- """
- value = vim.eval(self._prefix + variable_name)
- if value.startswith(self._STRING_NOTATION):
- return value[1 : ]
- else:
- return int(value[1 : ])
-
- def __setitem__(self, variable_name, value):
- """Sets the specifiec vim variable.
-
- Args:
- variable_name: Name of the variable to set.
- value: The new value.
- """
- if isinstance(value, bytes):
- value = self._STRING_NOTATION + value
- else:
- value = '%s%d' % (self._NUMBER_NOTATION, value)
- vim.command('let %s%s = "%s"' %
- (self._prefix, variable_name, value))
-
- def __delitem__(self, variable_name):
- """Deletes the specifiec vim variable.
-
- Args:
- variable_name: Name of the variable to delete.
- """
- vim.command('unlet %s%s' % (self._prefix, variable_name))
-
- def __contains__(self, variable_name):
- """Checks whether the variable is exist or not.
-
- Args:
- variable_name: Name of the variable to check.
+############### Handler for Variable stores only in python #####################
+class ScopeVars(object):
+ """A scoped variables handler.
- Return:
- True if the variable exists; otherwise, False.
- """
- return vim.eval('exists("%s%s")' %
- (self._prefix, variable_name)) == '1'
-
-
-class JSONPackage(object):
- """Send/receive json by tcp connection.
-
- Attribute:
- content: Content of the package body.
+ Attributes:
+ _curr_scope: Current scope number.
+ _vars: A dict contains all scope's variables and the values.
"""
- ENCODING = 'utf-8'
- COMPRESS_LEVEL = 2
- HEADER_LENGTH = 10
def __init__(self):
"""Constructor."""
- self.content = None
-
- def send_to(self, fd):
- """Sends a string to the tcp-connection.
-
- Args:
- fd: Socket fd.
- """
- string = json.dumps(self.content)
- body = JSONPackage._create_body_from_string(string)
- header = JSONPackage._create_header_from_body(body)
- fd.send(header + body)
-
- def recv_from(self, fd):
- """Receives a string from the tcp-connection.
-
- Args:
- fd: Socket fd.
- """
- header = JSONPackage._recv_header_string(fd)
- body = JSONPackage._recv_body_string(fd, header)
- self.content = json.loads(body)
-
- @staticmethod
- def _create_body_from_string(string):
- """Creates package body from data string.
-
- Args:
- string: Data string.
-
- Returns:
- Package body.
- """
- byte_string = string.encode(JSONPackage.ENCODING)
- return zlib.compress(byte_string, JSONPackage.COMPRESS_LEVEL)
-
- @staticmethod
- def _create_header_from_body(body):
- """Creates package header from package body.
-
- Args:
- body: Package body.
-
- Returns:
- Package header.
- """
- header_string = ('%%0%dd' % JSONPackage.HEADER_LENGTH) % len(body)
- return header_string.encode(JSONPackage.ENCODING)
-
- @staticmethod
- def _recv_header_string(conn):
- """Receives package header from specified tcp connection.
-
- Args:
- conn: The specified tcp connection.
-
- Returns:
- Package header.
- """
- byte = conn.recv(JSONPackage.HEADER_LENGTH)
- return byte.decode(JSONPackage.ENCODING)
-
- @staticmethod
- def _recv_body_string(conn, header):
- """Receives package body from specified tcp connection and header.
-
- Args:
- conn: The specified tcp connection.
- header: The package header.
-
- Returns:
- Package body.
- """
- body_length = int(header)
- body = conn.recv(body_length)
- body_byte = zlib.decompress(body)
- return body_byte.decode(JSONPackage.ENCODING)
-
-
-class VimVarInfo(object):
- """Gets/sets variables in vim.
+ self._curr_scope = None
+ self._vars = {}
- Attributes:
- _getter: A function which will return the object for this class to
- access this variablies.
- """
- def __init__(self, var=None, getter=None):
- """Constructor.
+ @property
+ def curr_scope(self):
+ """Gets the current scope number."""
+ return self._curr_scope
- Args:
- var: The object for this class to access the vars.
- getter: If it is not None, it must be a function which will return
- the object for this class to access the vars.
- """
- if getter is None:
- self._getter = lambda : var
- else:
- self._getter = getter
+ @curr_scope.setter
+ def curr_scope(self, value):
+ """Sets the current scope number."""
+ self._curr_scope = value
+ self._vars.setdefault(self._curr_scope, {})
- def __getitem__(self, variable_name):
- """Gets the specified vim variable.
+ 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:
- None if the value is not exists, otherwise the value.
+ The value.
"""
- return self.get(variable_name)
+ return self._vars[self._curr_scope].get(variable_name, default)
- def get(self, variable_name, default_value=None):
- """Gets the specified vim variable.
+ def __getitem__(self, variable_name):
+ """Gets the specified variable.
Args:
variable_name: Name of the variable to get.
- default_value: The default to return if the variable is not exist.
Return:
- default_value if the value is not exists, otherwise the value.
+ The value.
"""
- if variable_name not in self._getter():
- return default_value
- return VimInfo.transform_to_py(self._getter()[variable_name])
+ return self._vars[self._curr_scope][variable_name]
def __setitem__(self, variable_name, value):
- """Sets the specifiec vim variable.
+ """Sets the specifiec variable.
Args:
variable_name: Name of the variable to set.
value: The new value.
"""
- self._getter()[variable_name] = VimInfo.transform_to_vim(value)
+ self._vars[self._curr_scope][variable_name] = value
def __delitem__(self, variable_name):
- """Deletes the specifiec vim variable.
+ """Deletes the specifiec variable.
Args:
variable_name: Name of the variable to delete.
"""
- del self._getter()[variable_name]
+ 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._getter()
+ 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 __init__(self, *_):
@@ -596,32 +429,22 @@ class VimHighlightInfo(object):
@staticmethod
def num_of_groups():
"""Gets the number of groups."""
- return VimInfo.bvar.get(VARNAMES.NUM_GROUPS, DEFAULT_TIMEOUT)
+ return py_bvars.get(VARNAMES.NUM_GROUPS, DEFAULT_NUM_GROUPS)
class VimInfoMeta(type):
"""An interface for accessing the vim's vars, buffer, cursors, etc.
Static attributes:
- gvar: An instance of VimVarInfo, for accessing the global variables in
- vim.
- bvar: An instance of VimVarInfo, for accessing the buffer's variables in
- vim.
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.
"""
- if 'vars' in dir(vim):
- gvar = VimVarInfo(vim.vars)
- bvar = VimVarInfo(getter=lambda : vim.current.buffer.vars)
- else:
- gvar = VimVarInfo(SimpleVimVars('g:'))
- bvar = VimVarInfo(SimpleVimVars('b:'))
cursors = VimCursorsInfo()
highlight = VimHighlightInfo()
- ENCODING = vim_options['encoding']
+ ENCODING = vim.eval('&encoding')
def __init__(self, *args):
"""Constructor."""
@@ -630,14 +453,13 @@ class VimInfoMeta(type):
@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[:]]
+ 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."""
- vim.current.buffer[0 : len(vim.current.buffer)] = \
- [VimInfo.transform_to_vim(line) for line in lines]
+ tr = [VimInfo.transform_to_vim(line) for line in lines]
+ vim.current.buffer[0 : len(vim.current.buffer)] = tr
@property
def text(self):
@@ -696,17 +518,17 @@ class VimInfoMeta(type):
priority: Priority for the vim function matchadd().
positions: List of row-column position.
"""
- last_id = VimInfo.bvar[VARNAMES.PREFIX + group_name]
+ last_id = py_wvars.get(VARNAMES.GROUP_NAME_PREFIX + group_name, None)
if last_id is not None and last_id > 0:
- ret = vim.eval('matchdelete(%d)' % last_id)
- del VimInfo.bvar[VARNAMES.PREFIX + group_name]
+ 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])
mid = int(vim.eval("matchadd('%s', '%s', %d)" %
(group_name, patterns, priority)))
if mid != -1:
- VimInfo.bvar[VARNAMES.PREFIX + group_name] = mid
+ py_wvars[VARNAMES.GROUP_NAME_PREFIX + group_name] = mid
@staticmethod
def transform_to_py(data):
@@ -740,13 +562,13 @@ class VimInfoMeta(type):
# 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):
+ 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)):
+class VimInfo(with_metaclass(VimInfoMeta, object)): # pylint: disable=W0232
"""An interface for accessing the vim's vars, buffer, cursors, etc."""
@staticmethod
def init():
@@ -754,6 +576,80 @@ class VimInfo(with_metaclass(VimInfoMeta, object)):
VimInfo.cursors.update_text_lines()
+########################### 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.
@@ -767,10 +663,9 @@ class TCPConnection(object):
conn: TCP-connection.
"""
self._conn = conn
- self._conn.settimeout(
- VimInfo.bvar.get(VARNAMES.TIMEOUT, DEFAULT_NUM_GROUPS))
+ self._conn.settimeout(py_bvars.get(VARNAMES.TIMEOUT, DEFAULT_TIMEOUT))
- def send(self, data):
+ def send_all(self, data):
"""Sends the data until timeout or the socket closed.
Args:
@@ -778,7 +673,7 @@ class TCPConnection(object):
"""
self._conn.sendall(data)
- def recv(self, nbyte):
+ def recv_all(self, nbyte):
"""Receives the data until timeout or the socket closed.
Args:
@@ -809,19 +704,30 @@ class TCPClient(object):
"""TCP client.
Attributes:
- _sock: Connection.
+ _conn: Connection.
+
+ Static attributes:
+ _conns: A dict stores connections.
"""
- def __init__(self):
- """Constructor, automatically connects to the server."""
- try:
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.connect((VimInfo.bvar[VARNAMES.SERVER_NAME],
- VimInfo.bvar[VARNAMES.SERVER_PORT]))
- self._sock = TCPConnection(sock)
- except TypeError as e:
- raise TCPClientError('Cannot connect to server: %r' % e)
- except socket.error as e:
- raise TCPClientError('Cannot connect to server: %r' % e)
+ _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.
@@ -832,33 +738,42 @@ class TCPClient(object):
Return:
The response.
"""
- pkg = JSONPackage()
- pkg.content = req
- pkg.send_to(self._sock)
- pkg.recv_from(self._sock)
- return pkg.content
+ try:
+ JSONPackage(req).send(self._conn.send_all)
+ return JSONPackage(recv_func=self._conn.recv_all).content
+ except socket.error as e:
+ raise TCPClientError(e)
+ except JSONPackageError as e:
+ raise TCPClientError(e)
def close(self):
"""Closes the socket."""
- self._sock.close()
+ self._conn.close()
+ for key, conn in TCPClient._conns.items():
+ if conn is self._conn:
+ del TCPClient._conns[key]
+ break
+################################ Some operations ###############################
+def set_scopes():
+ py_bvars.curr_scope = vim.current.buffer.number
+ py_wvars.curr_scope = vim.current.window.number
+
def get_my_info(init):
"""Gets my information for server.
Return:
The information for server.
"""
- return {
- JSON_TOKEN.IDENTITY : VimInfo.bvar[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.TEXT : VimInfo.text,
- }
+ 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.TEXT : VimInfo.text}
def set_my_info(json_info):
@@ -926,6 +841,7 @@ def set_other_visual(name, mode, beg, end):
VimInfo.highlight[name].add_visual((row, col))
+############################## Supported operations ############################
def connect(server_name, server_port, identity):
"""Connects to the server.
@@ -934,9 +850,10 @@ def connect(server_name, server_port, identity):
server_port: Server port.
identity: Identity string of this user.
"""
- VimInfo.bvar[VARNAMES.SERVER_NAME] = server_name
- VimInfo.bvar[VARNAMES.SERVER_PORT] = int(server_port)
- VimInfo.bvar[VARNAMES.IDENTITY] = identity
+ set_scopes()
+ py_bvars[VARNAMES.SERVER_NAME] = server_name
+ py_bvars[VARNAMES.SERVER_PORT] = int(server_port)
+ py_bvars[VARNAMES.IDENTITY] = identity
sync(init=True)
@@ -947,32 +864,48 @@ def sync(init=False):
init: Flag for whether it should tell the server to reset this user or
not.
"""
- if VARNAMES.SERVER_NAME in VimInfo.bvar:
- conn = TCPClient()
- response = conn.request(get_my_info(init))
- conn.close()
+ set_scopes()
+ 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:
- raise Exception(response[JSON_TOKEN.ERROR])
+ print(response[JSON_TOKEN.ERROR])
+ return
set_my_info(response)
set_others_info(response)
- VimInfo.bvar[VARNAMES.USERS] = ', '.join(
+ py_bvars[VARNAMES.USERS] = ', '.join(
[user[JSON_TOKEN.NICKNAME] for user in response[JSON_TOKEN.OTHERS]])
def disconnect():
"""Disconnects with the server."""
- conn = TCPClient()
- conn.request({JSON_TOKEN.BYE : True,
- JSON_TOKEN.IDENTITY : VimInfo.bvar[VARNAMES.IDENTITY]})
- conn.close()
- del VimInfo.bvar[VARNAMES.SERVER_NAME]
- del VimInfo.bvar[VARNAMES.SERVER_PORT]
- del VimInfo.bvar[VARNAMES.IDENTITY]
- print('bye')
+ set_scopes()
+ 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."""
+ set_scopes()
print('Highlight information:')
print('Groups of normal cursor position:')
for index in range(VimInfo.highlight.num_of_groups()):
@@ -983,7 +916,19 @@ def show_info():
print('Groups of selection area:')
for index in range(VimInfo.highlight.num_of_groups()):
vim.command('hi %s' % VISUAL_GROUP(index))
- print('Users: %r' % VimInfo.bvar[VARNAMES.USERS])
+ 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.
+ """
+ set_scopes()
+ py_bvars[getattr(VARNAMES, variable_name)] = value
+ print('bvars[%s] = %s' % (getattr(VARNAMES, variable_name), value))
################################## Initialize ##################################