mirror of
https://github.com/hexedtech/codemp-sublime.git
synced 2024-12-04 20:04:52 +01:00
fix: miscelaneous fixes
Former-commit-id: f008552b7a5ca5f57a9802df6e7b1a09102afc74
This commit is contained in:
parent
127e103d03
commit
822dc9a045
5 changed files with 260 additions and 151 deletions
|
@ -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'
|
||||
}
|
||||
},
|
||||
]
|
72
plugin.py
72
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,7 +233,6 @@ 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()
|
||||
|
@ -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
|
||||
|
|
124
src/buffers.py
124
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)
|
||||
|
|
|
@ -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 []
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue