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
This commit is contained in:
cschen 2025-02-22 19:39:32 +01:00
parent b79e25748c
commit e7a3f7f367
4 changed files with 110 additions and 93 deletions

View file

@ -119,11 +119,9 @@ class CodempDisconnectCommand(sublime_plugin.WindowCommand):
return session.is_active() return session.is_active()
def run(self): def run(self):
cli = session.client wslist = session.client.active_workspaces()
for ws in wslist:
for ws in workspaces.lookup(): workspaces.remove(ws)
if cli.leave_workspace(ws.id):
workspaces.remove(ws)
session.drop_client() session.drop_client()
logger.info(f"disconnected from server '{session.config.host}'!") 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] def run(self, workspace_id): # pyright: ignore[reportIncompatibleMethodOverride]
if workspace_id is None: if workspace_id in workspaces:
return return
logger.info(f"Joining workspace: '{workspace_id}'...") logger.info(f"Joining workspace: '{workspace_id}'...")
try: workspaces.register(workspace_id)
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)
# Leave Workspace Command # Leave Workspace Command
@ -172,14 +162,13 @@ class CodempLeaveWorkspaceCommand(sublime_plugin.WindowCommand):
) )
def run(self, workspace_id: str): # pyright: ignore[reportIncompatibleMethodOverride] def run(self, workspace_id: str): # pyright: ignore[reportIncompatibleMethodOverride]
try: if workspace_id not in workspaces:
workspaces.remove(workspace_id) sublime.error_message(f"You are not attached to the workspace '{workspace_id}'")
finally: logger.warning(f"You are not attached to the workspace_id '{workspace_id}'")
if not session.client.leave_workspace(workspace_id): return
logger.error(f"could not leave the workspace '{workspace_id}'")
else:
logger.debug(f"successfully left the workspace '{workspace_id}'")
logger.debug("We are about to remove the workspace {}".format(workspace_id))
workspaces.remove(workspace_id)
class CodempInviteToWorkspaceCommand(sublime_plugin.WindowCommand): class CodempInviteToWorkspaceCommand(sublime_plugin.WindowCommand):
def is_enabled(self) -> bool: def is_enabled(self) -> bool:
@ -208,10 +197,6 @@ class CodempCreateWorkspaceCommand(sublime_plugin.WindowCommand):
def is_enabled(self): def is_enabled(self):
return session.is_active() 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] def run(self, workspace_id: str): # pyright: ignore[reportIncompatibleMethodOverride]
try: try:
session.client.create_workspace(workspace_id) session.client.create_workspace(workspace_id)
@ -224,11 +209,6 @@ class CodempDeleteWorkspaceCommand(sublime_plugin.WindowCommand):
def is_enabled(self): def is_enabled(self):
return session.is_active() 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] def run(self, workspace_id: str): # pyright: ignore[reportIncompatibleMethodOverride]
if workspace_id in workspaces: if workspace_id in workspaces:
if not sublime.ok_cancel_dialog( if not sublime.ok_cancel_dialog(

View file

@ -1,14 +1,14 @@
import sublime import sublime
import sublime_plugin import sublime_plugin
import logging import logging
import gc import os
from ..core.session import session from ..core.session import session
from ..core.workspace import workspaces from ..core.workspace import workspaces
from ..core.buffers import buffers from ..core.buffers import buffers
from ..text_listener import TEXT_LISTENER 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 from ..input_handlers import SimpleListInput, SimpleTextInput
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -37,11 +37,18 @@ class CodempJoinBufferCommand(sublime_plugin.WindowCommand):
return None return None
bflist = ws.handle.fetch_buffers().wait() bflist = ws.handle.fetch_buffers().wait()
return SimpleListInput( if bflist:
("buffer_id", 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] def run(self, workspace_id, buffer_id): # pyright: ignore[reportIncompatibleMethodOverride]
if not buffer_id:
return
try: vws = workspaces.lookupId(workspace_id) try: vws = workspaces.lookupId(workspace_id)
except KeyError: except KeyError:
logger.error(f"Can't create buffer: '{workspace_id}' does not exists or is not active.") 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: except KeyError:
pass pass
# now we can defer the attaching process
logger.debug(f"attempting to attach to {buffer_id}...") logger.debug(f"attempting to attach to {buffer_id}...")
ctl_promise = vws.handle.attach_buffer(buffer_id)
def _(): def _():
try: vbuff = some(buffers.register(buffer_id, vws))
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.sync(TEXT_LISTENER) vbuff.sync(TEXT_LISTENER)
sublime.set_timeout_async(_) sublime.set_timeout_async(_)
# Leave Buffer Comand # Leave Buffer Comand
class CodempLeaveBufferCommand(sublime_plugin.WindowCommand): class CodempLeaveBufferCommand(sublime_plugin.WindowCommand):
def is_enabled(self): def is_enabled(self):
@ -88,20 +84,13 @@ class CodempLeaveBufferCommand(sublime_plugin.WindowCommand):
) )
def run(self, buffer_id): # pyright: ignore[reportIncompatibleMethodOverride] def run(self, buffer_id): # pyright: ignore[reportIncompatibleMethodOverride]
try: if buffer_id not in buffers:
buff = buffers.lookupId(buffer_id) logger.warning(f"The buffer was already removed: '{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.")
return return
# The call must happen separately, otherwise it causes sublime to crash... # The call must happen separately, otherwise it causes sublime to crash...
# no idea why... # no idea why...
sublime.set_timeout(lambda: buffers.remove(buffer_id), 10)
def _(): def _():
buffers.remove(buffer_id) buffers.remove(buffer_id)
if not vws.handle.detach_buffer(buffer_id): if not vws.handle.detach_buffer(buffer_id):
@ -113,7 +102,7 @@ class CodempLeaveBufferCommand(sublime_plugin.WindowCommand):
# Leave Buffer Comand # Leave Buffer Comand
class CodempCreateBufferCommand(sublime_plugin.WindowCommand): class CodempCreateBufferCommand(sublime_plugin.WindowCommand):
def is_enabled(self): def is_enabled(self):
return len(workspaces.lookup()) > 0 return workspaces.hasactive()
def run(self, workspace_id, buffer_id):# pyright: ignore[reportIncompatibleMethodOverride] def run(self, workspace_id, buffer_id):# pyright: ignore[reportIncompatibleMethodOverride]
try: vws = workspaces.lookupId(workspace_id) 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}'") logger.warning(f"You are not attached to the workspace '{workspace_id}'")
return return
vws.handle.create_buffer(buffer_id) vws.handle.create_buffer(buffer_id).wait()
logger.info( 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." 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) try: vws = workspaces.lookupId(workspace_id)
except KeyError: except KeyError:
sublime.error_message(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}'") logger.warning(f"You are not attached to the workspace {workspace_id}")
return return
if buffer_id in buffers: if buffer_id in buffers:
if not sublime.ok_cancel_dialog( 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?", Do you want to detach and delete it?",
ok_title="yes", title="Delete Buffer?", ok_title="yes", title="Delete Buffer?",
): return ): return

View file

@ -71,17 +71,27 @@ def bind_callback(v: sublime.View):
return _callback return _callback
class BufferManager(): 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.handle: codemp.BufferController = handle
self.view: sublime.View = v self.view: sublime.View = v
self.islocal: bool = islocal
self.id = self.handle.path() self.id = self.handle.path()
self.filename = filename self.filename = filename
self.handle.callback(bind_callback(self.view)) 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): def __del__(self):
logger.debug(f"dropping buffer {self.id}") logger.debug(f"dropping buffer {self.id}")
self.view.close()
self.handle.clear_callback() 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): def __hash__(self):
return hash(self.id) return hash(self.id)
@ -103,17 +113,26 @@ class BufferManager():
def sync(self, text_listener): def sync(self, text_listener):
promise = self.handle.content() promise = self.handle.content()
def _(): def _():
current_contents = get_contents(self.view) # current_contents = get_contents(self.view)
content = promise.wait() content = promise.wait()
if content == current_contents:
return
safe_listener_detach(text_listener) safe_listener_detach(text_listener)
populate_view(self.view, content) populate_view(self.view, content)
safe_listener_attach(text_listener, self.view.buffer()) safe_listener_attach(text_listener, self.view.buffer())
sublime.status_message("Syncd contents.") sublime.status_message("Syncd contents.")
sublime.set_timeout_async(_) 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(): class BufferRegistry():
def __init__(self): def __init__(self):
self._buffers: bidict[BufferManager, WorkspaceManager] = bidict() self._buffers: bidict[BufferManager, WorkspaceManager] = bidict()
@ -142,28 +161,32 @@ class BufferRegistry():
if not bfm: raise KeyError if not bfm: raise KeyError
return bfm return bfm
def register(self, bhandle: codemp.BufferController, wsm: WorkspaceManager): def register(self, buff: str, wsm: WorkspaceManager, localview: sublime.View | None = None):
bid = bhandle.path()
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() win = sublime.active_window()
newfileflags = sublime.NewFileFlags.TRANSIENT \ if not localview:
| sublime.NewFileFlags.ADD_TO_SELECTION \ newfileflags = sublime.NewFileFlags.TRANSIENT \
| sublime.NewFileFlags.FORCE_CLONE | sublime.NewFileFlags.ADD_TO_SELECTION \
view = win.new_file(newfileflags) | sublime.NewFileFlags.FORCE_CLONE
view = win.new_file(newfileflags)
view.set_scratch(True) view.set_scratch(True)
view.set_name(os.path.basename(bid)) view.set_name(os.path.basename(buff))
syntax = sublime.find_syntax_for_file(bid) syntax = sublime.find_syntax_for_file(buff)
if syntax: if syntax:
view.assign_syntax(syntax) view.assign_syntax(syntax)
else:
view.settings().set(g.CODEMP_VIEW_TAG, True) view = localview
view.settings().set(g.CODEMP_BUFFER_ID, bid)
view.set_status(g.SUBLIME_STATUS_ID, "[Codemp]")
tmpfile = "DISABLE" tmpfile = "DISABLE"
bfm = BufferManager(bhandle, view, tmpfile) bfm = BufferManager(buffctl, view, tmpfile, islocal = localview is not None)
self._buffers[bfm] = wsm self._buffers[bfm] = wsm
return bfm return bfm
@ -171,9 +194,16 @@ class BufferRegistry():
def remove(self, bf: BufferManager | str): def remove(self, bf: BufferManager | str):
if isinstance(bf, str): if isinstance(bf, str):
bf = self.lookupId(bf) bf = self.lookupId(bf)
ws = self.lookupParent(bf)
del self._buffers[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() buffers = BufferRegistry()

View file

@ -8,7 +8,6 @@ import sublime
import shutil import shutil
import tempfile import tempfile
import logging import logging
import gc
from codemp import Selection from codemp import Selection
from .. import globals as g from .. import globals as g
@ -107,7 +106,7 @@ class WorkspaceRegistry():
return True return True
def hasactive(self): 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]: def lookup(self, w: Optional[sublime.Window] = None) -> list[WorkspaceManager]:
if not w: if not w:
@ -117,7 +116,7 @@ class WorkspaceRegistry():
def lookupParent(self, ws: WorkspaceManager | str) -> sublime.Window: def lookupParent(self, ws: WorkspaceManager | str) -> sublime.Window:
if isinstance(ws, str): if isinstance(ws, str):
wsm = self.lookupId(ws) ws = self.lookupId(ws)
return self._workspaces[ws] return self._workspaces[ws]
def lookupId(self, wid: str) -> WorkspaceManager: def lookupId(self, wid: str) -> WorkspaceManager:
@ -125,25 +124,44 @@ class WorkspaceRegistry():
if not wsm: raise KeyError if not wsm: raise KeyError
return wsm return wsm
def register(self, wshandle: codemp.Workspace) -> WorkspaceManager: def register(self, wid: str) -> WorkspaceManager:
win = sublime.active_window() win = sublime.active_window()
# tmpdir = tempfile.mkdtemp(prefix="codemp_") # tmpdir = tempfile.mkdtemp(prefix="codemp_")
# add_project_folder(win, tmpdir, f"{g.WORKSPACE_FOLDER_PREFIX}{wshandle.id()}") # 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" tmpdir = "DISABLED"
wm = WorkspaceManager(wshandle, win, tmpdir) wm = WorkspaceManager(ws, win, tmpdir)
self._workspaces[wm] = win self._workspaces[wm] = win
return wm return wm
def remove(self, ws: WorkspaceManager | str): def remove(self, ws: WorkspaceManager | str):
if isinstance(ws, str): if isinstance(ws, str):
ws = self.lookupId(ws) ws = self.lookupId(ws)
# remove_project_folder(ws.window, f"{g.WORKSPACE_FOLDER_PREFIX}{ws.id}") logger.debug("removing all the buffers from the workspace")
# shutil.rmtree(ws.rootdir, ignore_errors=True) # 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] 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() workspaces = WorkspaceRegistry()