diff --git a/Codemp.sublime-commands b/Codemp.sublime-commands index 21bd39a..314d0e1 100644 --- a/Codemp.sublime-commands +++ b/Codemp.sublime-commands @@ -19,6 +19,13 @@ "file": "${packages}/CodempClient/README.md" } }, + { + "caption": "Codemp: Dump Internals", + "command": "codemp_client_dump", + "args": { + // "server_host": "http://[::1]:50051" + } + }, { // # on_window_command, does not trigger when called from the command palette // # See: https://github.com/sublimehq/sublime_text/issues/2234 @@ -64,4 +71,20 @@ // 'buffer_id': 'test' } }, + { + "caption": "Codemp: Create Buffer", + "command": "codemp_create_buffer", + "arg": { + // 'workspace_id': 'asd' + // 'buffer_id': 'test' + } + }, + { + "caption": "Codemp: Delete Buffer", + "command": "codemp_delete_buffer", + "arg": { + // 'workspace_id': 'asd' + // 'buffer_id': 'test' + } + }, ] \ No newline at end of file diff --git a/plugin.py b/plugin.py index e7e41aa..0d67ac3 100644 --- a/plugin.py +++ b/plugin.py @@ -83,11 +83,12 @@ class EventListener(sublime_plugin.EventListener): class CodempClientViewEventListener(sublime_plugin.ViewEventListener): @classmethod def is_applicable(cls, settings): + logger.debug(settings.get(g.CODEMP_BUFFER_TAG, False)) return settings.get(g.CODEMP_BUFFER_TAG, False) @classmethod def applies_to_primary_view_only(cls): - return False + return True def on_selection_modified_async(self): region = self.view.sel()[0] @@ -97,15 +98,22 @@ class CodempClientViewEventListener(sublime_plugin.ViewEventListener): vws = client.workspace_from_view(self.view) vbuff = client.buffer_from_view(self.view) if vws is None or vbuff is None: - raise + logger.error("we couldn't find the matching buffer or workspace!") + return + + logger.debug(f"selection modified! {vws.id}, {vbuff.id} - {start}, {end}") vws.send_cursor(vbuff.id, start, end) def on_activated(self): global TEXT_LISTENER + vbuff = client.buffer_from_view(self.view) + logging.debug(f"'{vbuff.id}' view activated!") safe_listener_attach(TEXT_LISTENER, self.view.buffer()) # pyright: ignore def on_deactivated(self): global TEXT_LISTENER + vbuff = client.buffer_from_view(self.view) + logging.debug(f"'{vbuff.id}' view deactivated!") safe_listener_detach(TEXT_LISTENER) # pyright: ignore def on_pre_close(self): @@ -147,6 +155,7 @@ class CodempClientTextChangeListener(sublime_plugin.TextChangeListener): vbuff = client.buffer_from_view(self.buffer.primary_view()) if vbuff is not None: # but then we block the main one for the actual sending! + logger.debug(f"local buffer change! {vbuff.id}") sublime.set_timeout(lambda: vbuff.send_buffer_change(changes)) @@ -167,6 +176,14 @@ class CodempClientTextChangeListener(sublime_plugin.TextChangeListener): # replace_text: swaps the content of a view with the given text. +class CodempClientDumpCommand(sublime_plugin.WindowCommand): + def is_enabled(self) -> bool: + return super().is_enabled() + + def run(self): + client.dump() + + # Client Commands ############################################################################# # Connect Command @@ -195,7 +212,7 @@ class CodempConnectCommand(sublime_plugin.WindowCommand): def input(self, args): if "server_host" not in args: return SimpleTextInput( - ("server_host", "http://127.0.0.1:50051"), + ("server_host", "http://codemp.alemi.dev:50053"), ("user_name", f"user-{random.random()}"), ) @@ -216,23 +233,22 @@ class CodempJoinWorkspaceCommand(sublime_plugin.WindowCommand): def run(self, workspace_id): assert client.codemp is not None - if client.valid_workspace(workspace_id): - logger.info(f"Joining workspace: '{workspace_id}'...") - promise = client.codemp.join_workspace(workspace_id) - active_window = sublime.active_window() + logger.info(f"Joining workspace: '{workspace_id}'...") + promise = client.codemp.join_workspace(workspace_id) + active_window = sublime.active_window() - def defer_instantiation(promise): - try: - workspace = promise.wait() - except Exception as e: - logger.error( - f"Could not join workspace '{workspace_id}'.\n\nerror: {e}" - ) - sublime.error_message(f"Could not join workspace '{workspace_id}'") - return - client.install_workspace(workspace, active_window) + def defer_instantiation(promise): + try: + workspace = promise.wait() + except Exception as e: + logger.error( + f"Could not join workspace '{workspace_id}'.\n\nerror: {e}" + ) + sublime.error_message(f"Could not join workspace '{workspace_id}'") + return + client.install_workspace(workspace, active_window) - sublime.set_timeout_async(lambda: defer_instantiation(promise)) + sublime.set_timeout_async(lambda: defer_instantiation(promise)) # the else shouldn't really happen, and if it does, it should already be instantiated. # ignore. @@ -241,7 +257,7 @@ class CodempJoinWorkspaceCommand(sublime_plugin.WindowCommand): def input(self, args): if "workspace_id" not in args: - return SimpleTextInput(("workspace_id", "")) + return SimpleTextInput(("workspace_id", "workspace?")) # To allow for having a selection and choosing non existing workspaces @@ -284,8 +300,11 @@ class CodempLeaveWorkspaceCommand(sublime_plugin.WindowCommand): return client.codemp is not None and len(client.all_workspaces(self.window)) > 0 def run(self, workspace_id: str): - # client.leave_workspace(id) - pass + assert client.codemp is not None + if client.codemp.leave_workspace(workspace_id): + vws = client.workspace_from_id(workspace_id) + if vws is not None: + client.uninstall_workspace(vws) def input(self, args): if "id" not in args: @@ -310,8 +329,10 @@ class CodempJoinBufferCommand(sublime_plugin.WindowCommand): # if not existing ask for creation (create + attach) vws = client.workspace_from_id(workspace_id) assert vws is not None + # is the buffer already installed? if vws.valid_buffer(buffer_id): + logger.debug("buffer already installed!") return # do nothing. if buffer_id not in vws.codemp.filetree(filter=buffer_id): @@ -330,17 +351,22 @@ class CodempJoinBufferCommand(sublime_plugin.WindowCommand): create_promise.wait() # now we can defer the attaching process + logger.debug(f"attempting to attach to {buffer_id}...") promise = vws.codemp.attach(buffer_id) def deferred_attach(promise): try: buff_ctl = promise.wait() + logger.debug("attach successfull!") except Exception as e: logging.error(f"error when attaching to buffer '{id}':\n\n {e}") sublime.error_message(f"Could not attach to buffer '{buffer_id}'") return vbuff = vws.install_buffer(buff_ctl) - # TODO! if the view is already active calling focus_view() will not trigger the on_activate + client.register_buffer(vws, vbuff) # we need to keep track of it. + + # TODO! if the view is already active calling focus_view() + # will not trigger the on_activate self.window.focus_view(vbuff.view) sublime.set_timeout_async(lambda: deferred_attach(promise)) @@ -350,7 +376,7 @@ class CodempJoinBufferCommand(sublime_plugin.WindowCommand): def input(self, args): if "workspace_id" not in args: - return ActiveWorkspacesIdList(self.window, get_buffer=True) + return ActiveWorkspacesIdList(self.window, buffer_list=True) if "buffer_id" not in args: return BufferIdList(args["workspace_id"]) @@ -373,6 +399,7 @@ class CodempLeaveBufferCommand(sublime_plugin.WindowCommand): def defer_detach(): if vws.codemp.detach(buffer_id): vws.uninstall_buffer(vbuff) + client.unregister_buffer(vbuff) sublime.set_timeout_async(defer_detach) @@ -381,7 +408,7 @@ class CodempLeaveBufferCommand(sublime_plugin.WindowCommand): def input(self, args): if "workspace_id" not in args: - return ActiveWorkspacesIdList(self.window, get_buffer=True) + return ActiveWorkspacesIdList(self.window, buffer_list=True) if "buffer_id" not in args: return BufferIdList(args["workspace_id"]) @@ -413,10 +440,12 @@ class CodempCreateBufferCommand(sublime_plugin.WindowCommand): def input(self, args): if "workspace_id" not in args: - return ActiveWorkspacesIdList(self.window, get_buffer=True) + return ActiveWorkspacesIdList(self.window, buffer_text=True) if "buffer_id" not in args: - return SimpleTextInput(("buffer_id", "new buffer")) + return SimpleTextInput( + (("buffer_id", "new buffer")), + ) class CodempDeleteBufferCommand(sublime_plugin.WindowCommand): @@ -475,7 +504,7 @@ class CodempDeleteBufferCommand(sublime_plugin.WindowCommand): def input(self, args): if "workspace_id" not in args: - return ActiveWorkspacesIdList(self.window, get_buffer=True) + return ActiveWorkspacesIdList(self.window, buffer_list=True) if "buffer_id" not in args: return BufferIdList(args["workspace_id"]) @@ -494,7 +523,7 @@ class CodempReplaceTextCommand(sublime_plugin.TextCommand): ############################################################ class SimpleTextInput(sublime_plugin.TextInputHandler): def __init__(self, *args: Tuple[str, str]): - assert len(args) > 0 + logging.debug(f"why isn't the text input working? {args}") self.argname = args[0][0] self.default = args[0][1] self.next_inputs = args[1:] @@ -506,15 +535,19 @@ class SimpleTextInput(sublime_plugin.TextInputHandler): return self.argname def next_input(self, args): + logging.debug( + f"why isn't the text input working? {self.argname}, {self.default}" + ) if len(self.next_inputs) > 0: if self.next_inputs[0][0] not in args: return SimpleTextInput(*self.next_inputs) class ActiveWorkspacesIdList(sublime_plugin.ListInputHandler): - def __init__(self, window=None, get_buffer=False): + def __init__(self, window=None, buffer_list=False, buffer_text=False): self.window = window - self.get_buffer = get_buffer + self.buffer_list = buffer_list + self.buffer_text = buffer_text def name(self): return "workspace_id" @@ -523,14 +556,17 @@ class ActiveWorkspacesIdList(sublime_plugin.ListInputHandler): return [vws.id for vws in client.all_workspaces(self.window)] def next_input(self, args): - if self.get_buffer: + if self.buffer_list: return BufferIdList(args["workspace_id"]) + elif self.buffer_text: + return SimpleTextInput(("buffer_id", "new buffer")) class BufferIdList(sublime_plugin.ListInputHandler): def __init__(self, workspace_id): + vws = client.workspace_from_id(workspace_id) self.add_entry_text = "* create new..." - self.list = [vbuff.id for vbuff in client.all_buffers(workspace_id)] + self.list = vws.codemp.filetree(None) self.list.sort() self.list.append(self.add_entry_text) self.preselected = None diff --git a/src/buffers.py b/src/buffers.py index 361ec03..e13ffee 100644 --- a/src/buffers.py +++ b/src/buffers.py @@ -1,13 +1,53 @@ +from __future__ import annotations + import sublime import os import logging import codemp from Codemp.src import globals as g +from Codemp.src.utils import populate_view logger = logging.getLogger(__name__) +def make_bufferchange_cb(buff: VirtualBuffer): + def __callback(bufctl: codemp.BufferController): + def get_change_and_apply(): + change_id = buff.view.change_id() + while change := bufctl.try_recv().wait(): + logger.debug("received remote buffer change!") + if change is None: + break + + if change.is_empty(): + logger.debug("change is empty. skipping.") + continue + + # In case a change arrives to a background buffer, just apply it. + # We are not listening on it. Otherwise, interrupt the listening + # to avoid echoing back the change just received. + if buff.view.id() == g.ACTIVE_CODEMP_VIEW: + buff.view.settings()[g.CODEMP_IGNORE_NEXT_TEXT_CHANGE] = True + + # we need to go through a sublime text command, since the method, + # view.replace needs an edit token, that is obtained only when calling + # a textcommand associated with a view. + buff.view.run_command( + "codemp_replace_text", + { + "start": change.start, + "end": change.end, + "content": change.content, + "change_id": change_id, + }, # pyright: ignore + ) + + sublime.set_timeout(get_change_and_apply) + + return __callback + + # This class is used as an abstraction between the local buffers (sublime side) and the # remote buffers (codemp side), to handle the syncronicity. # This class is mainly manipulated by a VirtualWorkspace, that manages its buffers @@ -25,31 +65,21 @@ class VirtualBuffer: def __hash__(self) -> int: return hash(self.id) + def sync(self): + promise = self.buffctl.content() + + def defer_sync(promise): + content = promise.wait() + populate_view(self.view, content) + + sublime.set_timeout_async(lambda: defer_sync(promise)) + def cleanup(self): self.uninstall() self.buffctl.stop() - def install(self, rootdir): - if self.installed: - return - - self.tmpfile = os.path.join(rootdir, self.id) - open(self.tmpfile, "a").close() - - self.view.set_scratch(True) - self.view.set_name(self.id) - self.view.retarget(self.tmpfile) - - s = self.view.settings() - self.view.set_status(g.SUBLIME_STATUS_ID, "[Codemp]") - s[g.CODEMP_BUFFER_TAG] = True - - self.__activate() - - self.installed = True - def uninstall(self): - if not self.installed: + if not getattr(self, "installed", False): return self.__deactivate() @@ -62,9 +92,29 @@ class VirtualBuffer: self.installed = False + def install(self, rootdir): + if getattr(self, "installed", False): + return + + self.tmpfile = os.path.join(rootdir, self.id) + open(self.tmpfile, "a").close() + + # self.view.set_scratch(True) + self.view.set_name(self.id) + self.view.retarget(self.tmpfile) + + s = self.view.settings() + self.view.set_status(g.SUBLIME_STATUS_ID, "[Codemp]") + s[g.CODEMP_BUFFER_TAG] = True + + self.sync() + self.__activate() + + self.installed = True + def __activate(self): logger.info(f"registering a callback for buffer: {self.id}") - self.buffctl.callback(self.__apply_bufferchange_cb) + self.buffctl.callback(make_bufferchange_cb(self)) self.isactive = True def __deactivate(self): @@ -85,35 +135,3 @@ class VirtualBuffer: # we must block and wait the send request to make sure the change went through ok self.buffctl.send(region.begin(), region.end(), change.str).wait() - - def __apply_bufferchange_cb(self, bufctl: codemp.BufferController): - def get_change_and_apply(): - change_id = self.view.change_id() - while change := bufctl.try_recv().wait(): - if change is None: - break - - if change.is_empty(): - logger.debug("change is empty. skipping.") - continue - - # In case a change arrives to a background buffer, just apply it. - # We are not listening on it. Otherwise, interrupt the listening - # to avoid echoing back the change just received. - if self.view.id() == g.ACTIVE_CODEMP_VIEW: - self.view.settings()[g.CODEMP_IGNORE_NEXT_TEXT_CHANGE] = True - - # we need to go through a sublime text command, since the method, - # view.replace needs an edit token, that is obtained only when calling - # a textcommand associated with a view. - self.view.run_command( - "codemp_replace_text", - { - "start": change.start, - "end": change.end, - "content": change.content, - "change_id": change_id, - }, # pyright: ignore - ) - - sublime.set_timeout(get_change_and_apply) diff --git a/src/client.py b/src/client.py index 2d0cecb..861a686 100644 --- a/src/client.py +++ b/src/client.py @@ -28,59 +28,81 @@ class VirtualClient: self.driver = codemp.init(lambda msg: logger.log(logger.level, msg), False) # bookkeeping corner - self.__id2buffer: dict[str, VirtualBuffer] = {} - self.__id2workspace: dict[str, VirtualWorkspace] = {} - self.__view2buff: dict[sublime.View, VirtualBuffer] = {} + self._id2buffer: dict[str, VirtualBuffer] = {} + self._id2workspace: dict[str, VirtualWorkspace] = {} + self._view2buff: dict[sublime.View, VirtualBuffer] = {} - self.__buff2workspace: bidict[VirtualBuffer, VirtualWorkspace] = bidict() - self.__workspace2window: bidict[VirtualWorkspace, sublime.Window] = bidict() + self._buff2workspace: bidict[VirtualBuffer, VirtualWorkspace] = bidict() + self._workspace2window: bidict[VirtualWorkspace, sublime.Window] = bidict() + + def dump(self): + logger.debug("CLIENT STATUS:") + logger.debug("WORKSPACES:") + logger.debug(f"{self._id2workspace}") + logger.debug(f"{self._workspace2window}") + logger.debug(f"{self._workspace2window.inverse}") + logger.debug(f"{self._buff2workspace}") + logger.debug(f"{self._buff2workspace.inverse}") + logger.debug("VIEWS") + logger.debug(f"{self._view2buff}") + logger.debug(f"{self._id2buffer}") def valid_window(self, window: sublime.Window): - return window in self.__workspace2window.inverse + return window in self._workspace2window.inverse def valid_workspace(self, workspace: VirtualWorkspace | str): if isinstance(workspace, str): - return client.__id2workspace.get(workspace) is not None + return client._id2workspace.get(workspace) is not None - return workspace in self.__workspace2window + return workspace in self._workspace2window def all_workspaces( self, window: Optional[sublime.Window] = None ) -> list[VirtualWorkspace]: if window is None: - return list(self.__workspace2window.keys()) + return list(self._workspace2window.keys()) else: - return self.__workspace2window.inverse.get(window, []) + return self._workspace2window.inverse.get(window, []) def workspace_from_view(self, view: sublime.View) -> Optional[VirtualWorkspace]: - buff = self.__view2buff.get(view, None) - return self.__buff2workspace.get(buff, None) + buff = self._view2buff.get(view, None) + return self._buff2workspace.get(buff, None) def workspace_from_buffer(self, buff: VirtualBuffer) -> Optional[VirtualWorkspace]: - return self.__buff2workspace.get(buff) + return self._buff2workspace.get(buff) def workspace_from_id(self, id: str) -> Optional[VirtualWorkspace]: - return self.__id2workspace.get(id) + return self._id2workspace.get(id) def all_buffers( self, workspace: Optional[VirtualWorkspace | str] = None ) -> list[VirtualBuffer]: if workspace is None: - return list(self.__buff2workspace.keys()) + return list(self._buff2workspace.keys()) else: if isinstance(workspace, str): - workspace = client.__id2workspace[workspace] - return self.__buff2workspace.inverse.get(workspace, []) + workspace = client._id2workspace[workspace] + return self._buff2workspace.inverse.get(workspace, []) def buffer_from_view(self, view: sublime.View) -> Optional[VirtualBuffer]: - return self.__view2buff.get(view) + return self._view2buff.get(view) def buffer_from_id(self, id: str) -> Optional[VirtualBuffer]: - return self.__id2buffer.get(id) + return self._id2buffer.get(id) def view_from_buffer(self, buff: VirtualBuffer) -> sublime.View: return buff.view + def register_buffer(self, workspace: VirtualWorkspace, buffer: VirtualBuffer): + self._buff2workspace[buffer] = workspace + self._id2buffer[buffer.id] = buffer + self._view2buff[buffer.view] = buffer + + def unregister_buffer(self, buffer: VirtualBuffer): + del self._buff2workspace[buffer] + del self._id2buffer[buffer.id] + del self._view2buff[buffer.view] + def disconnect(self): if self.codemp is None: return @@ -90,11 +112,11 @@ class VirtualClient: vws.cleanup() self.codemp.leave_workspace(vws.id) - self.__id2workspace.clear() - self.__id2buffer.clear() - self.__buff2workspace.clear() - self.__view2buff.clear() - self.__workspace2window.clear() + self._id2workspace.clear() + self._id2buffer.clear() + self._buff2workspace.clear() + self._view2buff.clear() + self._workspace2window.clear() self.codemp = None def connect(self, host: str, user: str, password: str): @@ -112,22 +134,25 @@ class VirtualClient: # we pass the window as well so if the window changes in the mean # time we have the correct one! vws = VirtualWorkspace(workspace, window) - self.__workspace2window[vws] = window - self.__id2workspace[vws.id] = vws + self._workspace2window[vws] = window + self._id2workspace[vws.id] = vws vws.install() - return vws def uninstall_workspace(self, vws: VirtualWorkspace): - if vws not in self.__workspace2window: + if vws not in self._workspace2window: raise logger.info(f"Uninstalling workspace '{vws.id}'...") vws.cleanup() - del self.__id2workspace[vws.id] - del self.__workspace2window[vws] - self.__buff2workspace.inverse_del(vws) + del self._workspace2window[vws] + del self._id2workspace[vws.id] + buffers = self._buff2workspace.inverse[vws] + for vbuff in buffers: + self.unregister_buffer(vbuff) + # self._buff2workspace.inverse_del(vws) - if we delete all straight + # keys the last delete will remove also the empty key. def workspaces_in_server(self): return self.codemp.active_workspaces() if self.codemp else [] diff --git a/src/workspace.py b/src/workspace.py index 20856a4..4505471 100644 --- a/src/workspace.py +++ b/src/workspace.py @@ -16,6 +16,28 @@ from Codemp.src.utils import bidict logger = logging.getLogger(__name__) +def make_cursor_callback(workspace: VirtualWorkspace): + def __callback(ctl: codemp.CursorController): + def get_event_and_draw(): + while event := ctl.try_recv().wait(): + logger.debug("received remote cursor movement!") + if event is None: + break + + vbuff = workspace.buff_by_id(event.buffer) + if vbuff is None: + logger.warning( + "received a cursor event for a buffer that wasn't saved internally." + ) + continue + + draw_cursor_region(vbuff.view, event.start, event.end, event.user) + + sublime.set_timeout_async(get_event_and_draw) + + return __callback + + # A virtual workspace is a bridge class that aims to translate # events that happen to the codemp workspaces into sublime actions class VirtualWorkspace: @@ -30,8 +52,8 @@ class VirtualWorkspace: self.codemp.fetch_users() # mapping remote ids -> local ids - self.__buff2view: bidict[VirtualBuffer, sublime.View] = bidict() - self.__id2buff: dict[str, VirtualBuffer] = {} + self._buff2view: bidict[VirtualBuffer, sublime.View] = bidict() + self._id2buff: dict[str, VirtualBuffer] = {} # self.id_map: dict[str, int] = {} # self.active_buffers: dict[int, VirtualBuffer] = {} # local_id -> VBuff @@ -44,27 +66,27 @@ class VirtualWorkspace: # if not get up to speed! self.codemp.fetch_buffers().wait() attached_buffers = self.codemp.buffer_list() - all(id in self.__id2buff for id in attached_buffers) + all(id in self._id2buff for id in attached_buffers) # TODO! def valid_buffer(self, buff: VirtualBuffer | str): if isinstance(buff, str): return self.buff_by_id(buff) is not None - return buff in self.__buff2view + return buff in self._buff2view def all_buffers(self) -> list[VirtualBuffer]: - return list(self.__buff2view.keys()) + return list(self._buff2view.keys()) def buff_by_view(self, view: sublime.View) -> Optional[VirtualBuffer]: - buff = self.__buff2view.inverse.get(view) + buff = self._buff2view.inverse.get(view) return buff[0] if buff is not None else None def buff_by_id(self, id: str) -> Optional[VirtualBuffer]: - return self.__id2buff.get(id) + return self._id2buff.get(id) def all_views(self) -> list[sublime.View]: - return list(self.__buff2view.inverse.keys()) + return list(self._buff2view.inverse.keys()) def view_by_buffer(self, buffer: VirtualBuffer) -> sublime.View: return buffer.view @@ -76,14 +98,14 @@ class VirtualWorkspace: for view in self.all_views(): view.close() - self.__buff2view.clear() - self.__id2buff.clear() - self.uninstall() self.curctl.stop() + self._buff2view.clear() + self._id2buff.clear() + def uninstall(self): - if not self.installed: + if not getattr(self, "installed", False): return self.__deactivate() @@ -106,7 +128,7 @@ class VirtualWorkspace: self.installed = False def install(self): - if self.installed: + if getattr(self, "installed", False): return # initialise the virtual filesystem @@ -127,7 +149,7 @@ class VirtualWorkspace: self.installed = True def __activate(self): - self.curctl.callback(self.__move_cursor_callback) + self.curctl.callback(make_cursor_callback(self)) self.isactive = True def __deactivate(self): @@ -135,11 +157,13 @@ class VirtualWorkspace: self.isactive = False def install_buffer(self, buff: codemp.BufferController) -> VirtualBuffer: + logger.debug(f"installing buffer {buff.name()}") view = self.window.new_file() vbuff = VirtualBuffer(buff, view) - self.__buff2view[vbuff] = view - self.__id2buff[vbuff.id] = vbuff + logger.debug("created virtual buffer") + self._buff2view[vbuff] = view + self._id2buff[vbuff.id] = vbuff vbuff.install(self.rootdir) @@ -148,28 +172,11 @@ class VirtualWorkspace: def uninstall_buffer(self, vbuff: VirtualBuffer): vbuff.cleanup() buffview = self.view_by_buffer(vbuff) - del self.__buff2view[vbuff] - del self.__id2buff[vbuff.id] + del self._buff2view[vbuff] + del self._id2buff[vbuff.id] buffview.close() def send_cursor(self, id: str, start: Tuple[int, int], end: Tuple[int, int]): # we can safely ignore the promise, we don't really care if everything # is ok for now with the cursor. self.curctl.send(id, start, end) - - def __move_cursor_callback(self, ctl: codemp.CursorController): - def get_event_and_draw(): - while event := ctl.try_recv().wait(): - if event is None: - break - - vbuff = self.buff_by_id(event.buffer) - if vbuff is None: - logger.warning( - "received a cursor event for a buffer that wasn't saved internally." - ) - continue - - draw_cursor_region(vbuff.view, event.start, event.end, event.user) - - sublime.set_timeout_async(get_event_and_draw)