mirror of
https://github.com/hexedtech/codemp-sublime.git
synced 2024-11-22 14:54:48 +01:00
wip: huge refactor with smarter and simpler struture
Former-commit-id: fbd0bca8094642dd8d2cf6c3f154af3b10dff95b
This commit is contained in:
parent
74705d94c0
commit
539a4e4e5e
15 changed files with 545 additions and 450 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,6 +1,6 @@
|
||||||
/target
|
/target
|
||||||
test*
|
test*
|
||||||
/lib
|
lib
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|
|
@ -1,159 +0,0 @@
|
||||||
import sublime_plugin
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from typing import Tuple, Union, List
|
|
||||||
|
|
||||||
from .src.client import client
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
# Input handlers
|
|
||||||
############################################################
|
|
||||||
class SimpleTextInput(sublime_plugin.TextInputHandler):
|
|
||||||
def __init__(self, *args: Tuple[str, Union[str, List[str]]]):
|
|
||||||
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:]
|
|
||||||
|
|
||||||
def initial_text(self):
|
|
||||||
if isinstance(self.default, str):
|
|
||||||
return self.default
|
|
||||||
else: return ""
|
|
||||||
|
|
||||||
def name(self):
|
|
||||||
return self.argname
|
|
||||||
|
|
||||||
def next_input(self, args):
|
|
||||||
if len(self.next_inputs) > 0:
|
|
||||||
if self.next_inputs[0][0] not in args:
|
|
||||||
if isinstance(self.next_inputs[0][1], list):
|
|
||||||
return SimpleListInput(*self.next_inputs)
|
|
||||||
else:
|
|
||||||
return SimpleTextInput(*self.next_inputs)
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleListInput(sublime_plugin.ListInputHandler):
|
|
||||||
def __init__(self, *args: Tuple[str, Union["list[str]", str]]):
|
|
||||||
self.argname = args[0][0]
|
|
||||||
self.list = args[0][1]
|
|
||||||
self.next_inputs = args[1:]
|
|
||||||
|
|
||||||
def name(self):
|
|
||||||
return self.argname
|
|
||||||
|
|
||||||
def list_items(self):
|
|
||||||
if isinstance(self.list, list):
|
|
||||||
return self.list
|
|
||||||
else:
|
|
||||||
return [self.list]
|
|
||||||
|
|
||||||
def next_input(self, args):
|
|
||||||
if len(self.next_inputs) > 0:
|
|
||||||
if self.next_inputs[0][0] not in args:
|
|
||||||
if isinstance(self.next_inputs[0][1], str):
|
|
||||||
return SimpleTextInput(*self.next_inputs)
|
|
||||||
else:
|
|
||||||
return SimpleListInput(*self.next_inputs)
|
|
||||||
|
|
||||||
|
|
||||||
class ActiveWorkspacesIdList(sublime_plugin.ListInputHandler):
|
|
||||||
def __init__(self, window=None, buffer_list=False, buffer_text=False):
|
|
||||||
self.window = window
|
|
||||||
self.buffer_list = buffer_list
|
|
||||||
self.buffer_text = buffer_text
|
|
||||||
|
|
||||||
def name(self):
|
|
||||||
return "workspace_id"
|
|
||||||
|
|
||||||
def list_items(self):
|
|
||||||
return [vws.id for vws in client.all_workspaces(self.window)]
|
|
||||||
|
|
||||||
def next_input(self, args):
|
|
||||||
if self.buffer_list:
|
|
||||||
return BufferIdList(args["workspace_id"])
|
|
||||||
elif self.buffer_text:
|
|
||||||
return SimpleTextInput(("buffer_id", "new buffer"))
|
|
||||||
|
|
||||||
|
|
||||||
# To allow for having a selection and choosing non existing workspaces
|
|
||||||
# we do a little dance: We pass this list input handler to a TextInputHandler
|
|
||||||
# when we select "Create New..." which adds his result to the list of possible
|
|
||||||
# workspaces and pop itself off the stack to go back to the list handler.
|
|
||||||
class WorkspaceIdList(sublime_plugin.ListInputHandler):
|
|
||||||
def __init__(self):
|
|
||||||
assert client.codemp is not None # the command should not be available
|
|
||||||
|
|
||||||
# at the moment, the client can't give us a full list of existing workspaces
|
|
||||||
# so a textinputhandler would be more appropriate. but we keep this for the future
|
|
||||||
|
|
||||||
self.add_entry_text = "* add entry..."
|
|
||||||
self.list = client.codemp.list_workspaces(True, True).wait()
|
|
||||||
self.list.sort()
|
|
||||||
self.list.append(self.add_entry_text)
|
|
||||||
self.preselected = None
|
|
||||||
|
|
||||||
def name(self):
|
|
||||||
return "workspace_id"
|
|
||||||
|
|
||||||
def placeholder(self):
|
|
||||||
return "Workspace"
|
|
||||||
|
|
||||||
def list_items(self):
|
|
||||||
if self.preselected is not None:
|
|
||||||
return (self.list, self.preselected)
|
|
||||||
else:
|
|
||||||
return self.list
|
|
||||||
|
|
||||||
def next_input(self, args):
|
|
||||||
if args["workspace_id"] == self.add_entry_text:
|
|
||||||
return AddListEntry(self)
|
|
||||||
|
|
||||||
|
|
||||||
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 = vws.codemp.filetree(None)
|
|
||||||
self.list.sort()
|
|
||||||
self.list.append(self.add_entry_text)
|
|
||||||
self.preselected = None
|
|
||||||
|
|
||||||
def name(self):
|
|
||||||
return "buffer_id"
|
|
||||||
|
|
||||||
def placeholder(self):
|
|
||||||
return "Buffer Id"
|
|
||||||
|
|
||||||
def list_items(self):
|
|
||||||
if self.preselected is not None:
|
|
||||||
return (self.list, self.preselected)
|
|
||||||
else:
|
|
||||||
return self.list
|
|
||||||
|
|
||||||
def next_input(self, args):
|
|
||||||
if args["buffer_id"] == self.add_entry_text:
|
|
||||||
return AddListEntry(self)
|
|
||||||
|
|
||||||
|
|
||||||
class AddListEntry(sublime_plugin.TextInputHandler):
|
|
||||||
# this class works when the list input handler
|
|
||||||
# added appended a new element to it's list that will need to be
|
|
||||||
# replaced with the entry added from here!
|
|
||||||
def __init__(self, list_input_handler):
|
|
||||||
self.parent = list_input_handler
|
|
||||||
|
|
||||||
def name(self):
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def validate(self, text: str) -> bool:
|
|
||||||
return not len(text) == 0
|
|
||||||
|
|
||||||
def confirm(self, text: str):
|
|
||||||
self.parent.list.pop() # removes the add_entry_text
|
|
||||||
self.parent.list.insert(0, text)
|
|
||||||
self.parent.preselected = 0
|
|
||||||
|
|
||||||
def next_input(self, args):
|
|
||||||
return sublime_plugin.BackInputHandler()
|
|
|
@ -1,25 +1,76 @@
|
||||||
|
# pyright: reportIncompatibleMethodOverride=false
|
||||||
import sublime
|
import sublime
|
||||||
import sublime_plugin
|
import sublime_plugin
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .src.client import client
|
from lib import codemp
|
||||||
from .src.utils import safe_listener_attach
|
from .plugin.utils import safe_listener_detach
|
||||||
from .src.utils import safe_listener_detach
|
from .plugin.core.session import session
|
||||||
from .src import globals as g
|
from .plugin.core.registry import workspaces
|
||||||
|
from .plugin.core.registry import buffers
|
||||||
|
|
||||||
|
from .plugin.commands.client import CodempConnectCommand
|
||||||
|
from .plugin.commands.client import CodempDisconnectCommand
|
||||||
|
from .plugin.commands.client import CodempCreateWorkspaceCommand
|
||||||
|
from .plugin.commands.client import CodempDeleteWorkspaceCommand
|
||||||
|
from .plugin.commands.client import CodempJoinWorkspaceCommand
|
||||||
|
from .plugin.commands.client import CodempLeaveWorkspaceCommand
|
||||||
|
from .plugin.commands.client import CodempInviteToWorkspaceCommand
|
||||||
|
|
||||||
|
from .plugin.commands.workspace import CodempCreateBufferCommand
|
||||||
|
from .plugin.commands.workspace import CodempDeleteBufferCommand
|
||||||
|
from .plugin.commands.workspace import CodempJoinBufferCommand
|
||||||
|
from .plugin.commands.workspace import CodempLeaveBufferCommand
|
||||||
|
|
||||||
|
LOG_LEVEL = logging.DEBUG
|
||||||
|
handler = logging.StreamHandler()
|
||||||
|
handler.setFormatter(
|
||||||
|
logging.Formatter(
|
||||||
|
fmt="<{thread}/{threadName}> {levelname} [{name} :: {funcName}] {message}",
|
||||||
|
style="{",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
package_logger = logging.getLogger(__package__)
|
||||||
|
package_logger.setLevel(LOG_LEVEL)
|
||||||
|
package_logger.propagate = False
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Initialisation and Deinitialisation
|
||||||
# Listeners
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
def plugin_loaded():
|
||||||
|
package_logger.addHandler(handler)
|
||||||
|
logger.debug("plugin loaded")
|
||||||
|
|
||||||
|
def plugin_unloaded():
|
||||||
|
logger.debug("unloading")
|
||||||
|
safe_listener_detach(TEXT_LISTENER)
|
||||||
|
package_logger.removeHandler(handler)
|
||||||
|
|
||||||
|
|
||||||
|
def kill_all():
|
||||||
|
for ws in workspaces.lookup():
|
||||||
|
session.client.leave_workspace(ws.id)
|
||||||
|
workspaces.remove(ws)
|
||||||
|
|
||||||
|
session.stop()
|
||||||
|
|
||||||
|
|
||||||
|
class CodempReplaceTextCommand(sublime_plugin.TextCommand):
|
||||||
|
def run(self, edit, start, end, content, change_id):
|
||||||
|
# we modify the region to account for any change that happened in the mean time
|
||||||
|
region = self.view.transform_region_from(sublime.Region(start, end), change_id)
|
||||||
|
self.view.replace(edit, region, content)
|
||||||
|
|
||||||
|
|
||||||
class EventListener(sublime_plugin.EventListener):
|
class EventListener(sublime_plugin.EventListener):
|
||||||
def is_enabled(self):
|
def is_enabled(self):
|
||||||
return client.codemp is not None
|
return session.is_active()
|
||||||
|
|
||||||
def on_exit(self):
|
def on_exit(self):
|
||||||
client.disconnect()
|
kill_all()
|
||||||
if client.driver is not None:
|
# client.disconnect()
|
||||||
client.driver.stop()
|
# if client.driver is not None:
|
||||||
|
# client.driver.stop()
|
||||||
|
|
||||||
def on_pre_close_window(self, window):
|
def on_pre_close_window(self, window):
|
||||||
assert client.codemp is not None
|
assert client.codemp is not None
|
||||||
|
@ -113,5 +164,23 @@ class CodempClientTextChangeListener(sublime_plugin.TextChangeListener):
|
||||||
logger.debug(f"local buffer change! {vbuff.id}")
|
logger.debug(f"local buffer change! {vbuff.id}")
|
||||||
vbuff.send_buffer_change(changes)
|
vbuff.send_buffer_change(changes)
|
||||||
|
|
||||||
|
|
||||||
TEXT_LISTENER = CodempClientTextChangeListener()
|
TEXT_LISTENER = CodempClientTextChangeListener()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Proxy Commands ( NOT USED, left just in case we need it again. )
|
||||||
|
#############################################################################
|
||||||
|
# class ProxyCodempShareCommand(sublime_plugin.WindowCommand):
|
||||||
|
# # on_window_command, does not trigger when called from the command palette
|
||||||
|
# # See: https://github.com/sublimehq/sublime_text/issues/2234
|
||||||
|
# def run(self, **kwargs):
|
||||||
|
# self.window.run_command("codemp_share", kwargs)
|
||||||
|
#
|
||||||
|
# def input(self, args):
|
||||||
|
# if 'sublime_buffer' not in args:
|
||||||
|
# return SublimeBufferPathInputHandler()
|
||||||
|
#
|
||||||
|
# def input_description(self):
|
||||||
|
# return 'Share Buffer:'
|
72
plugin.py
72
plugin.py
|
@ -1,72 +0,0 @@
|
||||||
# pyright: reportIncompatibleMethodOverride=false
|
|
||||||
import sublime
|
|
||||||
import sublime_plugin
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from .src.utils import safe_listener_detach
|
|
||||||
from listeners import TEXT_LISTENER
|
|
||||||
|
|
||||||
from client_commands import CodempConnectCommand
|
|
||||||
from client_commands import CodempDisconnectCommand
|
|
||||||
from client_commands import CodempCreateWorkspaceCommand
|
|
||||||
from client_commands import CodempDeleteWorkspaceCommand
|
|
||||||
from client_commands import CodempJoinWorkspaceCommand
|
|
||||||
from client_commands import CodempLeaveWorkspaceCommand
|
|
||||||
from client_commands import CodempInviteToWorkspaceCommand
|
|
||||||
|
|
||||||
from workspace_commands import CodempCreateBufferCommand
|
|
||||||
from workspace_commands import CodempDeleteBufferCommand
|
|
||||||
from workspace_commands import CodempJoinBufferCommand
|
|
||||||
from workspace_commands import CodempLeaveBufferCommand
|
|
||||||
|
|
||||||
LOG_LEVEL = logging.DEBUG
|
|
||||||
handler = logging.StreamHandler()
|
|
||||||
handler.setFormatter(
|
|
||||||
logging.Formatter(
|
|
||||||
fmt="<{thread}/{threadName}> {levelname} [{name} :: {funcName}] {message}",
|
|
||||||
style="{",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
package_logger = logging.getLogger(__package__)
|
|
||||||
package_logger.addHandler(handler)
|
|
||||||
package_logger.setLevel(LOG_LEVEL)
|
|
||||||
package_logger.propagate = False
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
# Initialisation and Deinitialisation
|
|
||||||
##############################################################################
|
|
||||||
def plugin_loaded():
|
|
||||||
logger.debug("plugin loaded")
|
|
||||||
|
|
||||||
|
|
||||||
def plugin_unloaded():
|
|
||||||
logger.debug("unloading")
|
|
||||||
safe_listener_detach(TEXT_LISTENER)
|
|
||||||
package_logger.removeHandler(handler)
|
|
||||||
# client.disconnect()
|
|
||||||
|
|
||||||
|
|
||||||
# Text Change Command
|
|
||||||
#############################################################################
|
|
||||||
class CodempReplaceTextCommand(sublime_plugin.TextCommand):
|
|
||||||
def run(self, edit, start, end, content, change_id):
|
|
||||||
# we modify the region to account for any change that happened in the mean time
|
|
||||||
region = self.view.transform_region_from(sublime.Region(start, end), change_id)
|
|
||||||
self.view.replace(edit, region, content)
|
|
||||||
|
|
||||||
|
|
||||||
# Proxy Commands ( NOT USED, left just in case we need it again. )
|
|
||||||
#############################################################################
|
|
||||||
# class ProxyCodempShareCommand(sublime_plugin.WindowCommand):
|
|
||||||
# # on_window_command, does not trigger when called from the command palette
|
|
||||||
# # See: https://github.com/sublimehq/sublime_text/issues/2234
|
|
||||||
# def run(self, **kwargs):
|
|
||||||
# self.window.run_command("codemp_share", kwargs)
|
|
||||||
#
|
|
||||||
# def input(self, args):
|
|
||||||
# if 'sublime_buffer' not in args:
|
|
||||||
# return SublimeBufferPathInputHandler()
|
|
||||||
#
|
|
||||||
# def input_description(self):
|
|
||||||
# return 'Share Buffer:'
|
|
|
@ -5,10 +5,12 @@ import sublime_plugin
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from .src.client import client
|
from ...lib import codemp
|
||||||
|
from ..core.session import session
|
||||||
|
from ..core.registry import workspaces
|
||||||
|
|
||||||
from input_handlers import SimpleTextInput
|
from input_handlers import SimpleTextInput
|
||||||
from input_handlers import SimpleListInput
|
from input_handlers import SimpleListInput
|
||||||
from input_handlers import ActiveWorkspacesIdList
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -17,19 +19,21 @@ logger = logging.getLogger(__name__)
|
||||||
# Connect Command
|
# Connect Command
|
||||||
class CodempConnectCommand(sublime_plugin.WindowCommand):
|
class CodempConnectCommand(sublime_plugin.WindowCommand):
|
||||||
def is_enabled(self) -> bool:
|
def is_enabled(self) -> bool:
|
||||||
return client.codemp is None
|
return True
|
||||||
|
|
||||||
def run(self, server_host, user_name, password): # pyright: ignore[reportIncompatibleMethodOverride]
|
def run(self, server_host, user_name, password): # pyright: ignore[reportIncompatibleMethodOverride]
|
||||||
logger.info(f"Connecting to {server_host} with user {user_name}...")
|
|
||||||
def _():
|
def _():
|
||||||
try:
|
try:
|
||||||
client.connect(server_host, user_name, password)
|
config = codemp.get_default_config()
|
||||||
|
config.host = server_host
|
||||||
|
config.username = user_name
|
||||||
|
config.password = password
|
||||||
|
session.connect(config)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
sublime.error_message(
|
sublime.error_message(
|
||||||
"Could not connect:\n Make sure the server is up\n\
|
"Could not connect:\n Make sure the server is up\n\
|
||||||
and your credentials are correct."
|
and your credentials are correct."
|
||||||
)
|
)
|
||||||
|
|
||||||
sublime.set_timeout_async(_)
|
sublime.set_timeout_async(_)
|
||||||
|
|
||||||
def input_description(self):
|
def input_description(self):
|
||||||
|
@ -58,10 +62,13 @@ class CodempConnectCommand(sublime_plugin.WindowCommand):
|
||||||
# Disconnect Command
|
# Disconnect Command
|
||||||
class CodempDisconnectCommand(sublime_plugin.WindowCommand):
|
class CodempDisconnectCommand(sublime_plugin.WindowCommand):
|
||||||
def is_enabled(self):
|
def is_enabled(self):
|
||||||
return client.codemp is not None
|
return session.is_active()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
client.disconnect()
|
for ws in workspaces.lookup():
|
||||||
|
ws.uninstall()
|
||||||
|
|
||||||
|
session.disconnect()
|
||||||
|
|
||||||
|
|
||||||
# Join Workspace Command
|
# Join Workspace Command
|
||||||
|
@ -108,7 +115,8 @@ class CodempJoinWorkspaceCommand(sublime_plugin.WindowCommand):
|
||||||
# Leave Workspace Command
|
# Leave Workspace Command
|
||||||
class CodempLeaveWorkspaceCommand(sublime_plugin.WindowCommand):
|
class CodempLeaveWorkspaceCommand(sublime_plugin.WindowCommand):
|
||||||
def is_enabled(self):
|
def is_enabled(self):
|
||||||
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): # pyright: ignore[reportIncompatibleMethodOverride]
|
def run(self, workspace_id: str): # pyright: ignore[reportIncompatibleMethodOverride]
|
||||||
assert client.codemp is not None
|
assert client.codemp is not None
|
||||||
|
@ -176,7 +184,8 @@ class CodempDeleteWorkspaceCommand(sublime_plugin.WindowCommand):
|
||||||
return
|
return
|
||||||
if not client.codemp.leave_workspace(workspace_id):
|
if not client.codemp.leave_workspace(workspace_id):
|
||||||
logger.debug("error while leaving the workspace:")
|
logger.debug("error while leaving the workspace:")
|
||||||
return
|
raise RuntimeError("error while leaving the workspace")
|
||||||
|
|
||||||
client.uninstall_workspace(vws)
|
client.uninstall_workspace(vws)
|
||||||
|
|
||||||
client.codemp.delete_workspace(workspace_id)
|
client.codemp.delete_workspace(workspace_id)
|
|
@ -1,25 +1,17 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..workspace.workspace import VirtualWorkspace
|
||||||
|
|
||||||
import sublime
|
import sublime
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import codemp
|
from ..utils import bidict
|
||||||
from .workspace import VirtualWorkspace
|
|
||||||
from .buffers import VirtualBuffer
|
|
||||||
from .utils import bidict
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# the client will be responsible to keep track of everything!
|
|
||||||
# it will need 3 bidirectional dictionaries and 2 normal ones
|
|
||||||
# normal: workspace_id -> VirtualWorkspaces
|
|
||||||
# normal: buffer_id -> VirtualBuffer
|
|
||||||
# bidir: VirtualBuffer <-> VirtualWorkspace
|
|
||||||
# bidir: VirtualBuffer <-> Sublime.View
|
|
||||||
# bidir: VirtualWorkspace <-> Sublime.Window
|
|
||||||
|
|
||||||
|
|
||||||
class VirtualClient:
|
class VirtualClient:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -34,53 +26,6 @@ class VirtualClient:
|
||||||
self._buff2workspace: bidict[VirtualBuffer, VirtualWorkspace] = bidict()
|
self._buff2workspace: bidict[VirtualBuffer, VirtualWorkspace] = bidict()
|
||||||
self._workspace2window: dict[VirtualWorkspace, sublime.Window] = {}
|
self._workspace2window: dict[VirtualWorkspace, sublime.Window] = {}
|
||||||
|
|
||||||
def all_workspaces(
|
|
||||||
self, window: Optional[sublime.Window] = None
|
|
||||||
) -> list[VirtualWorkspace]:
|
|
||||||
if window is None:
|
|
||||||
return list(self._workspace2window.keys())
|
|
||||||
else:
|
|
||||||
return [
|
|
||||||
ws
|
|
||||||
for ws in self._workspace2window
|
|
||||||
if self._workspace2window[ws] == window
|
|
||||||
]
|
|
||||||
|
|
||||||
def workspace_from_view(self, view: sublime.View) -> Optional[VirtualWorkspace]:
|
|
||||||
buff = self._view2buff.get(view, None)
|
|
||||||
return self.workspace_from_buffer(buff) if buff is not None else None
|
|
||||||
|
|
||||||
def workspace_from_buffer(self, vbuff: VirtualBuffer) -> Optional[VirtualWorkspace]:
|
|
||||||
return self._buff2workspace.get(vbuff, None)
|
|
||||||
|
|
||||||
def workspace_from_id(self, id: str) -> Optional[VirtualWorkspace]:
|
|
||||||
return self._id2workspace.get(id)
|
|
||||||
|
|
||||||
def all_buffers(
|
|
||||||
self, workspace: Optional[VirtualWorkspace | str] = None
|
|
||||||
) -> list[VirtualBuffer]:
|
|
||||||
if workspace is None:
|
|
||||||
return list(self._id2buffer.values())
|
|
||||||
elif isinstance(workspace, str):
|
|
||||||
workspace = client._id2workspace[workspace]
|
|
||||||
return self._buff2workspace.inverse.get(workspace, [])
|
|
||||||
else:
|
|
||||||
return self._buff2workspace.inverse.get(workspace, [])
|
|
||||||
|
|
||||||
def buffer_from_view(self, view: sublime.View) -> Optional[VirtualBuffer]:
|
|
||||||
return self._view2buff.get(view)
|
|
||||||
|
|
||||||
def buffer_from_id(self, id: str) -> Optional[VirtualBuffer]:
|
|
||||||
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 disconnect(self):
|
def disconnect(self):
|
||||||
if self.codemp is None:
|
if self.codemp is None:
|
||||||
return
|
return
|
||||||
|
@ -141,11 +86,6 @@ class VirtualClient:
|
||||||
|
|
||||||
vws.uninstall()
|
vws.uninstall()
|
||||||
|
|
||||||
def unregister_buffer(self, buffer: VirtualBuffer):
|
|
||||||
del self._buff2workspace[buffer]
|
|
||||||
del self._id2buffer[buffer.id]
|
|
||||||
del self._view2buff[buffer.view]
|
|
||||||
|
|
||||||
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 []
|
||||||
|
|
5
plugin/core/registry.py
Normal file
5
plugin/core/registry.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from .workspace import WorkspaceRegistry
|
||||||
|
from .buffers import BufferRegistry
|
||||||
|
|
||||||
|
workspaces = WorkspaceRegistry()
|
||||||
|
buffers = BufferRegistry()
|
60
plugin/core/session.py
Normal file
60
plugin/core/session.py
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import logging
|
||||||
|
from ...lib import codemp
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class SessionManager():
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._running = False
|
||||||
|
self._driver = None
|
||||||
|
self._client = None
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
return self._driver is not None \
|
||||||
|
and self._running \
|
||||||
|
and self._client is not None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client(self):
|
||||||
|
return self._client
|
||||||
|
|
||||||
|
def get_or_init(self) -> codemp.Driver:
|
||||||
|
if self._driver:
|
||||||
|
return self._driver
|
||||||
|
|
||||||
|
self._driver = codemp.init()
|
||||||
|
logger.debug("registering logger callback...")
|
||||||
|
if not codemp.set_logger(lambda msg: logger.debug(msg), False):
|
||||||
|
logger.debug(
|
||||||
|
"could not register the logger... \
|
||||||
|
If reconnecting it's ok, \
|
||||||
|
the previous logger is still registered"
|
||||||
|
)
|
||||||
|
self._running = True
|
||||||
|
return self._driver
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
if not self._driver:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.disconnect()
|
||||||
|
self._driver.stop()
|
||||||
|
self._running = False
|
||||||
|
self._driver = None
|
||||||
|
|
||||||
|
def connect(self, config: codemp.Config) -> codemp.Client:
|
||||||
|
if not self._running:
|
||||||
|
self.get_or_init()
|
||||||
|
|
||||||
|
self._client = codemp.connect(config).wait()
|
||||||
|
logger.debug(f"Connected to '{config.host}' as user {self._client.user_name} (id: {self._client.user_id})")
|
||||||
|
return self._client
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
if not self._client:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._client = None
|
||||||
|
|
||||||
|
|
||||||
|
session = SessionManager()
|
221
plugin/core/workspace.py
Normal file
221
plugin/core/workspace.py
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ...main import CodempClientTextChangeListener
|
||||||
|
from ...lib import codemp
|
||||||
|
|
||||||
|
import sublime
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from .. import globals as g
|
||||||
|
from .buffers import VirtualBuffer
|
||||||
|
from ..utils import draw_cursor_region
|
||||||
|
from ..utils import bidict
|
||||||
|
from ..core.registry import buffers
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# def make_cursor_callback(workspace: VirtualWorkspace):
|
||||||
|
# def _callback(ctl: codemp.CursorController):
|
||||||
|
# def _():
|
||||||
|
# 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(
|
||||||
|
# f"{workspace.id} 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(_)
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
# def __init__(self, handle: codemp.Workspace, window: sublime.Window):
|
||||||
|
# self.handle: codemp.Workspace = handle
|
||||||
|
# self.window: sublime.Window = window
|
||||||
|
# self.curctl: codemp.CursorController = self.handle.cursor()
|
||||||
|
|
||||||
|
# self.id: str = self.handle.id()
|
||||||
|
|
||||||
|
# self.handle.fetch_buffers()
|
||||||
|
# self.handle.fetch_users()
|
||||||
|
|
||||||
|
# self._id2buff: dict[str, VirtualBuffer] = {}
|
||||||
|
|
||||||
|
# tmpdir = tempfile.mkdtemp(prefix="codemp_")
|
||||||
|
# self.rootdir = tmpdir
|
||||||
|
|
||||||
|
# proj: dict = self.window.project_data() # pyright: ignore
|
||||||
|
# if proj is None:
|
||||||
|
# proj = {"folders": []} # pyright: ignore, Value can be None
|
||||||
|
|
||||||
|
# proj["folders"].append(
|
||||||
|
# {"name": f"{g.WORKSPACE_FOLDER_PREFIX}{self.id}", "path": self.rootdir}
|
||||||
|
# )
|
||||||
|
# self.window.set_project_data(proj)
|
||||||
|
|
||||||
|
# self.curctl.callback(make_cursor_callback(self))
|
||||||
|
# self.isactive = True
|
||||||
|
|
||||||
|
# def __del__(self):
|
||||||
|
# logger.debug("workspace destroyed!")
|
||||||
|
|
||||||
|
# def __hash__(self) -> int:
|
||||||
|
# # so we can use these as dict keys!
|
||||||
|
# return hash(self.id)
|
||||||
|
|
||||||
|
# def uninstall(self):
|
||||||
|
# self.curctl.clear_callback()
|
||||||
|
# self.isactive = False
|
||||||
|
# self.curctl.stop()
|
||||||
|
|
||||||
|
# for vbuff in self._id2buff.values():
|
||||||
|
# vbuff.uninstall()
|
||||||
|
# if not self.handle.detach(vbuff.id):
|
||||||
|
# logger.warning(
|
||||||
|
# f"could not detach from '{vbuff.id}' for workspace '{self.id}'."
|
||||||
|
# )
|
||||||
|
# self._id2buff.clear()
|
||||||
|
|
||||||
|
# proj: dict = self.window.project_data() # type:ignore
|
||||||
|
# if proj is None:
|
||||||
|
# raise
|
||||||
|
|
||||||
|
# clean_proj_folders = list(
|
||||||
|
# filter(
|
||||||
|
# lambda f: f.get("name", "") != f"{g.WORKSPACE_FOLDER_PREFIX}{self.id}",
|
||||||
|
# proj["folders"],
|
||||||
|
# )
|
||||||
|
# )
|
||||||
|
# proj["folders"] = clean_proj_folders
|
||||||
|
# self.window.set_project_data(proj)
|
||||||
|
|
||||||
|
# logger.info(f"cleaning up virtual workspace '{self.id}'")
|
||||||
|
# shutil.rmtree(self.rootdir, ignore_errors=True)
|
||||||
|
|
||||||
|
# def install_buffer(
|
||||||
|
# self, buff: codemp.BufferController, listener: CodempClientTextChangeListener
|
||||||
|
# ) -> VirtualBuffer:
|
||||||
|
# logger.debug(f"installing buffer {buff.path()}")
|
||||||
|
|
||||||
|
# view = self.window.new_file()
|
||||||
|
# vbuff = VirtualBuffer(buff, view, self.rootdir)
|
||||||
|
# self._id2buff[vbuff.id] = vbuff
|
||||||
|
|
||||||
|
# vbuff.sync(listener)
|
||||||
|
|
||||||
|
# return vbuff
|
||||||
|
|
||||||
|
def add_project_folder(w: sublime.Window, folder: str, name: str = ""):
|
||||||
|
proj: dict = w.project_data() # pyright: ignore
|
||||||
|
if proj is None:
|
||||||
|
proj = {"folders": []} # pyright: ignore, Value can be None
|
||||||
|
|
||||||
|
if name == "":
|
||||||
|
entry = {"path": folder}
|
||||||
|
else:
|
||||||
|
entry = {"name": name, "path": folder}
|
||||||
|
|
||||||
|
proj["folders"].append(entry)
|
||||||
|
|
||||||
|
w.set_project_data(proj)
|
||||||
|
|
||||||
|
def remove_project_folder(w: sublime.Window, filterstr: str):
|
||||||
|
proj: dict = self.window.project_data() # type:ignore
|
||||||
|
if proj is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
clean_proj_folders = list(
|
||||||
|
filter(
|
||||||
|
lambda f: f.get("name", "") != filterstr,
|
||||||
|
proj["folders"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
proj["folders"] = clean_proj_folders
|
||||||
|
w.set_project_data(proj)
|
||||||
|
|
||||||
|
class WorkspaceManager():
|
||||||
|
def __init__(self, handle: codemp.Workspace, window: sublime.Window, rootdir: str) -> None:
|
||||||
|
self.handle: codemp.Workspace = handle
|
||||||
|
self.window: sublime.Window = window
|
||||||
|
self.curctl: codemp.CursorController = self.handle.cursor()
|
||||||
|
self.rootdir: str = rootdir
|
||||||
|
|
||||||
|
self.id = self.handle.id()
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.curctl.clear_callback()
|
||||||
|
self.curctl.stop()
|
||||||
|
|
||||||
|
# TODO: STUFF WITH THE BUFFERS IN THE REGISTRY
|
||||||
|
|
||||||
|
for buff in self.handle.buffer_list():
|
||||||
|
if not self.handle.detach(buff):
|
||||||
|
logger.warning(
|
||||||
|
f"could not detach from '{buff}' for workspace '{self.id}'."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspaceRegistry():
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._workspaces: bidict[WorkspaceManager, sublime.Window] = bidict()
|
||||||
|
|
||||||
|
def lookup(self, w: sublime.Window | None = None) -> list[WorkspaceManager]:
|
||||||
|
if not w:
|
||||||
|
return list(self._workspaces.keys())
|
||||||
|
ws = self._workspaces.inverse.get(w)
|
||||||
|
return ws if ws else []
|
||||||
|
|
||||||
|
def lookupId(self, wid: str) -> WorkspaceManager | None:
|
||||||
|
return next((ws for ws in self._workspaces if ws.id == wid), None)
|
||||||
|
|
||||||
|
def add(self, wshandle: codemp.Workspace):
|
||||||
|
win = sublime.active_window()
|
||||||
|
|
||||||
|
tmpdir = tempfile.mkdtemp(prefix="codemp_")
|
||||||
|
name = f"{g.WORKSPACE_FOLDER_PREFIX}{wshandle.id()}"
|
||||||
|
add_project_folder(win, tmpdir, name)
|
||||||
|
|
||||||
|
wm = WorkspaceManager(wshandle, win, tmpdir)
|
||||||
|
self._workspaces[wm] = win
|
||||||
|
|
||||||
|
def remove(self, ws: WorkspaceManager | str | None):
|
||||||
|
if isinstance(ws, str):
|
||||||
|
ws = self.lookupId(ws)
|
||||||
|
|
||||||
|
if not ws:
|
||||||
|
return
|
||||||
|
|
||||||
|
remove_project_folder(ws.window, f"{g.WORKSPACE_FOLDER_PREFIX}{ws.id}")
|
||||||
|
shutil.rmtree(ws.rootdir, ignore_errors=True)
|
||||||
|
del self._workspaces[ws]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
155
plugin/input_handlers.py
Normal file
155
plugin/input_handlers.py
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
import sublime_plugin
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from typing import Tuple, Union, List
|
||||||
|
|
||||||
|
# Input handlers
|
||||||
|
############################################################
|
||||||
|
class SimpleTextInput(sublime_plugin.TextInputHandler):
|
||||||
|
def __init__(self, *args: Tuple[str, Union[str, List[str]]]):
|
||||||
|
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:]
|
||||||
|
|
||||||
|
def initial_text(self):
|
||||||
|
if isinstance(self.default, str):
|
||||||
|
return self.default
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return self.argname
|
||||||
|
|
||||||
|
def next_input(self, args):
|
||||||
|
if len(self.next_inputs) > 0:
|
||||||
|
if self.next_inputs[0][0] not in args:
|
||||||
|
if isinstance(self.next_inputs[0][1], list):
|
||||||
|
return SimpleListInput(*self.next_inputs)
|
||||||
|
else:
|
||||||
|
return SimpleTextInput(*self.next_inputs)
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleListInput(sublime_plugin.ListInputHandler):
|
||||||
|
def __init__(self, *args: Tuple[str, Union["list[str]", str]]):
|
||||||
|
self.argname = args[0][0]
|
||||||
|
self.list = args[0][1]
|
||||||
|
self.next_inputs = args[1:]
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return self.argname
|
||||||
|
|
||||||
|
def list_items(self):
|
||||||
|
if isinstance(self.list, list):
|
||||||
|
return self.list
|
||||||
|
else:
|
||||||
|
return [self.list]
|
||||||
|
|
||||||
|
def next_input(self, args):
|
||||||
|
if len(self.next_inputs) > 0:
|
||||||
|
if self.next_inputs[0][0] not in args:
|
||||||
|
if isinstance(self.next_inputs[0][1], str):
|
||||||
|
return SimpleTextInput(*self.next_inputs)
|
||||||
|
else:
|
||||||
|
return SimpleListInput(*self.next_inputs)
|
||||||
|
|
||||||
|
|
||||||
|
# class ActiveWorkspacesIdList(sublime_plugin.ListInputHandler):
|
||||||
|
# def __init__(self, window=None, buffer_list=False, buffer_text=False):
|
||||||
|
# self.window = window
|
||||||
|
# self.buffer_list = buffer_list
|
||||||
|
# self.buffer_text = buffer_text
|
||||||
|
|
||||||
|
# def name(self):
|
||||||
|
# return "workspace_id"
|
||||||
|
|
||||||
|
# def list_items(self):
|
||||||
|
# return [vws.id for vws in client.all_workspaces(self.window)]
|
||||||
|
|
||||||
|
# def next_input(self, args):
|
||||||
|
# if self.buffer_list:
|
||||||
|
# return BufferIdList(args["workspace_id"])
|
||||||
|
# elif self.buffer_text:
|
||||||
|
# return SimpleTextInput(("buffer_id", "new buffer"))
|
||||||
|
|
||||||
|
|
||||||
|
# # To allow for having a selection and choosing non existing workspaces
|
||||||
|
# # we do a little dance: We pass this list input handler to a TextInputHandler
|
||||||
|
# # when we select "Create New..." which adds his result to the list of possible
|
||||||
|
# # workspaces and pop itself off the stack to go back to the list handler.
|
||||||
|
# class WorkspaceIdList(sublime_plugin.ListInputHandler):
|
||||||
|
# def __init__(self):
|
||||||
|
# assert client.codemp is not None # the command should not be available
|
||||||
|
|
||||||
|
# # at the moment, the client can't give us a full list of existing workspaces
|
||||||
|
# # so a textinputhandler would be more appropriate. but we keep this for the future
|
||||||
|
|
||||||
|
# self.add_entry_text = "* add entry..."
|
||||||
|
# self.list = client.codemp.list_workspaces(True, True).wait()
|
||||||
|
# self.list.sort()
|
||||||
|
# self.list.append(self.add_entry_text)
|
||||||
|
# self.preselected = None
|
||||||
|
|
||||||
|
# def name(self):
|
||||||
|
# return "workspace_id"
|
||||||
|
|
||||||
|
# def placeholder(self):
|
||||||
|
# return "Workspace"
|
||||||
|
|
||||||
|
# def list_items(self):
|
||||||
|
# if self.preselected is not None:
|
||||||
|
# return (self.list, self.preselected)
|
||||||
|
# else:
|
||||||
|
# return self.list
|
||||||
|
|
||||||
|
# def next_input(self, args):
|
||||||
|
# if args["workspace_id"] == self.add_entry_text:
|
||||||
|
# return AddListEntry(self)
|
||||||
|
|
||||||
|
|
||||||
|
# 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 = vws.codemp.filetree(None)
|
||||||
|
# self.list.sort()
|
||||||
|
# self.list.append(self.add_entry_text)
|
||||||
|
# self.preselected = None
|
||||||
|
|
||||||
|
# def name(self):
|
||||||
|
# return "buffer_id"
|
||||||
|
|
||||||
|
# def placeholder(self):
|
||||||
|
# return "Buffer Id"
|
||||||
|
|
||||||
|
# def list_items(self):
|
||||||
|
# if self.preselected is not None:
|
||||||
|
# return (self.list, self.preselected)
|
||||||
|
# else:
|
||||||
|
# return self.list
|
||||||
|
|
||||||
|
# def next_input(self, args):
|
||||||
|
# if args["buffer_id"] == self.add_entry_text:
|
||||||
|
# return AddListEntry(self)
|
||||||
|
|
||||||
|
|
||||||
|
class AddListEntry(sublime_plugin.TextInputHandler):
|
||||||
|
# this class works when the list input handler
|
||||||
|
# added appended a new element to it's list that will need to be
|
||||||
|
# replaced with the entry added from here!
|
||||||
|
def __init__(self, list_input_handler):
|
||||||
|
self.parent = list_input_handler
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def validate(self, text: str) -> bool:
|
||||||
|
return not len(text) == 0
|
||||||
|
|
||||||
|
def confirm(self, text: str):
|
||||||
|
self.parent.list.pop() # removes the add_entry_text
|
||||||
|
self.parent.list.insert(0, text)
|
||||||
|
self.parent.preselected = 0
|
||||||
|
|
||||||
|
def next_input(self, args):
|
||||||
|
return sublime_plugin.BackInputHandler()
|
133
src/workspace.py
133
src/workspace.py
|
@ -1,133 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
from typing import Optional, Tuple
|
|
||||||
|
|
||||||
from ..listeners import CodempClientTextChangeListener
|
|
||||||
import sublime
|
|
||||||
import shutil
|
|
||||||
import tempfile
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import codemp
|
|
||||||
from . import globals as g
|
|
||||||
from .buffers import VirtualBuffer
|
|
||||||
from .utils import draw_cursor_region
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def make_cursor_callback(workspace: VirtualWorkspace):
|
|
||||||
def _callback(ctl: codemp.CursorController):
|
|
||||||
def _():
|
|
||||||
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(
|
|
||||||
f"{workspace.id} 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(_)
|
|
||||||
|
|
||||||
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:
|
|
||||||
def __init__(self, handle: codemp.Workspace, window: sublime.Window):
|
|
||||||
self.codemp: codemp.Workspace = handle
|
|
||||||
self.window: sublime.Window = window
|
|
||||||
self.curctl: codemp.CursorController = self.codemp.cursor()
|
|
||||||
|
|
||||||
self.id: str = self.codemp.id()
|
|
||||||
|
|
||||||
self.codemp.fetch_buffers()
|
|
||||||
self.codemp.fetch_users()
|
|
||||||
|
|
||||||
self._id2buff: dict[str, VirtualBuffer] = {}
|
|
||||||
|
|
||||||
tmpdir = tempfile.mkdtemp(prefix="codemp_")
|
|
||||||
self.rootdir = tmpdir
|
|
||||||
|
|
||||||
proj: dict = self.window.project_data() # pyright: ignore
|
|
||||||
if proj is None:
|
|
||||||
proj = {"folders": []} # pyright: ignore, Value can be None
|
|
||||||
|
|
||||||
proj["folders"].append(
|
|
||||||
{"name": f"{g.WORKSPACE_FOLDER_PREFIX}{self.id}", "path": self.rootdir}
|
|
||||||
)
|
|
||||||
self.window.set_project_data(proj)
|
|
||||||
|
|
||||||
self.curctl.callback(make_cursor_callback(self))
|
|
||||||
self.isactive = True
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
logger.debug("workspace destroyed!")
|
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
|
||||||
# so we can use these as dict keys!
|
|
||||||
return hash(self.id)
|
|
||||||
|
|
||||||
def uninstall(self):
|
|
||||||
self.curctl.clear_callback()
|
|
||||||
self.isactive = False
|
|
||||||
self.curctl.stop()
|
|
||||||
|
|
||||||
for vbuff in self._id2buff.values():
|
|
||||||
vbuff.uninstall()
|
|
||||||
if not self.codemp.detach(vbuff.id):
|
|
||||||
logger.warning(
|
|
||||||
f"could not detach from '{vbuff.id}' for workspace '{self.id}'."
|
|
||||||
)
|
|
||||||
self._id2buff.clear()
|
|
||||||
|
|
||||||
proj: dict = self.window.project_data() # type:ignore
|
|
||||||
if proj is None:
|
|
||||||
raise
|
|
||||||
|
|
||||||
clean_proj_folders = list(
|
|
||||||
filter(
|
|
||||||
lambda f: f.get("name", "") != f"{g.WORKSPACE_FOLDER_PREFIX}{self.id}",
|
|
||||||
proj["folders"],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
proj["folders"] = clean_proj_folders
|
|
||||||
self.window.set_project_data(proj)
|
|
||||||
|
|
||||||
logger.info(f"cleaning up virtual workspace '{self.id}'")
|
|
||||||
shutil.rmtree(self.rootdir, ignore_errors=True)
|
|
||||||
|
|
||||||
def all_buffers(self) -> list[VirtualBuffer]:
|
|
||||||
return list(self._id2buff.values())
|
|
||||||
|
|
||||||
def buff_by_id(self, id: str) -> Optional[VirtualBuffer]:
|
|
||||||
return self._id2buff.get(id)
|
|
||||||
|
|
||||||
def install_buffer(
|
|
||||||
self, buff: codemp.BufferController, listener: CodempClientTextChangeListener
|
|
||||||
) -> VirtualBuffer:
|
|
||||||
logger.debug(f"installing buffer {buff.path()}")
|
|
||||||
|
|
||||||
view = self.window.new_file()
|
|
||||||
vbuff = VirtualBuffer(buff, view, self.rootdir)
|
|
||||||
self._id2buff[vbuff.id] = vbuff
|
|
||||||
|
|
||||||
vbuff.sync(listener)
|
|
||||||
|
|
||||||
return vbuff
|
|
||||||
|
|
||||||
def uninstall_buffer(self, vbuff: VirtualBuffer):
|
|
||||||
del self._id2buff[vbuff.id]
|
|
||||||
self.codemp.detach(vbuff.id)
|
|
||||||
vbuff.uninstall()
|
|
||||||
|
|
||||||
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)
|
|
Loading…
Reference in a new issue