From e7a3f7f3676967246161685f8d9cd81989539a67 Mon Sep 17 00:00:00 2001 From: cschen <camillo.schenone@gmail.com> Date: Sat, 22 Feb 2025 19:39:32 +0100 Subject: [PATCH] fix: clearer creation of virtual workspaces and buffers. fix: fix messages formatting fix: handle workspaces without buffers when attaching fix: workspave lookupparent feat: initial setup to support local views --- plugin/commands/client.py | 42 ++++++-------------- plugin/commands/workspace.py | 55 +++++++++++---------------- plugin/core/buffers.py | 74 +++++++++++++++++++++++++----------- plugin/core/workspace.py | 32 ++++++++++++---- 4 files changed, 110 insertions(+), 93 deletions(-) diff --git a/plugin/commands/client.py b/plugin/commands/client.py index 16714fc..9158ee7 100644 --- a/plugin/commands/client.py +++ b/plugin/commands/client.py @@ -119,11 +119,9 @@ class CodempDisconnectCommand(sublime_plugin.WindowCommand): return session.is_active() def run(self): - cli = session.client - - for ws in workspaces.lookup(): - if cli.leave_workspace(ws.id): - workspaces.remove(ws) + wslist = session.client.active_workspaces() + for ws in wslist: + workspaces.remove(ws) session.drop_client() logger.info(f"disconnected from server '{session.config.host}'!") @@ -145,19 +143,11 @@ class CodempJoinWorkspaceCommand(sublime_plugin.WindowCommand): ) def run(self, workspace_id): # pyright: ignore[reportIncompatibleMethodOverride] - if workspace_id is None: + if workspace_id in workspaces: return logger.info(f"Joining workspace: '{workspace_id}'...") - try: - ws = session.client.attach_workspace(workspace_id).wait() - except Exception as e: - logger.error(f"Could not join workspace '{workspace_id}': {e}") - sublime.error_message(f"Could not join workspace '{workspace_id}'") - raise e - - logger.debug("Joined! Adding workspace to registry") - workspaces.register(ws) + workspaces.register(workspace_id) # Leave Workspace Command @@ -172,14 +162,13 @@ class CodempLeaveWorkspaceCommand(sublime_plugin.WindowCommand): ) def run(self, workspace_id: str): # pyright: ignore[reportIncompatibleMethodOverride] - try: - workspaces.remove(workspace_id) - finally: - if not session.client.leave_workspace(workspace_id): - logger.error(f"could not leave the workspace '{workspace_id}'") - else: - logger.debug(f"successfully left the workspace '{workspace_id}'") + if workspace_id not in workspaces: + sublime.error_message(f"You are not attached to the workspace '{workspace_id}'") + logger.warning(f"You are not attached to the workspace_id '{workspace_id}'") + return + logger.debug("We are about to remove the workspace {}".format(workspace_id)) + workspaces.remove(workspace_id) class CodempInviteToWorkspaceCommand(sublime_plugin.WindowCommand): def is_enabled(self) -> bool: @@ -208,10 +197,6 @@ class CodempCreateWorkspaceCommand(sublime_plugin.WindowCommand): def is_enabled(self): return session.is_active() - # def input(self, args): - # if "workspace_id" not in args: - # return SimpleTextInput(("workspace_id", "new workspace name")) - def run(self, workspace_id: str): # pyright: ignore[reportIncompatibleMethodOverride] try: session.client.create_workspace(workspace_id) @@ -224,11 +209,6 @@ class CodempDeleteWorkspaceCommand(sublime_plugin.WindowCommand): def is_enabled(self): return session.is_active() - # def input(self, args): - # workspaces = session.get_workspaces(owned=True, invited=False) # noqa: F841 - # if "workspace_id" not in args: - # return SimpleListInput(("workspace_id", workspaces)) - def run(self, workspace_id: str): # pyright: ignore[reportIncompatibleMethodOverride] if workspace_id in workspaces: if not sublime.ok_cancel_dialog( diff --git a/plugin/commands/workspace.py b/plugin/commands/workspace.py index 1cafb2e..dfc94d9 100644 --- a/plugin/commands/workspace.py +++ b/plugin/commands/workspace.py @@ -1,14 +1,14 @@ import sublime import sublime_plugin import logging -import gc +import os from ..core.session import session from ..core.workspace import workspaces from ..core.buffers import buffers from ..text_listener import TEXT_LISTENER -from ..utils import safe_listener_attach, safe_listener_detach, populate_view +from ..utils import some from ..input_handlers import SimpleListInput, SimpleTextInput logger = logging.getLogger(__name__) @@ -37,11 +37,18 @@ class CodempJoinBufferCommand(sublime_plugin.WindowCommand): return None bflist = ws.handle.fetch_buffers().wait() - return SimpleListInput( - ("buffer_id", bflist), - ) + if bflist: + return SimpleListInput( + ("buffer_id", bflist) + ) + else: + sublime.error_message("Workspace does not have any buffers inside.") + return None def run(self, workspace_id, buffer_id): # pyright: ignore[reportIncompatibleMethodOverride] + if not buffer_id: + return + try: vws = workspaces.lookupId(workspace_id) except KeyError: logger.error(f"Can't create buffer: '{workspace_id}' does not exists or is not active.") @@ -54,25 +61,14 @@ class CodempJoinBufferCommand(sublime_plugin.WindowCommand): except KeyError: pass - # now we can defer the attaching process logger.debug(f"attempting to attach to {buffer_id}...") - ctl_promise = vws.handle.attach_buffer(buffer_id) def _(): - try: - buff_ctl = ctl_promise.wait() - logger.debug("attach successfull!") - except Exception as e: - logger.error(f"error when attaching to buffer '{id}':\n\n {e}") - sublime.error_message(f"Could not attach to buffer '{buffer_id}'") - return - - vbuff = buffers.register(buff_ctl, vws) + vbuff = some(buffers.register(buffer_id, vws)) vbuff.sync(TEXT_LISTENER) sublime.set_timeout_async(_) - # Leave Buffer Comand class CodempLeaveBufferCommand(sublime_plugin.WindowCommand): def is_enabled(self): @@ -88,20 +84,13 @@ class CodempLeaveBufferCommand(sublime_plugin.WindowCommand): ) def run(self, buffer_id): # pyright: ignore[reportIncompatibleMethodOverride] - try: - buff = buffers.lookupId(buffer_id) - vws = buffers.lookupParent(buff) - except KeyError: - sublime.error_message(f"You are not attached to the buffer '{buffer_id}'") - logger.warning(f"You are not attached to the buffer '{buffer_id}'") - return - - if not vws.handle.get_buffer(buffer_id): - logger.error("The desired buffer is not managed by the workspace.") + if buffer_id not in buffers: + logger.warning(f"The buffer was already removed: '{buffer_id}'") return # The call must happen separately, otherwise it causes sublime to crash... # no idea why... + sublime.set_timeout(lambda: buffers.remove(buffer_id), 10) def _(): buffers.remove(buffer_id) if not vws.handle.detach_buffer(buffer_id): @@ -113,7 +102,7 @@ class CodempLeaveBufferCommand(sublime_plugin.WindowCommand): # Leave Buffer Comand class CodempCreateBufferCommand(sublime_plugin.WindowCommand): def is_enabled(self): - return len(workspaces.lookup()) > 0 + return workspaces.hasactive() def run(self, workspace_id, buffer_id):# pyright: ignore[reportIncompatibleMethodOverride] try: vws = workspaces.lookupId(workspace_id) @@ -122,9 +111,9 @@ class CodempCreateBufferCommand(sublime_plugin.WindowCommand): logger.warning(f"You are not attached to the workspace '{workspace_id}'") return - vws.handle.create_buffer(buffer_id) + vws.handle.create_buffer(buffer_id).wait() logger.info( - "created buffer '{buffer_id}' in the workspace '{workspace_id}'.\n\ + f"created buffer '{buffer_id}' in the workspace '{workspace_id}'.\n\ To interact with it you need to attach to it with Codemp: Attach." ) @@ -136,14 +125,14 @@ class CodempDeleteBufferCommand(sublime_plugin.WindowCommand): try: vws = workspaces.lookupId(workspace_id) except KeyError: - sublime.error_message(f"You are not attached to the workspace '{workspace_id}'") - logger.warning(f"You are not attached to the workspace '{workspace_id}'") + sublime.error_message(f"You are not attached to the workspace {workspace_id}") + logger.warning(f"You are not attached to the workspace {workspace_id}") return if buffer_id in buffers: if not sublime.ok_cancel_dialog( - "You are currently attached to '{buffer_id}'.\n\ + f"You are currently attached to '{buffer_id}'.\n\ Do you want to detach and delete it?", ok_title="yes", title="Delete Buffer?", ): return diff --git a/plugin/core/buffers.py b/plugin/core/buffers.py index b535a80..e0a552f 100644 --- a/plugin/core/buffers.py +++ b/plugin/core/buffers.py @@ -71,17 +71,27 @@ def bind_callback(v: sublime.View): return _callback class BufferManager(): - def __init__(self, handle: codemp.BufferController, v: sublime.View, filename: str): + def __init__(self, handle: codemp.BufferController, v: sublime.View, filename: str, islocal: bool): self.handle: codemp.BufferController = handle self.view: sublime.View = v + self.islocal: bool = islocal self.id = self.handle.path() self.filename = filename self.handle.callback(bind_callback(self.view)) + self.view.settings().set(g.CODEMP_VIEW_TAG, True) + self.view.settings().set(g.CODEMP_BUFFER_ID, self.id) + self.view.set_status(g.SUBLIME_STATUS_ID, "[Codemp]") + def __del__(self): logger.debug(f"dropping buffer {self.id}") - self.view.close() self.handle.clear_callback() + if self.islocal: + self.view.settings().erase(g.CODEMP_BUFFER_ID) + self.view.settings().erase(g.CODEMP_VIEW_TAG) + self.view.set_status(g.SUBLIME_STATUS_ID, "") + else: + self.view.close() def __hash__(self): return hash(self.id) @@ -103,17 +113,26 @@ class BufferManager(): def sync(self, text_listener): promise = self.handle.content() def _(): - current_contents = get_contents(self.view) + # current_contents = get_contents(self.view) content = promise.wait() - if content == current_contents: - return safe_listener_detach(text_listener) populate_view(self.view, content) safe_listener_attach(text_listener, self.view.buffer()) + sublime.status_message("Syncd contents.") sublime.set_timeout_async(_) + def overwrite(self, text_listener): + localcontents = get_contents(self.view) + remotecontents = self.handle.content().wait() + remotelen = len(remotecontents) + self.handle.send( + TextChange(start=0, end=remotelen, content= localcontents) + ) + self.sync(text_listener) + + class BufferRegistry(): def __init__(self): self._buffers: bidict[BufferManager, WorkspaceManager] = bidict() @@ -142,28 +161,32 @@ class BufferRegistry(): if not bfm: raise KeyError return bfm - def register(self, bhandle: codemp.BufferController, wsm: WorkspaceManager): - bid = bhandle.path() - + def register(self, buff: str, wsm: WorkspaceManager, localview: sublime.View | None = None): + + try: buffctl = wsm.handle.attach_buffer(buff).wait() + except Exception as e: + logger.error(f"error when attaching to buffer '{id}':\n\n {e}") + sublime.error_message(f"Could not attach to buffer '{buff}'") + raise e + win = sublime.active_window() - newfileflags = sublime.NewFileFlags.TRANSIENT \ - | sublime.NewFileFlags.ADD_TO_SELECTION \ - | sublime.NewFileFlags.FORCE_CLONE - view = win.new_file(newfileflags) + if not localview: + newfileflags = sublime.NewFileFlags.TRANSIENT \ + | sublime.NewFileFlags.ADD_TO_SELECTION \ + | sublime.NewFileFlags.FORCE_CLONE + view = win.new_file(newfileflags) - view.set_scratch(True) - view.set_name(os.path.basename(bid)) - syntax = sublime.find_syntax_for_file(bid) - if syntax: - view.assign_syntax(syntax) - - view.settings().set(g.CODEMP_VIEW_TAG, True) - view.settings().set(g.CODEMP_BUFFER_ID, bid) - view.set_status(g.SUBLIME_STATUS_ID, "[Codemp]") + view.set_scratch(True) + view.set_name(os.path.basename(buff)) + syntax = sublime.find_syntax_for_file(buff) + if syntax: + view.assign_syntax(syntax) + else: + view = localview tmpfile = "DISABLE" - bfm = BufferManager(bhandle, view, tmpfile) + bfm = BufferManager(buffctl, view, tmpfile, islocal = localview is not None) self._buffers[bfm] = wsm return bfm @@ -171,9 +194,16 @@ class BufferRegistry(): def remove(self, bf: BufferManager | str): if isinstance(bf, str): bf = self.lookupId(bf) + ws = self.lookupParent(bf) del self._buffers[bf] + bf = bf.id + if not ws.handle.detach_buffer(bf): + logger.error(f"could not leave the buffer {bf}.") + else: + logger.debug(f"successfully detached from {bf}.") + buffers = BufferRegistry() diff --git a/plugin/core/workspace.py b/plugin/core/workspace.py index 810c2c4..b177d6b 100644 --- a/plugin/core/workspace.py +++ b/plugin/core/workspace.py @@ -8,7 +8,6 @@ import sublime import shutil import tempfile import logging -import gc from codemp import Selection from .. import globals as g @@ -107,7 +106,7 @@ class WorkspaceRegistry(): return True def hasactive(self): - return len(session.client.active_workspaces()) > 0 + return len(self._workspaces.keys()) > 0 def lookup(self, w: Optional[sublime.Window] = None) -> list[WorkspaceManager]: if not w: @@ -117,7 +116,7 @@ class WorkspaceRegistry(): def lookupParent(self, ws: WorkspaceManager | str) -> sublime.Window: if isinstance(ws, str): - wsm = self.lookupId(ws) + ws = self.lookupId(ws) return self._workspaces[ws] def lookupId(self, wid: str) -> WorkspaceManager: @@ -125,25 +124,44 @@ class WorkspaceRegistry(): if not wsm: raise KeyError return wsm - def register(self, wshandle: codemp.Workspace) -> WorkspaceManager: + def register(self, wid: str) -> WorkspaceManager: win = sublime.active_window() # tmpdir = tempfile.mkdtemp(prefix="codemp_") # add_project_folder(win, tmpdir, f"{g.WORKSPACE_FOLDER_PREFIX}{wshandle.id()}") + try: + ws = session.client.attach_workspace(wid).wait() + except Exception as e: + logger.error(f"Could not join workspace '{wid}': {e}") + sublime.error_message(f"Could not join workspace '{wid}'") + raise e + logger.debug("Joined! Adding workspace to registry") tmpdir = "DISABLED" - wm = WorkspaceManager(wshandle, win, tmpdir) + wm = WorkspaceManager(ws, win, tmpdir) self._workspaces[wm] = win + return wm def remove(self, ws: WorkspaceManager | str): if isinstance(ws, str): ws = self.lookupId(ws) - # remove_project_folder(ws.window, f"{g.WORKSPACE_FOLDER_PREFIX}{ws.id}") - # shutil.rmtree(ws.rootdir, ignore_errors=True) + logger.debug("removing all the buffers from the workspace") + # we need ids, we can't keep references to the buffermanager here + bufferlist = [buff.id for buff in buffers.lookup(ws)] + for buff in bufferlist: + logger.debug("removing the buffer {}".format(buff)) + buffers.remove(buff) + del self._workspaces[ws] + ws = ws.id + if not session.client.leave_workspace(ws): + logger.error(f"could not leave the workspace '{ws}'") + else: + logger.debug(f"successfully left the workspace '{ws}'") + workspaces = WorkspaceRegistry()