mirror of
https://github.com/hexedtech/codemp-sublime.git
synced 2024-11-25 00:04:49 +01:00
fix: miscelaneous fixes
Former-commit-id: f008552b7a5ca5f57a9802df6e7b1a09102afc74
This commit is contained in:
parent
296ef0ad36
commit
eb18401e91
5 changed files with 260 additions and 151 deletions
|
@ -19,6 +19,13 @@
|
||||||
"file": "${packages}/CodempClient/README.md"
|
"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
|
// # 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
|
||||||
|
@ -64,4 +71,20 @@
|
||||||
// 'buffer_id': 'test'
|
// '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):
|
class CodempClientViewEventListener(sublime_plugin.ViewEventListener):
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_applicable(cls, settings):
|
def is_applicable(cls, settings):
|
||||||
|
logger.debug(settings.get(g.CODEMP_BUFFER_TAG, False))
|
||||||
return settings.get(g.CODEMP_BUFFER_TAG, False)
|
return settings.get(g.CODEMP_BUFFER_TAG, False)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def applies_to_primary_view_only(cls):
|
def applies_to_primary_view_only(cls):
|
||||||
return False
|
return True
|
||||||
|
|
||||||
def on_selection_modified_async(self):
|
def on_selection_modified_async(self):
|
||||||
region = self.view.sel()[0]
|
region = self.view.sel()[0]
|
||||||
|
@ -97,15 +98,22 @@ class CodempClientViewEventListener(sublime_plugin.ViewEventListener):
|
||||||
vws = client.workspace_from_view(self.view)
|
vws = client.workspace_from_view(self.view)
|
||||||
vbuff = client.buffer_from_view(self.view)
|
vbuff = client.buffer_from_view(self.view)
|
||||||
if vws is None or vbuff is None:
|
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)
|
vws.send_cursor(vbuff.id, start, end)
|
||||||
|
|
||||||
def on_activated(self):
|
def on_activated(self):
|
||||||
global TEXT_LISTENER
|
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
|
safe_listener_attach(TEXT_LISTENER, self.view.buffer()) # pyright: ignore
|
||||||
|
|
||||||
def on_deactivated(self):
|
def on_deactivated(self):
|
||||||
global TEXT_LISTENER
|
global TEXT_LISTENER
|
||||||
|
vbuff = client.buffer_from_view(self.view)
|
||||||
|
logging.debug(f"'{vbuff.id}' view deactivated!")
|
||||||
safe_listener_detach(TEXT_LISTENER) # pyright: ignore
|
safe_listener_detach(TEXT_LISTENER) # pyright: ignore
|
||||||
|
|
||||||
def on_pre_close(self):
|
def on_pre_close(self):
|
||||||
|
@ -147,6 +155,7 @@ class CodempClientTextChangeListener(sublime_plugin.TextChangeListener):
|
||||||
vbuff = client.buffer_from_view(self.buffer.primary_view())
|
vbuff = client.buffer_from_view(self.buffer.primary_view())
|
||||||
if vbuff is not None:
|
if vbuff is not None:
|
||||||
# but then we block the main one for the actual sending!
|
# 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))
|
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.
|
# 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
|
# Client Commands
|
||||||
#############################################################################
|
#############################################################################
|
||||||
# Connect Command
|
# Connect Command
|
||||||
|
@ -195,7 +212,7 @@ class CodempConnectCommand(sublime_plugin.WindowCommand):
|
||||||
def input(self, args):
|
def input(self, args):
|
||||||
if "server_host" not in args:
|
if "server_host" not in args:
|
||||||
return SimpleTextInput(
|
return SimpleTextInput(
|
||||||
("server_host", "http://127.0.0.1:50051"),
|
("server_host", "http://codemp.alemi.dev:50053"),
|
||||||
("user_name", f"user-{random.random()}"),
|
("user_name", f"user-{random.random()}"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -216,7 +233,6 @@ class CodempJoinWorkspaceCommand(sublime_plugin.WindowCommand):
|
||||||
|
|
||||||
def run(self, workspace_id):
|
def run(self, workspace_id):
|
||||||
assert client.codemp is not None
|
assert client.codemp is not None
|
||||||
if client.valid_workspace(workspace_id):
|
|
||||||
logger.info(f"Joining workspace: '{workspace_id}'...")
|
logger.info(f"Joining workspace: '{workspace_id}'...")
|
||||||
promise = client.codemp.join_workspace(workspace_id)
|
promise = client.codemp.join_workspace(workspace_id)
|
||||||
active_window = sublime.active_window()
|
active_window = sublime.active_window()
|
||||||
|
@ -241,7 +257,7 @@ class CodempJoinWorkspaceCommand(sublime_plugin.WindowCommand):
|
||||||
|
|
||||||
def input(self, args):
|
def input(self, args):
|
||||||
if "workspace_id" not in 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
|
# 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
|
return client.codemp is not None and len(client.all_workspaces(self.window)) > 0
|
||||||
|
|
||||||
def run(self, workspace_id: str):
|
def run(self, workspace_id: str):
|
||||||
# client.leave_workspace(id)
|
assert client.codemp is not None
|
||||||
pass
|
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):
|
def input(self, args):
|
||||||
if "id" not in args:
|
if "id" not in args:
|
||||||
|
@ -310,8 +329,10 @@ class CodempJoinBufferCommand(sublime_plugin.WindowCommand):
|
||||||
# if not existing ask for creation (create + attach)
|
# if not existing ask for creation (create + attach)
|
||||||
vws = client.workspace_from_id(workspace_id)
|
vws = client.workspace_from_id(workspace_id)
|
||||||
assert vws is not None
|
assert vws is not None
|
||||||
|
|
||||||
# is the buffer already installed?
|
# is the buffer already installed?
|
||||||
if vws.valid_buffer(buffer_id):
|
if vws.valid_buffer(buffer_id):
|
||||||
|
logger.debug("buffer already installed!")
|
||||||
return # do nothing.
|
return # do nothing.
|
||||||
|
|
||||||
if buffer_id not in vws.codemp.filetree(filter=buffer_id):
|
if buffer_id not in vws.codemp.filetree(filter=buffer_id):
|
||||||
|
@ -330,17 +351,22 @@ class CodempJoinBufferCommand(sublime_plugin.WindowCommand):
|
||||||
create_promise.wait()
|
create_promise.wait()
|
||||||
|
|
||||||
# now we can defer the attaching process
|
# now we can defer the attaching process
|
||||||
|
logger.debug(f"attempting to attach to {buffer_id}...")
|
||||||
promise = vws.codemp.attach(buffer_id)
|
promise = vws.codemp.attach(buffer_id)
|
||||||
|
|
||||||
def deferred_attach(promise):
|
def deferred_attach(promise):
|
||||||
try:
|
try:
|
||||||
buff_ctl = promise.wait()
|
buff_ctl = promise.wait()
|
||||||
|
logger.debug("attach successfull!")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"error when attaching to buffer '{id}':\n\n {e}")
|
logging.error(f"error when attaching to buffer '{id}':\n\n {e}")
|
||||||
sublime.error_message(f"Could not attach to buffer '{buffer_id}'")
|
sublime.error_message(f"Could not attach to buffer '{buffer_id}'")
|
||||||
return
|
return
|
||||||
vbuff = vws.install_buffer(buff_ctl)
|
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)
|
self.window.focus_view(vbuff.view)
|
||||||
|
|
||||||
sublime.set_timeout_async(lambda: deferred_attach(promise))
|
sublime.set_timeout_async(lambda: deferred_attach(promise))
|
||||||
|
@ -350,7 +376,7 @@ class CodempJoinBufferCommand(sublime_plugin.WindowCommand):
|
||||||
|
|
||||||
def input(self, args):
|
def input(self, args):
|
||||||
if "workspace_id" not in 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:
|
if "buffer_id" not in args:
|
||||||
return BufferIdList(args["workspace_id"])
|
return BufferIdList(args["workspace_id"])
|
||||||
|
@ -373,6 +399,7 @@ class CodempLeaveBufferCommand(sublime_plugin.WindowCommand):
|
||||||
def defer_detach():
|
def defer_detach():
|
||||||
if vws.codemp.detach(buffer_id):
|
if vws.codemp.detach(buffer_id):
|
||||||
vws.uninstall_buffer(vbuff)
|
vws.uninstall_buffer(vbuff)
|
||||||
|
client.unregister_buffer(vbuff)
|
||||||
|
|
||||||
sublime.set_timeout_async(defer_detach)
|
sublime.set_timeout_async(defer_detach)
|
||||||
|
|
||||||
|
@ -381,7 +408,7 @@ class CodempLeaveBufferCommand(sublime_plugin.WindowCommand):
|
||||||
|
|
||||||
def input(self, args):
|
def input(self, args):
|
||||||
if "workspace_id" not in 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:
|
if "buffer_id" not in args:
|
||||||
return BufferIdList(args["workspace_id"])
|
return BufferIdList(args["workspace_id"])
|
||||||
|
@ -413,10 +440,12 @@ class CodempCreateBufferCommand(sublime_plugin.WindowCommand):
|
||||||
|
|
||||||
def input(self, args):
|
def input(self, args):
|
||||||
if "workspace_id" not in 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:
|
if "buffer_id" not in args:
|
||||||
return SimpleTextInput(("buffer_id", "new buffer"))
|
return SimpleTextInput(
|
||||||
|
(("buffer_id", "new buffer")),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CodempDeleteBufferCommand(sublime_plugin.WindowCommand):
|
class CodempDeleteBufferCommand(sublime_plugin.WindowCommand):
|
||||||
|
@ -475,7 +504,7 @@ class CodempDeleteBufferCommand(sublime_plugin.WindowCommand):
|
||||||
|
|
||||||
def input(self, args):
|
def input(self, args):
|
||||||
if "workspace_id" not in 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:
|
if "buffer_id" not in args:
|
||||||
return BufferIdList(args["workspace_id"])
|
return BufferIdList(args["workspace_id"])
|
||||||
|
@ -494,7 +523,7 @@ class CodempReplaceTextCommand(sublime_plugin.TextCommand):
|
||||||
############################################################
|
############################################################
|
||||||
class SimpleTextInput(sublime_plugin.TextInputHandler):
|
class SimpleTextInput(sublime_plugin.TextInputHandler):
|
||||||
def __init__(self, *args: Tuple[str, str]):
|
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.argname = args[0][0]
|
||||||
self.default = args[0][1]
|
self.default = args[0][1]
|
||||||
self.next_inputs = args[1:]
|
self.next_inputs = args[1:]
|
||||||
|
@ -506,15 +535,19 @@ class SimpleTextInput(sublime_plugin.TextInputHandler):
|
||||||
return self.argname
|
return self.argname
|
||||||
|
|
||||||
def next_input(self, args):
|
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 len(self.next_inputs) > 0:
|
||||||
if self.next_inputs[0][0] not in args:
|
if self.next_inputs[0][0] not in args:
|
||||||
return SimpleTextInput(*self.next_inputs)
|
return SimpleTextInput(*self.next_inputs)
|
||||||
|
|
||||||
|
|
||||||
class ActiveWorkspacesIdList(sublime_plugin.ListInputHandler):
|
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.window = window
|
||||||
self.get_buffer = get_buffer
|
self.buffer_list = buffer_list
|
||||||
|
self.buffer_text = buffer_text
|
||||||
|
|
||||||
def name(self):
|
def name(self):
|
||||||
return "workspace_id"
|
return "workspace_id"
|
||||||
|
@ -523,14 +556,17 @@ class ActiveWorkspacesIdList(sublime_plugin.ListInputHandler):
|
||||||
return [vws.id for vws in client.all_workspaces(self.window)]
|
return [vws.id for vws in client.all_workspaces(self.window)]
|
||||||
|
|
||||||
def next_input(self, args):
|
def next_input(self, args):
|
||||||
if self.get_buffer:
|
if self.buffer_list:
|
||||||
return BufferIdList(args["workspace_id"])
|
return BufferIdList(args["workspace_id"])
|
||||||
|
elif self.buffer_text:
|
||||||
|
return SimpleTextInput(("buffer_id", "new buffer"))
|
||||||
|
|
||||||
|
|
||||||
class BufferIdList(sublime_plugin.ListInputHandler):
|
class BufferIdList(sublime_plugin.ListInputHandler):
|
||||||
def __init__(self, workspace_id):
|
def __init__(self, workspace_id):
|
||||||
|
vws = client.workspace_from_id(workspace_id)
|
||||||
self.add_entry_text = "* create new..."
|
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.sort()
|
||||||
self.list.append(self.add_entry_text)
|
self.list.append(self.add_entry_text)
|
||||||
self.preselected = None
|
self.preselected = None
|
||||||
|
|
124
src/buffers.py
124
src/buffers.py
|
@ -1,13 +1,53 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import sublime
|
import sublime
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import codemp
|
import codemp
|
||||||
from Codemp.src import globals as g
|
from Codemp.src import globals as g
|
||||||
|
from Codemp.src.utils import populate_view
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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
|
# This class is used as an abstraction between the local buffers (sublime side) and the
|
||||||
# remote buffers (codemp side), to handle the syncronicity.
|
# remote buffers (codemp side), to handle the syncronicity.
|
||||||
# This class is mainly manipulated by a VirtualWorkspace, that manages its buffers
|
# This class is mainly manipulated by a VirtualWorkspace, that manages its buffers
|
||||||
|
@ -25,31 +65,21 @@ class VirtualBuffer:
|
||||||
def __hash__(self) -> int:
|
def __hash__(self) -> int:
|
||||||
return hash(self.id)
|
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):
|
def cleanup(self):
|
||||||
self.uninstall()
|
self.uninstall()
|
||||||
self.buffctl.stop()
|
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):
|
def uninstall(self):
|
||||||
if not self.installed:
|
if not getattr(self, "installed", False):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.__deactivate()
|
self.__deactivate()
|
||||||
|
@ -62,9 +92,29 @@ class VirtualBuffer:
|
||||||
|
|
||||||
self.installed = False
|
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):
|
def __activate(self):
|
||||||
logger.info(f"registering a callback for buffer: {self.id}")
|
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
|
self.isactive = True
|
||||||
|
|
||||||
def __deactivate(self):
|
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
|
# 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()
|
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)
|
self.driver = codemp.init(lambda msg: logger.log(logger.level, msg), False)
|
||||||
|
|
||||||
# bookkeeping corner
|
# bookkeeping corner
|
||||||
self.__id2buffer: dict[str, VirtualBuffer] = {}
|
self._id2buffer: dict[str, VirtualBuffer] = {}
|
||||||
self.__id2workspace: dict[str, VirtualWorkspace] = {}
|
self._id2workspace: dict[str, VirtualWorkspace] = {}
|
||||||
self.__view2buff: dict[sublime.View, VirtualBuffer] = {}
|
self._view2buff: dict[sublime.View, VirtualBuffer] = {}
|
||||||
|
|
||||||
self.__buff2workspace: bidict[VirtualBuffer, VirtualWorkspace] = bidict()
|
self._buff2workspace: bidict[VirtualBuffer, VirtualWorkspace] = bidict()
|
||||||
self.__workspace2window: bidict[VirtualWorkspace, sublime.Window] = 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):
|
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):
|
def valid_workspace(self, workspace: VirtualWorkspace | str):
|
||||||
if isinstance(workspace, 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(
|
def all_workspaces(
|
||||||
self, window: Optional[sublime.Window] = None
|
self, window: Optional[sublime.Window] = None
|
||||||
) -> list[VirtualWorkspace]:
|
) -> list[VirtualWorkspace]:
|
||||||
if window is None:
|
if window is None:
|
||||||
return list(self.__workspace2window.keys())
|
return list(self._workspace2window.keys())
|
||||||
else:
|
else:
|
||||||
return self.__workspace2window.inverse.get(window, [])
|
return self._workspace2window.inverse.get(window, [])
|
||||||
|
|
||||||
def workspace_from_view(self, view: sublime.View) -> Optional[VirtualWorkspace]:
|
def workspace_from_view(self, view: sublime.View) -> Optional[VirtualWorkspace]:
|
||||||
buff = self.__view2buff.get(view, None)
|
buff = self._view2buff.get(view, None)
|
||||||
return self.__buff2workspace.get(buff, None)
|
return self._buff2workspace.get(buff, None)
|
||||||
|
|
||||||
def workspace_from_buffer(self, buff: VirtualBuffer) -> Optional[VirtualWorkspace]:
|
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]:
|
def workspace_from_id(self, id: str) -> Optional[VirtualWorkspace]:
|
||||||
return self.__id2workspace.get(id)
|
return self._id2workspace.get(id)
|
||||||
|
|
||||||
def all_buffers(
|
def all_buffers(
|
||||||
self, workspace: Optional[VirtualWorkspace | str] = None
|
self, workspace: Optional[VirtualWorkspace | str] = None
|
||||||
) -> list[VirtualBuffer]:
|
) -> list[VirtualBuffer]:
|
||||||
if workspace is None:
|
if workspace is None:
|
||||||
return list(self.__buff2workspace.keys())
|
return list(self._buff2workspace.keys())
|
||||||
else:
|
else:
|
||||||
if isinstance(workspace, str):
|
if isinstance(workspace, str):
|
||||||
workspace = client.__id2workspace[workspace]
|
workspace = client._id2workspace[workspace]
|
||||||
return self.__buff2workspace.inverse.get(workspace, [])
|
return self._buff2workspace.inverse.get(workspace, [])
|
||||||
|
|
||||||
def buffer_from_view(self, view: sublime.View) -> Optional[VirtualBuffer]:
|
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]:
|
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:
|
def view_from_buffer(self, buff: VirtualBuffer) -> sublime.View:
|
||||||
return buff.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):
|
def disconnect(self):
|
||||||
if self.codemp is None:
|
if self.codemp is None:
|
||||||
return
|
return
|
||||||
|
@ -90,11 +112,11 @@ class VirtualClient:
|
||||||
vws.cleanup()
|
vws.cleanup()
|
||||||
self.codemp.leave_workspace(vws.id)
|
self.codemp.leave_workspace(vws.id)
|
||||||
|
|
||||||
self.__id2workspace.clear()
|
self._id2workspace.clear()
|
||||||
self.__id2buffer.clear()
|
self._id2buffer.clear()
|
||||||
self.__buff2workspace.clear()
|
self._buff2workspace.clear()
|
||||||
self.__view2buff.clear()
|
self._view2buff.clear()
|
||||||
self.__workspace2window.clear()
|
self._workspace2window.clear()
|
||||||
self.codemp = None
|
self.codemp = None
|
||||||
|
|
||||||
def connect(self, host: str, user: str, password: str):
|
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
|
# we pass the window as well so if the window changes in the mean
|
||||||
# time we have the correct one!
|
# time we have the correct one!
|
||||||
vws = VirtualWorkspace(workspace, window)
|
vws = VirtualWorkspace(workspace, window)
|
||||||
self.__workspace2window[vws] = window
|
self._workspace2window[vws] = window
|
||||||
self.__id2workspace[vws.id] = vws
|
self._id2workspace[vws.id] = vws
|
||||||
|
|
||||||
vws.install()
|
vws.install()
|
||||||
|
|
||||||
return vws
|
return vws
|
||||||
|
|
||||||
def uninstall_workspace(self, vws: VirtualWorkspace):
|
def uninstall_workspace(self, vws: VirtualWorkspace):
|
||||||
if vws not in self.__workspace2window:
|
if vws not in self._workspace2window:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
logger.info(f"Uninstalling workspace '{vws.id}'...")
|
logger.info(f"Uninstalling workspace '{vws.id}'...")
|
||||||
vws.cleanup()
|
vws.cleanup()
|
||||||
del self.__id2workspace[vws.id]
|
del self._workspace2window[vws]
|
||||||
del self.__workspace2window[vws]
|
del self._id2workspace[vws.id]
|
||||||
self.__buff2workspace.inverse_del(vws)
|
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):
|
def workspaces_in_server(self):
|
||||||
return self.codemp.active_workspaces() if self.codemp else []
|
return self.codemp.active_workspaces() if self.codemp else []
|
||||||
|
|
|
@ -16,6 +16,28 @@ from Codemp.src.utils import bidict
|
||||||
logger = logging.getLogger(__name__)
|
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
|
# A virtual workspace is a bridge class that aims to translate
|
||||||
# events that happen to the codemp workspaces into sublime actions
|
# events that happen to the codemp workspaces into sublime actions
|
||||||
class VirtualWorkspace:
|
class VirtualWorkspace:
|
||||||
|
@ -30,8 +52,8 @@ class VirtualWorkspace:
|
||||||
self.codemp.fetch_users()
|
self.codemp.fetch_users()
|
||||||
|
|
||||||
# mapping remote ids -> local ids
|
# mapping remote ids -> local ids
|
||||||
self.__buff2view: bidict[VirtualBuffer, sublime.View] = bidict()
|
self._buff2view: bidict[VirtualBuffer, sublime.View] = bidict()
|
||||||
self.__id2buff: dict[str, VirtualBuffer] = {}
|
self._id2buff: dict[str, VirtualBuffer] = {}
|
||||||
# self.id_map: dict[str, int] = {}
|
# self.id_map: dict[str, int] = {}
|
||||||
# self.active_buffers: dict[int, VirtualBuffer] = {} # local_id -> VBuff
|
# self.active_buffers: dict[int, VirtualBuffer] = {} # local_id -> VBuff
|
||||||
|
|
||||||
|
@ -44,27 +66,27 @@ class VirtualWorkspace:
|
||||||
# if not get up to speed!
|
# if not get up to speed!
|
||||||
self.codemp.fetch_buffers().wait()
|
self.codemp.fetch_buffers().wait()
|
||||||
attached_buffers = self.codemp.buffer_list()
|
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!
|
# TODO!
|
||||||
|
|
||||||
def valid_buffer(self, buff: VirtualBuffer | str):
|
def valid_buffer(self, buff: VirtualBuffer | str):
|
||||||
if isinstance(buff, str):
|
if isinstance(buff, str):
|
||||||
return self.buff_by_id(buff) is not None
|
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]:
|
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]:
|
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
|
return buff[0] if buff is not None else None
|
||||||
|
|
||||||
def buff_by_id(self, id: str) -> Optional[VirtualBuffer]:
|
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]:
|
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:
|
def view_by_buffer(self, buffer: VirtualBuffer) -> sublime.View:
|
||||||
return buffer.view
|
return buffer.view
|
||||||
|
@ -76,14 +98,14 @@ class VirtualWorkspace:
|
||||||
for view in self.all_views():
|
for view in self.all_views():
|
||||||
view.close()
|
view.close()
|
||||||
|
|
||||||
self.__buff2view.clear()
|
|
||||||
self.__id2buff.clear()
|
|
||||||
|
|
||||||
self.uninstall()
|
self.uninstall()
|
||||||
self.curctl.stop()
|
self.curctl.stop()
|
||||||
|
|
||||||
|
self._buff2view.clear()
|
||||||
|
self._id2buff.clear()
|
||||||
|
|
||||||
def uninstall(self):
|
def uninstall(self):
|
||||||
if not self.installed:
|
if not getattr(self, "installed", False):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.__deactivate()
|
self.__deactivate()
|
||||||
|
@ -106,7 +128,7 @@ class VirtualWorkspace:
|
||||||
self.installed = False
|
self.installed = False
|
||||||
|
|
||||||
def install(self):
|
def install(self):
|
||||||
if self.installed:
|
if getattr(self, "installed", False):
|
||||||
return
|
return
|
||||||
|
|
||||||
# initialise the virtual filesystem
|
# initialise the virtual filesystem
|
||||||
|
@ -127,7 +149,7 @@ class VirtualWorkspace:
|
||||||
self.installed = True
|
self.installed = True
|
||||||
|
|
||||||
def __activate(self):
|
def __activate(self):
|
||||||
self.curctl.callback(self.__move_cursor_callback)
|
self.curctl.callback(make_cursor_callback(self))
|
||||||
self.isactive = True
|
self.isactive = True
|
||||||
|
|
||||||
def __deactivate(self):
|
def __deactivate(self):
|
||||||
|
@ -135,11 +157,13 @@ class VirtualWorkspace:
|
||||||
self.isactive = False
|
self.isactive = False
|
||||||
|
|
||||||
def install_buffer(self, buff: codemp.BufferController) -> VirtualBuffer:
|
def install_buffer(self, buff: codemp.BufferController) -> VirtualBuffer:
|
||||||
|
logger.debug(f"installing buffer {buff.name()}")
|
||||||
view = self.window.new_file()
|
view = self.window.new_file()
|
||||||
|
|
||||||
vbuff = VirtualBuffer(buff, view)
|
vbuff = VirtualBuffer(buff, view)
|
||||||
self.__buff2view[vbuff] = view
|
logger.debug("created virtual buffer")
|
||||||
self.__id2buff[vbuff.id] = vbuff
|
self._buff2view[vbuff] = view
|
||||||
|
self._id2buff[vbuff.id] = vbuff
|
||||||
|
|
||||||
vbuff.install(self.rootdir)
|
vbuff.install(self.rootdir)
|
||||||
|
|
||||||
|
@ -148,28 +172,11 @@ class VirtualWorkspace:
|
||||||
def uninstall_buffer(self, vbuff: VirtualBuffer):
|
def uninstall_buffer(self, vbuff: VirtualBuffer):
|
||||||
vbuff.cleanup()
|
vbuff.cleanup()
|
||||||
buffview = self.view_by_buffer(vbuff)
|
buffview = self.view_by_buffer(vbuff)
|
||||||
del self.__buff2view[vbuff]
|
del self._buff2view[vbuff]
|
||||||
del self.__id2buff[vbuff.id]
|
del self._id2buff[vbuff.id]
|
||||||
buffview.close()
|
buffview.close()
|
||||||
|
|
||||||
def send_cursor(self, id: str, start: Tuple[int, int], end: Tuple[int, int]):
|
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
|
# we can safely ignore the promise, we don't really care if everything
|
||||||
# is ok for now with the cursor.
|
# is ok for now with the cursor.
|
||||||
self.curctl.send(id, start, end)
|
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