mirror of
https://github.com/hexedtech/codemp-sublime.git
synced 2024-11-22 14:54:48 +01:00
completed the text change compression to send a single operation to delta. Minor refactor, added stubs for further development
Former-commit-id: 83d6457ac959585d459d5b9af3e948e623bb6b8d
This commit is contained in:
parent
6fe5effb68
commit
fd528237b3
4 changed files with 264 additions and 188 deletions
|
@ -1 +1 @@
|
||||||
8114809f135d7ea9f88d0a45a9de8fc93fdbd9ff
|
a4493ce329ed791cd4bd17b102efdde3ca4acd8f
|
403
plugin.py
403
plugin.py
|
@ -14,6 +14,21 @@ _cursor_controller = None
|
||||||
_buffer_controller = None
|
_buffer_controller = None
|
||||||
_setting_key = "codemp_buffer"
|
_setting_key = "codemp_buffer"
|
||||||
|
|
||||||
|
_regions_colors = [
|
||||||
|
"region.redish",
|
||||||
|
"region.orangeish",
|
||||||
|
"region.yellowish",
|
||||||
|
"region.greenish",
|
||||||
|
"region.cyanish",
|
||||||
|
"region.bluish",
|
||||||
|
"region.purplish",
|
||||||
|
"region.pinkish"
|
||||||
|
]
|
||||||
|
|
||||||
|
def status_log(msg):
|
||||||
|
sublime.status_message("[codemp] {}".format(msg))
|
||||||
|
print("[codemp] {}".format(msg))
|
||||||
|
|
||||||
def store_task(name = None):
|
def store_task(name = None):
|
||||||
def store_named_task(task):
|
def store_named_task(task):
|
||||||
global _tasks
|
global _tasks
|
||||||
|
@ -22,154 +37,6 @@ def store_task(name = None):
|
||||||
|
|
||||||
return store_named_task
|
return store_named_task
|
||||||
|
|
||||||
def plugin_loaded():
|
|
||||||
global _client
|
|
||||||
_client = CodempClient()
|
|
||||||
sublime_asyncio.acquire() # instantiate and start a global event loop.
|
|
||||||
|
|
||||||
class CodempClientViewEventListener(sublime_plugin.ViewEventListener):
|
|
||||||
@classmethod
|
|
||||||
def is_applicable(cls, settings):
|
|
||||||
return "codemp_buffer" in settings
|
|
||||||
|
|
||||||
def on_selection_modified_async(self):
|
|
||||||
global _cursor_controller
|
|
||||||
if _cursor_controller:
|
|
||||||
sublime_asyncio.dispatch(send_selection(self.view))
|
|
||||||
|
|
||||||
def on_close(self):
|
|
||||||
self.view.settings()["codemp_buffer"] = False
|
|
||||||
|
|
||||||
class CodempClientTextChangeListener(sublime_plugin.TextChangeListener):
|
|
||||||
@classmethod
|
|
||||||
def is_applicable(cls, buffer):
|
|
||||||
for view in buffer.views():
|
|
||||||
if "codemp_buffer" in view.settings():
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def on_text_changed(self, changes):
|
|
||||||
global _buffer_controller
|
|
||||||
if _buffer_controller:
|
|
||||||
for change in changes:
|
|
||||||
sublime_asyncio.dispatch(apply_changes(change))
|
|
||||||
|
|
||||||
async def apply_changes(change):
|
|
||||||
global _buffer_controller
|
|
||||||
|
|
||||||
text = change.str
|
|
||||||
skip = change.a.pt
|
|
||||||
if change.len_utf8 == 0: # we are inserting new text.
|
|
||||||
tail = len(_buffer_controller.get_content()) - skip
|
|
||||||
else: # we are changing an existing region of text of length len_utf8
|
|
||||||
tail = len(_buffer_controller.get_content()) - skip - change.len_utf8
|
|
||||||
|
|
||||||
tail_skip = len(_buffer_controller.get_content()) - tail
|
|
||||||
print("[buff change]", skip, text, tail_skip)
|
|
||||||
await _buffer_controller.apply(skip, text, tail)
|
|
||||||
|
|
||||||
async def make_connection(server_host):
|
|
||||||
global _client
|
|
||||||
|
|
||||||
if _client.ready:
|
|
||||||
sublime.message_dialog("A connection already exists.")
|
|
||||||
return
|
|
||||||
|
|
||||||
sublime.status_message("[codemp] Connecting to {}".format(server_host))
|
|
||||||
print("[codemp] Connecting to {}".format(server_host))
|
|
||||||
await _client.connect(server_host)
|
|
||||||
|
|
||||||
id = await _client.get_id()
|
|
||||||
sublime.status_message("[codemp] Connected with client ID: {}".format(id))
|
|
||||||
print("[codemp] Connected with client ID: ", id)
|
|
||||||
|
|
||||||
async def move_cursor(usr, caller, path, start, end):
|
|
||||||
print(usr, caller, start, end)
|
|
||||||
|
|
||||||
async def sync_buffer(caller, start, end, txt):
|
|
||||||
print("[buffer]", caller, start, end, txt)
|
|
||||||
|
|
||||||
async def share_buffer(buffer):
|
|
||||||
global _client
|
|
||||||
global _cursor_controller
|
|
||||||
global _buffer_controller
|
|
||||||
|
|
||||||
if not _client.ready:
|
|
||||||
sublime.error_message("No connected client.")
|
|
||||||
return
|
|
||||||
|
|
||||||
sublime.status_message("[codemp] Sharing buffer {}".format(buffer))
|
|
||||||
print("[codemp] Sharing buffer {}".format(buffer))
|
|
||||||
|
|
||||||
view = get_matching_view(buffer)
|
|
||||||
contents = get_contents(view)
|
|
||||||
created = await _client.create(view.file_name(), contents)
|
|
||||||
if not created:
|
|
||||||
sublime.error_message("Could not share buffer.")
|
|
||||||
return
|
|
||||||
|
|
||||||
_buffer_controller = await _client.attach(buffer)
|
|
||||||
_buffer_controller.callback(sync_buffer, _client.id)
|
|
||||||
|
|
||||||
_cursor_controller = await _client.listen()
|
|
||||||
_cursor_controller.callback(move_cursor, _client.id)
|
|
||||||
|
|
||||||
if not _cursor_controller:
|
|
||||||
sublime.error_message("Could not subsribe a listener.")
|
|
||||||
return
|
|
||||||
if not _buffer_controller:
|
|
||||||
sublime.error_message("Could not attach to the buffer.")
|
|
||||||
return
|
|
||||||
|
|
||||||
sublime.status_message("[codemp] Listening")
|
|
||||||
print("[codemp] Listening")
|
|
||||||
|
|
||||||
view.settings()["codemp_buffer"] = True
|
|
||||||
|
|
||||||
async def join_buffer(window, buffer):
|
|
||||||
global _client
|
|
||||||
global _cursor_controller
|
|
||||||
global _buffer_controller
|
|
||||||
|
|
||||||
if not _client.ready:
|
|
||||||
sublime.error_message("No connected client.")
|
|
||||||
return
|
|
||||||
|
|
||||||
view = get_matching_view(buffer)
|
|
||||||
|
|
||||||
sublime.status_message("[codemp] Joining buffer {}".format(buffer))
|
|
||||||
print("[codemp] Joining buffer {}".format(buffer))
|
|
||||||
|
|
||||||
_buffer_controller = await _client.attach(buffer)
|
|
||||||
content = _buffer_controller.get_content()
|
|
||||||
view.run_command("codemp_replace_view", {"content": content})
|
|
||||||
|
|
||||||
_cursor_controller = await _client.listen()
|
|
||||||
_cursor_controller.callback(move_cursor)
|
|
||||||
|
|
||||||
|
|
||||||
if not _cursor_controller:
|
|
||||||
sublime.error_message("Could not subsribe a listener.")
|
|
||||||
return
|
|
||||||
if not _buffer_controller:
|
|
||||||
sublime.error_message("Could not attach to the buffer.")
|
|
||||||
return
|
|
||||||
|
|
||||||
sublime.status_message("[codemp] Listening")
|
|
||||||
print("[codemp] Listening")
|
|
||||||
|
|
||||||
view.settings()["codemp_buffer"] = True
|
|
||||||
|
|
||||||
async def send_selection(view):
|
|
||||||
global _cursor_controller
|
|
||||||
|
|
||||||
path = view.file_name()
|
|
||||||
region = view.sel()[0] # TODO: only the last placed cursor/selection.
|
|
||||||
start = view.rowcol(region.begin()) #only counts UTF8 chars
|
|
||||||
end = view.rowcol(region.end())
|
|
||||||
|
|
||||||
await _cursor_controller.send(path, start, end)
|
|
||||||
|
|
||||||
def get_contents(view):
|
def get_contents(view):
|
||||||
r = sublime.Region(0, view.size())
|
r = sublime.Region(0, view.size())
|
||||||
return view.substr(r)
|
return view.substr(r)
|
||||||
|
@ -180,25 +47,225 @@ def get_matching_view(path):
|
||||||
if view.file_name() == path:
|
if view.file_name() == path:
|
||||||
return view
|
return view
|
||||||
|
|
||||||
|
def rowcol_to_region(view, start, end):
|
||||||
|
a = view.text_point(start[0], start[1])
|
||||||
|
b = view.text_point(end[0], end[1])
|
||||||
|
return sublime.Region(a, b)
|
||||||
|
|
||||||
|
def plugin_loaded():
|
||||||
|
global _client
|
||||||
|
_client = CodempClient() # create an empty instance of the codemp client.
|
||||||
|
sublime_asyncio.acquire() # instantiate and start a global asyncio event loop.
|
||||||
|
|
||||||
|
def plugin_unloaded():
|
||||||
|
for window in sublime.windows():
|
||||||
|
for view in window.views():
|
||||||
|
if "codemp_buffer" in view.settings():
|
||||||
|
del view.settings()["codemp_buffer"]
|
||||||
|
# disconnect all buffers
|
||||||
|
# stop all callbacks
|
||||||
|
# disconnect the client.
|
||||||
|
print("unloading")
|
||||||
|
|
||||||
|
async def connect_command(server_host, session="default"):
|
||||||
|
global _client
|
||||||
|
status_log("Connecting to {}".format(server_host))
|
||||||
|
await _client.connect(server_host)
|
||||||
|
await join_workspace(session)
|
||||||
|
|
||||||
|
async def join_workspace(session):
|
||||||
|
global _client
|
||||||
|
global _cursor_controller
|
||||||
|
|
||||||
|
status_log("Joining workspace: {}".format(session))
|
||||||
|
_cursor_controller = await _client.join(session)
|
||||||
|
_cursor_controller.callback(move_cursor)
|
||||||
|
|
||||||
|
async def share_buffer_command(buffer):
|
||||||
|
global _client
|
||||||
|
global _cursor_controller
|
||||||
|
global _buffer_controller
|
||||||
|
|
||||||
|
status_log("Sharing buffer {}".format(buffer))
|
||||||
|
|
||||||
|
view = get_matching_view(buffer)
|
||||||
|
contents = get_contents(view)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await _client.create(buffer, contents)
|
||||||
|
|
||||||
|
_buffer_controller = await _client.attach(buffer)
|
||||||
|
_buffer_controller.callback(apply_buffer_change)
|
||||||
|
except Exception as e:
|
||||||
|
sublime.error_message("Could not share buffer: {}".format(e))
|
||||||
|
return
|
||||||
|
|
||||||
|
status_log("Listening")
|
||||||
|
view.set_status("z_codemp_buffer", "[Codemp]")
|
||||||
|
view.settings()["codemp_buffer"] = True
|
||||||
|
|
||||||
|
def move_cursor(cursor_event):
|
||||||
|
global _regions_colors
|
||||||
|
|
||||||
|
# TODO: make the matching user/color more solid. now all users have one color cursor.
|
||||||
|
# Maybe make all cursors the same color and only use annotations as a discriminant.
|
||||||
|
view = get_matching_view(cursor_event.buffer)
|
||||||
|
if "codemp_buffer" in view.settings():
|
||||||
|
reg = rowcol_to_region(view, cursor_event.start, cursor_event.end)
|
||||||
|
reg_flags = sublime.RegionFlags.DRAW_EMPTY | sublime.RegionFlags.DRAW_NO_FILL
|
||||||
|
|
||||||
|
view.add_regions("codemp_cursors", [reg], flags = reg_flags, scope=_regions_colors[2], annotations = [cursor_event.user])
|
||||||
|
|
||||||
|
def send_cursor(view):
|
||||||
|
global _cursor_controller
|
||||||
|
|
||||||
|
path = view.file_name()
|
||||||
|
region = view.sel()[0] # TODO: only the last placed cursor/selection.
|
||||||
|
start = view.rowcol(region.begin()) #only counts UTF8 chars
|
||||||
|
end = view.rowcol(region.end())
|
||||||
|
|
||||||
|
_cursor_controller.send(path, start, end)
|
||||||
|
|
||||||
|
def send_buffer_change(buffer, changes):
|
||||||
|
global _buffer_controller
|
||||||
|
|
||||||
|
view = buffer.primary_view()
|
||||||
|
start, txt, end = compress_changes(view, changes)
|
||||||
|
|
||||||
|
contlen = len(_buffer_controller.get_content())
|
||||||
|
_buffer_controller.delta(start, txt, min(end, contlen))
|
||||||
|
time.sleep(0.1)
|
||||||
|
print("server buffer: -------")
|
||||||
|
print(_buffer_controller.get_content())
|
||||||
|
|
||||||
|
def compress_changes(view, changes):
|
||||||
|
## TODO: doesn't work correctly.
|
||||||
|
|
||||||
|
# Sublime text on_text_changed events, gives a list of changes.
|
||||||
|
# in case of simple insertion or deletion this is fine.
|
||||||
|
# but if we swap a string (select it and add another string in it's place) or have multiple selections
|
||||||
|
# we receive two split text changes, first we add the new string in front of the selection
|
||||||
|
# and then we delete the old selection. e.g: [1234] -> hello is split into: [1234] -> hello[1234] -> hello[]
|
||||||
|
# this fucks over the operations factory algorithm, which panics if reading the operations sequentially,
|
||||||
|
# since the changes refer to the same point in time and are not updated each time.
|
||||||
|
|
||||||
|
# as a workaround, we compress all changes into a big change, which gives the region in which the change occurred
|
||||||
|
# and the new string, extracted directly from the local buffer already modified.
|
||||||
|
if len(changes) == 1:
|
||||||
|
return (changes[0].a.pt, changes[0].str, changes[0].b.pt)
|
||||||
|
|
||||||
|
return walk_compress_changes(view, changes)
|
||||||
|
|
||||||
|
def walk_compress_changes(view, changes):
|
||||||
|
# the bounding region of all text changes.
|
||||||
|
txt_a = float("inf")
|
||||||
|
txt_b = 0
|
||||||
|
|
||||||
|
# the region in the original buffer subjected to the change.
|
||||||
|
reg_a = float("inf")
|
||||||
|
reg_b = 0
|
||||||
|
|
||||||
|
# we keep track of how much the changes move the indexing of the buffer
|
||||||
|
buffer_shift = 0 # left - + right
|
||||||
|
|
||||||
|
for change in changes:
|
||||||
|
# the change in characters that the change would bring
|
||||||
|
# len(str) and .len_utf8 are mutually exclusive
|
||||||
|
# len(str) is when we insert new text at a position
|
||||||
|
# .len_utf8 is the length of the deleted/canceled string in the buffer
|
||||||
|
change_delta = len(change.str) - change.len_utf8
|
||||||
|
|
||||||
|
txt_a = min(txt_a, change.a.pt) # the text region is enlarged to the left
|
||||||
|
# On insertion, change.b.pt == change.a.pt
|
||||||
|
# If we meet a new insertion further than the current window
|
||||||
|
# we expand to the right by that change.
|
||||||
|
# On deletion, change.a.pt == change.b.pt - change.len_utf8
|
||||||
|
# when we delete a selection and it is further than the current window
|
||||||
|
# we enlarge to the right up until the begin of the deleted region.
|
||||||
|
if change.b.pt > txt_b:
|
||||||
|
txt_b = change.b.pt + change_delta
|
||||||
|
else:
|
||||||
|
# otherwise we just shift the window according to the change
|
||||||
|
txt_b += change_delta
|
||||||
|
|
||||||
|
reg_a = min(reg_a, change.a.pt) # text region enlarged to the left
|
||||||
|
# In this bit, we want to look at the buffer BEFORE the modifications
|
||||||
|
# but we are working on the buffer modified by all previous changes for each loop
|
||||||
|
# we use buffer_shift to keep track of how the buffer shifts around
|
||||||
|
# to map back to the correct index for each change in the unmodified buffer.
|
||||||
|
if change.b.pt + buffer_shift > reg_b:
|
||||||
|
# we only enlarge if we have changes that exceede on the right the current window
|
||||||
|
reg_b = change.b.pt + buffer_shift
|
||||||
|
|
||||||
|
# after using the change delta, we archive it for the next iterations
|
||||||
|
buffer_shift -= change_delta
|
||||||
|
|
||||||
|
# print("\t[buff change]", change.a.pt, change.str, "(", change.len_utf8,")", change.b.pt)
|
||||||
|
# print("[walking txt]", "[", txt_a, txt_b, "]")
|
||||||
|
# print("[walking reg]", "[", reg_a, reg_b, "]")
|
||||||
|
|
||||||
|
txt = view.substr(sublime.Region(txt_a, txt_b))
|
||||||
|
return reg_a, txt, reg_b
|
||||||
|
|
||||||
|
def apply_buffer_change(text_change):
|
||||||
|
print("test")
|
||||||
|
print(text_change)
|
||||||
|
print(text_change.start_incl, text_change.end_excl, text_change.content)
|
||||||
|
|
||||||
|
|
||||||
|
# Sublime interface
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
class CodempClientViewEventListener(sublime_plugin.ViewEventListener):
|
||||||
|
@classmethod
|
||||||
|
def is_applicable(cls, settings):
|
||||||
|
return "codemp_buffer" in settings
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def applies_to_primary_view_only(cls):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def on_selection_modified_async(self):
|
||||||
|
global _cursor_controller
|
||||||
|
if _cursor_controller:
|
||||||
|
send_cursor(self.view)
|
||||||
|
|
||||||
|
def on_close(self):
|
||||||
|
del self.view.settings()["codemp_buffer"]
|
||||||
|
|
||||||
|
def on_activated_async(self):
|
||||||
|
#gain input focus
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_deactivated_async(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class CodempClientTextChangeListener(sublime_plugin.TextChangeListener):
|
||||||
|
@classmethod
|
||||||
|
def is_applicable(cls, buffer):
|
||||||
|
if "codemp_buffer" in buffer.primary_view().settings():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def on_text_changed_async(self, changes):
|
||||||
|
global _buffer_controller
|
||||||
|
if _buffer_controller:
|
||||||
|
send_buffer_change(self.buffer, changes)
|
||||||
|
|
||||||
# See the proxy command class at the bottom
|
# See the proxy command class at the bottom
|
||||||
class CodempConnectCommand(sublime_plugin.WindowCommand):
|
class CodempConnectCommand(sublime_plugin.WindowCommand):
|
||||||
def run(self, server_host):
|
def run(self, server_host):
|
||||||
sublime_asyncio.dispatch(make_connection(server_host))
|
sublime_asyncio.dispatch(connect_command(server_host))
|
||||||
|
|
||||||
# see proxy command at the bottom
|
# see proxy command at the bottom
|
||||||
class CodempShareCommand(sublime_plugin.WindowCommand):
|
class CodempShareCommand(sublime_plugin.WindowCommand):
|
||||||
def run(self, buffer):
|
def run(self, buffer):
|
||||||
sublime_asyncio.dispatch(share_buffer(buffer))
|
sublime_asyncio.dispatch(share_buffer_command(buffer))
|
||||||
|
|
||||||
# see proxy command at the bottom
|
# see proxy command at the bottom
|
||||||
class CodempJoinCommand(sublime_plugin.WindowCommand):
|
# class CodempJoinCommand(sublime_plugin.WindowCommand):
|
||||||
def run(self, buffer):
|
# def run(self, buffer):
|
||||||
sublime_asyncio.dispatch(join_buffer(self.window, buffer))
|
# sublime_asyncio.dispatch(join_buffer(self.window, buffer))
|
||||||
|
|
||||||
class CodempPopulateView(sublime_plugin.TextCommand):
|
|
||||||
def run(self, edit, content):
|
|
||||||
self.view.replace(edit, sublime.Region(0, self.view.size()), content)
|
|
||||||
|
|
||||||
class ProxyCodempConnectCommand(sublime_plugin.WindowCommand):
|
class ProxyCodempConnectCommand(sublime_plugin.WindowCommand):
|
||||||
# on_window_command, does not trigger when called from the command palette
|
# on_window_command, does not trigger when called from the command palette
|
||||||
|
@ -227,18 +294,18 @@ class ProxyCodempShareCommand(sublime_plugin.WindowCommand):
|
||||||
return 'Share Buffer:'
|
return 'Share Buffer:'
|
||||||
|
|
||||||
|
|
||||||
class ProxyCodempJoinCommand(sublime_plugin.WindowCommand):
|
# class ProxyCodempJoinCommand(sublime_plugin.WindowCommand):
|
||||||
# on_window_command, does not trigger when called from the command palette
|
# # on_window_command, does not trigger when called from the command palette
|
||||||
# See: https://github.com/sublimehq/sublime_text/issues/2234
|
# # See: https://github.com/sublimehq/sublime_text/issues/2234
|
||||||
def run(self, **kwargs):
|
# def run(self, **kwargs):
|
||||||
self.window.run_command("codemp_join", kwargs)
|
# self.window.run_command("codemp_join", kwargs)
|
||||||
|
|
||||||
def input(self, args):
|
# def input(self, args):
|
||||||
if 'buffer' not in args:
|
# if 'buffer' not in args:
|
||||||
return BufferInputHandler()
|
# return BufferInputHandler()
|
||||||
|
|
||||||
def input_description(self):
|
# def input_description(self):
|
||||||
return 'Join Buffer:'
|
# return 'Join Buffer:'
|
||||||
|
|
||||||
class BufferInputHandler(sublime_plugin.ListInputHandler):
|
class BufferInputHandler(sublime_plugin.ListInputHandler):
|
||||||
def list_items(self):
|
def list_items(self):
|
||||||
|
@ -253,4 +320,4 @@ class BufferInputHandler(sublime_plugin.ListInputHandler):
|
||||||
|
|
||||||
class ServerHostInputHandler(sublime_plugin.TextInputHandler):
|
class ServerHostInputHandler(sublime_plugin.TextInputHandler):
|
||||||
def initial_text(self):
|
def initial_text(self):
|
||||||
return "http://[::1]:50051"
|
return "http://127.0.0.1:50051"
|
|
@ -5,42 +5,37 @@ class CodempClient():
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.handle = libcodemp.codemp_init()
|
self.handle = libcodemp.codemp_init()
|
||||||
self.ready = False
|
|
||||||
|
|
||||||
async def connect(self, server_host): # -> None
|
async def connect(self, server_host): # -> None
|
||||||
await self.handle.connect(server_host)
|
await self.handle.connect(server_host)
|
||||||
self.ready = True
|
|
||||||
|
|
||||||
def disconnect(self): # -> None
|
def disconnect(self): # -> None
|
||||||
# disconnect all buffers
|
# disconnect all buffers
|
||||||
# stop all callbacks
|
# stop all callbacks
|
||||||
self.handle = None
|
self.handle = None
|
||||||
self.ready = False
|
|
||||||
|
|
||||||
async def create(self, path, content=None): # -> None
|
async def create(self, path, content=None): # -> None
|
||||||
if self.ready:
|
await self.handle.create(path, content)
|
||||||
return await self.handle.create(path, content)
|
|
||||||
|
|
||||||
|
# join a workspace
|
||||||
async def join(self, session): # -> CursorController
|
async def join(self, session): # -> CursorController
|
||||||
if self.ready:
|
|
||||||
return CursorController(await self.handle.join(session))
|
return CursorController(await self.handle.join(session))
|
||||||
|
|
||||||
async def attach(self, path): # -> BufferController
|
async def attach(self, path): # -> BufferController
|
||||||
if self.ready:
|
|
||||||
return BufferController(await self.handle.attach(path))
|
return BufferController(await self.handle.attach(path))
|
||||||
|
|
||||||
async def get_cursor(self): # -> CursorController
|
async def get_cursor(self): # -> CursorController
|
||||||
if self.ready:
|
|
||||||
return CursorController(await self.handle.get_cursor())
|
return CursorController(await self.handle.get_cursor())
|
||||||
|
|
||||||
async def get_buffer(self, path): # -> BufferController
|
async def get_buffer(self, path): # -> BufferController
|
||||||
if self.ready:
|
|
||||||
return BufferController(await self.handle.get_buffer())
|
return BufferController(await self.handle.get_buffer())
|
||||||
|
|
||||||
async def remove_buffer(self, path): # -> None
|
async def remove_buffer(self, path): # -> None
|
||||||
if self.ready:
|
|
||||||
await self.handle.disconnect_buffer(path)
|
await self.handle.disconnect_buffer(path)
|
||||||
|
|
||||||
|
async def leave_workspace(self): # -> None
|
||||||
|
pass # todo
|
||||||
|
|
||||||
class CursorController():
|
class CursorController():
|
||||||
def __init__(self, handle):
|
def __init__(self, handle):
|
||||||
self.handle = handle
|
self.handle = handle
|
||||||
|
@ -62,7 +57,7 @@ class CursorController():
|
||||||
self.handle.drop_callback()
|
self.handle.drop_callback()
|
||||||
|
|
||||||
def callback(self, coro): # -> None
|
def callback(self, coro): # -> None
|
||||||
self.handle.callback(coro, id)
|
self.handle.callback(coro)
|
||||||
|
|
||||||
class BufferController():
|
class BufferController():
|
||||||
def __init__(self, handle):
|
def __init__(self, handle):
|
||||||
|
|
28
src/lib.rs
28
src/lib.rs
|
@ -1,6 +1,6 @@
|
||||||
use std::{sync::Arc, format};
|
use std::{sync::Arc, format};
|
||||||
|
|
||||||
use codemp::{prelude::*};
|
use codemp::prelude::*;
|
||||||
use codemp::errors::Error as CodempError;
|
use codemp::errors::Error as CodempError;
|
||||||
|
|
||||||
use pyo3::{
|
use pyo3::{
|
||||||
|
@ -58,7 +58,9 @@ impl PyClientHandle {
|
||||||
let rc = self.0.clone();
|
let rc = self.0.clone();
|
||||||
|
|
||||||
pyo3_asyncio::tokio::future_into_py(py, async move {
|
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||||
rc.connect(addr.as_str()).await.map_err(PyCodempError::from)?;
|
rc.connect(addr.as_str())
|
||||||
|
.await
|
||||||
|
.map_err(PyCodempError::from)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -89,6 +91,7 @@ impl PyClientHandle {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// join a workspace
|
||||||
fn join<'a>(&'a self, py: Python<'a>, session: String) -> PyResult<&'a PyAny> {
|
fn join<'a>(&'a self, py: Python<'a>, session: String) -> PyResult<&'a PyAny> {
|
||||||
let rc = self.0.clone();
|
let rc = self.0.clone();
|
||||||
|
|
||||||
|
@ -393,12 +396,11 @@ impl PyBufferController {
|
||||||
Ok(cont)
|
Ok(cont)
|
||||||
}
|
}
|
||||||
|
|
||||||
// What to do with this send? does it make sense to implement it at all?
|
// TODO: What to do with this send?
|
||||||
|
// does it make sense to implement it at all for the python side??
|
||||||
|
|
||||||
// fn send<'a>(&self, py: Python<'a>, skip: usize, text: String, tail: usize) -> PyResult<&'a PyAny>{
|
// fn send<'a>(&self, py: Python<'a>, skip: usize, text: String, tail: usize) -> PyResult<&'a PyAny>{
|
||||||
// let rc = self.handle.clone();
|
// todo!()
|
||||||
// pyo3_asyncio::tokio::future_into_py(py, async move {
|
|
||||||
// Ok(())
|
|
||||||
// })
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
fn try_recv(&self, py: Python<'_>) -> PyResult<PyObject> {
|
fn try_recv(&self, py: Python<'_>) -> PyResult<PyObject> {
|
||||||
|
@ -440,9 +442,16 @@ impl PyBufferController {
|
||||||
|
|
||||||
#[pyclass]
|
#[pyclass]
|
||||||
struct PyCursorEvent {
|
struct PyCursorEvent {
|
||||||
|
#[pyo3(get, set)]
|
||||||
user: String,
|
user: String,
|
||||||
|
|
||||||
|
#[pyo3(get, set)]
|
||||||
buffer: String,
|
buffer: String,
|
||||||
|
|
||||||
|
#[pyo3(get, set)]
|
||||||
start: (i32, i32),
|
start: (i32, i32),
|
||||||
|
|
||||||
|
#[pyo3(get, set)]
|
||||||
end: (i32, i32)
|
end: (i32, i32)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,8 +470,13 @@ impl From<CodempCursorEvent> for PyCursorEvent {
|
||||||
|
|
||||||
#[pyclass]
|
#[pyclass]
|
||||||
struct PyTextChange {
|
struct PyTextChange {
|
||||||
|
#[pyo3(get, set)]
|
||||||
start_incl: usize,
|
start_incl: usize,
|
||||||
|
|
||||||
|
#[pyo3(get, set)]
|
||||||
end_excl: usize,
|
end_excl: usize,
|
||||||
|
|
||||||
|
#[pyo3(get, set)]
|
||||||
content: String
|
content: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue