mirror of
https://github.com/hexedtech/codemp-sublime.git
synced 2024-11-22 14:54:48 +01:00
chore: continuing refactor. should be almost done now. Cleaned up registries, commands and improved lookup logic.
This commit is contained in:
parent
74f386e819
commit
8565356d38
8 changed files with 388 additions and 337 deletions
73
main.py
73
main.py
|
@ -73,11 +73,12 @@ class EventListener(sublime_plugin.EventListener):
|
|||
# client.driver.stop()
|
||||
|
||||
def on_pre_close_window(self, window):
|
||||
assert client.codemp is not None
|
||||
assert session.client is not None
|
||||
|
||||
for vws in client.all_workspaces(window):
|
||||
client.codemp.leave_workspace(vws.id)
|
||||
client.uninstall_workspace(vws)
|
||||
for vws in workspaces.lookup(window):
|
||||
sublime.run_command("codemp_leave_workspace", {
|
||||
"workspace_id": vws.id
|
||||
})
|
||||
|
||||
def on_text_command(self, view, command_name, args):
|
||||
if command_name == "codemp_replace_text":
|
||||
|
@ -91,7 +92,7 @@ class EventListener(sublime_plugin.EventListener):
|
|||
class CodempClientViewEventListener(sublime_plugin.ViewEventListener):
|
||||
@classmethod
|
||||
def is_applicable(cls, settings):
|
||||
return settings.get(g.CODEMP_BUFFER_TAG) is not None
|
||||
return settings.get(g.CODEMP_VIEW_TAG) is not None
|
||||
|
||||
@classmethod
|
||||
def applies_to_primary_view_only(cls):
|
||||
|
@ -102,14 +103,17 @@ class CodempClientViewEventListener(sublime_plugin.ViewEventListener):
|
|||
start = self.view.rowcol(region.begin())
|
||||
end = self.view.rowcol(region.end())
|
||||
|
||||
vws = client.workspace_from_view(self.view)
|
||||
vbuff = client.buffer_from_view(self.view)
|
||||
if vws is None or vbuff is None:
|
||||
logger.error("we couldn't find the matching buffer or workspace!")
|
||||
try:
|
||||
_, vws, vbuff = objects_from_view(self.view)
|
||||
except ValueError:
|
||||
logger.error(f"Could not find buffers associated with the view {self.view}.\
|
||||
Removig the tag to disable event listener. Reattach.")
|
||||
# delete the tag so we disable this event listener on the view
|
||||
del self.view.settings()[g.CODEMP_VIEW_TAG]
|
||||
return
|
||||
|
||||
logger.debug(f"selection modified! {vws.id}, {vbuff.id} - {start}, {end}")
|
||||
vws.send_cursor(vbuff.id, start, end)
|
||||
logger.debug(f"selection modified! {vws.id}, {vbuff.id} - {start}, {end}")
|
||||
|
||||
def on_activated(self):
|
||||
global TEXT_LISTENER
|
||||
|
@ -126,16 +130,12 @@ class CodempClientViewEventListener(sublime_plugin.ViewEventListener):
|
|||
logger.debug("closing active view")
|
||||
global TEXT_LISTENER
|
||||
safe_listener_detach(TEXT_LISTENER) # pyright: ignore
|
||||
|
||||
vws = client.workspace_from_view(self.view)
|
||||
vbuff = client.buffer_from_view(self.view)
|
||||
if vws is None or vbuff is None:
|
||||
logger.debug("no matching workspace or buffer.")
|
||||
try:
|
||||
_, vws, vbuff = objects_from_view(self.view)
|
||||
buffers.remove(vbuff)
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
client.unregister_buffer(vbuff)
|
||||
vws.uninstall_buffer(vbuff)
|
||||
|
||||
def on_text_command(self, command_name, args):
|
||||
if command_name == "codemp_replace_text":
|
||||
logger.info("got a codemp_replace_text command! but in the view listener")
|
||||
|
@ -145,30 +145,16 @@ class CodempClientViewEventListener(sublime_plugin.ViewEventListener):
|
|||
logger.info("got a codemp_replace_text command! but in the view listener")
|
||||
|
||||
|
||||
class CodempClientTextChangeListener(sublime_plugin.TextChangeListener):
|
||||
@classmethod
|
||||
def is_applicable(cls, buffer): # pyright: ignore
|
||||
# don't attach this event listener automatically
|
||||
# we'll do it by hand with .attach(buffer).
|
||||
return False
|
||||
|
||||
def on_text_changed(self, changes):
|
||||
s = self.buffer.primary_view().settings()
|
||||
if s.get(g.CODEMP_IGNORE_NEXT_TEXT_CHANGE, False):
|
||||
logger.debug("Ignoring echoing back the change.")
|
||||
s[g.CODEMP_IGNORE_NEXT_TEXT_CHANGE] = False
|
||||
return
|
||||
|
||||
vbuff = client.buffer_from_view(self.buffer.primary_view())
|
||||
if vbuff is not None:
|
||||
logger.debug(f"local buffer change! {vbuff.id}")
|
||||
vbuff.send_buffer_change(changes)
|
||||
|
||||
TEXT_LISTENER = CodempClientTextChangeListener()
|
||||
|
||||
|
||||
|
||||
# Next TODO:
|
||||
# Server configurations:
|
||||
# - where do we store it?
|
||||
# - TOML? yes probably toml
|
||||
|
||||
# * Quickpanel for connecting with stuff.
|
||||
# * Quickpanel for browsing the servers
|
||||
# * Move all "server actions" like, create, delete, rename etc. as quickpanel actions. (See SFTP plugin.)
|
||||
# * make panel for notifications!
|
||||
# * make panel for errors and logging!
|
||||
|
||||
# Proxy Commands ( NOT USED, left just in case we need it again. )
|
||||
#############################################################################
|
||||
|
@ -184,3 +170,8 @@ TEXT_LISTENER = CodempClientTextChangeListener()
|
|||
#
|
||||
# def input_description(self):
|
||||
# return 'Share Buffer:'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -6,32 +6,13 @@ import random
|
|||
import codemp
|
||||
from ..core.session import session
|
||||
from ..core.workspace import workspaces
|
||||
from ..core.buffers import buffers
|
||||
|
||||
from input_handlers import SimpleTextInput
|
||||
from input_handlers import SimpleListInput
|
||||
from ..input_handlers import SimpleTextInput
|
||||
from ..input_handlers import SimpleListInput
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class CodempConnectCommand(sublime_plugin.WindowCommand):
|
||||
def run(self, server_host, user_name, password): # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
def _():
|
||||
try:
|
||||
config = codemp.Config(
|
||||
username = user_name,
|
||||
password = password,
|
||||
host = server_host)
|
||||
session.connect(config)
|
||||
except Exception as e:
|
||||
sublime.error_message(
|
||||
"Could not connect:\n Make sure the server is up\n\
|
||||
and your credentials are correct."
|
||||
)
|
||||
sublime.set_timeout_async(_)
|
||||
|
||||
def input_description(self):
|
||||
return "Server host:"
|
||||
|
||||
def input(self, args):
|
||||
if "server_host" not in args:
|
||||
return SimpleTextInput(
|
||||
|
@ -51,6 +32,26 @@ class CodempConnectCommand(sublime_plugin.WindowCommand):
|
|||
("password", "password?"),
|
||||
)
|
||||
|
||||
def input_description(self):
|
||||
return "Server host:"
|
||||
|
||||
def run(self, server_host, user_name, password): # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
def _():
|
||||
try:
|
||||
config = codemp.Config(
|
||||
username = user_name,
|
||||
password = password,
|
||||
host = server_host,
|
||||
port=50053,
|
||||
tls=False)
|
||||
session.connect(config)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
sublime.error_message(
|
||||
"Could not connect:\n Make sure the server is up\n\
|
||||
and your credentials are correct."
|
||||
)
|
||||
sublime.set_timeout_async(_)
|
||||
|
||||
# Disconnect Command
|
||||
class CodempDisconnectCommand(sublime_plugin.WindowCommand):
|
||||
|
@ -59,13 +60,13 @@ class CodempDisconnectCommand(sublime_plugin.WindowCommand):
|
|||
|
||||
def run(self):
|
||||
cli = session.client
|
||||
assert cli is not None
|
||||
|
||||
for ws in workspaces.lookup():
|
||||
if cli.leave_workspace(ws.id):
|
||||
workspaces.remove(ws)
|
||||
|
||||
session.drop_client()
|
||||
logger.info(f"disconnected from server '{session.config.host}'!")
|
||||
|
||||
|
||||
# Join Workspace Command
|
||||
|
@ -73,117 +74,115 @@ class CodempJoinWorkspaceCommand(sublime_plugin.WindowCommand):
|
|||
def is_enabled(self) -> bool:
|
||||
return session.is_active()
|
||||
|
||||
def run(self, workspace_id): # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
if workspace_id is None:
|
||||
return
|
||||
|
||||
logger.info(f"Joining workspace: '{workspace_id}'...")
|
||||
promise = session.client.join_workspace(workspace_id)
|
||||
active_window = sublime.active_window()
|
||||
|
||||
def _():
|
||||
try:
|
||||
workspace = promise.wait()
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Could not join workspace '{workspace_id}'.\n\nerror: {e}"
|
||||
)
|
||||
sublime.error_message(f"Could not join workspace '{workspace_id}'")
|
||||
return
|
||||
workspaces.add(workspace)
|
||||
sublime.set_timeout_async(_)
|
||||
|
||||
def input_description(self):
|
||||
return "Join:"
|
||||
|
||||
def input(self, args):
|
||||
if "workspace_id" not in args:
|
||||
list = session.client.list_workspaces(True, True)
|
||||
wslist = session.get_workspaces()
|
||||
return SimpleListInput(
|
||||
("workspace_id", list.wait()),
|
||||
("workspace_id", wslist),
|
||||
)
|
||||
|
||||
def run(self, workspace_id): # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
if workspace_id is None:
|
||||
return
|
||||
|
||||
logger.info(f"Joining workspace: '{workspace_id}'...")
|
||||
try:
|
||||
ws = session.client.attach_workspace(workspace_id).wait()
|
||||
except Exception as e:
|
||||
logger.error(f"Could not join workspace '{workspace_id}': {e}")
|
||||
sublime.error_message(f"Could not join workspace '{workspace_id}'")
|
||||
return
|
||||
|
||||
logger.debug("Joined! Adding workspace to registry")
|
||||
workspaces.add(ws)
|
||||
|
||||
|
||||
# Leave Workspace Command
|
||||
class CodempLeaveWorkspaceCommand(sublime_plugin.WindowCommand):
|
||||
def is_enabled(self):
|
||||
return client.codemp is not None and \
|
||||
len(client.all_workspaces(self.window)) > 0
|
||||
|
||||
def run(self, workspace_id: str): # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
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)
|
||||
else:
|
||||
logger.error(f"could not leave the workspace '{workspace_id}'")
|
||||
return session.is_active() and \
|
||||
len(workspaces.lookup(self.window)) > 0
|
||||
|
||||
def input(self, args):
|
||||
if "workspace_id" not in args:
|
||||
return ActiveWorkspacesIdList()
|
||||
wslist = session.client.active_workspaces()
|
||||
return SimpleListInput(
|
||||
("workspace_id", wslist),
|
||||
)
|
||||
|
||||
def run(self, workspace_id: str): # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
try:
|
||||
workspaces.remove(workspace_id)
|
||||
finally:
|
||||
if not session.client.leave_workspace(workspace_id):
|
||||
logger.error(f"could not leave the workspace '{workspace_id}'")
|
||||
|
||||
|
||||
class CodempInviteToWorkspaceCommand(sublime_plugin.WindowCommand):
|
||||
def is_enabled(self) -> bool:
|
||||
return client.codemp is not None and len(client.all_workspaces(self.window)) > 0
|
||||
|
||||
def run(self, workspace_id: str, user: str): # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
assert client.codemp is not None
|
||||
client.codemp.invite_to_workspace(workspace_id, user)
|
||||
logger.debug(f"invite sent to user {user} for workspace {workspace_id}.")
|
||||
return session.is_active() and len(workspaces.lookup(self.window)) > 0
|
||||
|
||||
def input(self, args):
|
||||
assert client.codemp is not None
|
||||
if "workspace_id" not in args:
|
||||
wslist = client.codemp.list_workspaces(True, False)
|
||||
wslist = session.get_workspaces(owned=True, invited=False)
|
||||
return SimpleListInput(
|
||||
("workspace_id", wslist.wait()), ("user", "invitee's username")
|
||||
("workspace_id", wslist), ("user", "invitee's username")
|
||||
)
|
||||
|
||||
if "user" not in args:
|
||||
return SimpleTextInput(("user", "invitee's username"))
|
||||
|
||||
def run(self, workspace_id: str, user: str): # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
try:
|
||||
session.client.invite_to_workspace(workspace_id, user)
|
||||
logger.debug(f"invite sent to user {user} for workspace {workspace_id}.")
|
||||
except Exception as e:
|
||||
logger.error(f"Could not invite to workspace: {e}")
|
||||
|
||||
|
||||
|
||||
class CodempCreateWorkspaceCommand(sublime_plugin.WindowCommand):
|
||||
def is_enabled(self):
|
||||
return client.codemp is not None
|
||||
|
||||
def run(self, workspace_id: str): # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
assert client.codemp is not None
|
||||
client.codemp.create_workspace(workspace_id)
|
||||
return session.is_active()
|
||||
|
||||
def input(self, args):
|
||||
if "workspace_id" not in args:
|
||||
return SimpleTextInput(("workspace_id", "new workspace"))
|
||||
return SimpleTextInput(("workspace_id", "new workspace name"))
|
||||
|
||||
def run(self, workspace_id: str): # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
try:
|
||||
session.client.create_workspace(workspace_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Could not create workspace: {e}")
|
||||
|
||||
|
||||
|
||||
class CodempDeleteWorkspaceCommand(sublime_plugin.WindowCommand):
|
||||
def is_enabled(self):
|
||||
return client.codemp is not None
|
||||
return session.is_active()
|
||||
|
||||
def input(self, args):
|
||||
workspaces = session.get_workspaces(owned=True, invited=False) # noqa: F841
|
||||
if "workspace_id" not in args:
|
||||
return SimpleListInput(("workspace_id", workspaces)
|
||||
|
||||
def run(self, workspace_id: str): # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
assert client.codemp is not None
|
||||
|
||||
vws = client.workspace_from_id(workspace_id)
|
||||
if vws is not None:
|
||||
try:
|
||||
vws = workspaces.lookupId(workspace_id)
|
||||
if not sublime.ok_cancel_dialog(
|
||||
"You are currently attached to '{workspace_id}'.\n\
|
||||
Do you want to detach and delete it?",
|
||||
ok_title="yes",
|
||||
title="Delete Workspace?",
|
||||
ok_title="yes", title="Delete Workspace?",
|
||||
):
|
||||
return
|
||||
if not client.codemp.leave_workspace(workspace_id):
|
||||
logger.debug("error while leaving the workspace:")
|
||||
raise RuntimeError("error while leaving the workspace")
|
||||
self.window.run_command(
|
||||
"codemp_leave_workspace",
|
||||
{"workspace_id": workspace_id})
|
||||
|
||||
client.uninstall_workspace(vws)
|
||||
except KeyError: pass
|
||||
finally:
|
||||
session.client.delete_workspace(workspace_id)
|
||||
|
||||
client.codemp.delete_workspace(workspace_id)
|
||||
|
||||
def input(self, args):
|
||||
assert client.codemp is not None
|
||||
workspaces = client.codemp.list_workspaces(True, False) # noqa: F841
|
||||
if "workspace_id" not in args:
|
||||
return SimpleListInput(("workspace_id", workspaces.wait()))
|
||||
|
|
|
@ -2,54 +2,74 @@ import sublime
|
|||
import sublime_plugin
|
||||
import logging
|
||||
|
||||
from .src.client import client
|
||||
from listeners import TEXT_LISTENER
|
||||
from input_handlers import SimpleTextInput
|
||||
from input_handlers import ActiveWorkspacesIdList
|
||||
from input_handlers import BufferIdList
|
||||
from ..core.session import session
|
||||
from ..core.workspace import workspaces
|
||||
from ..core.buffers import buffers
|
||||
|
||||
from ..text_listener import TEXT_LISTENER
|
||||
from ..utils import safe_listener_attach, safe_listener_detach
|
||||
from ..input_handlers import SimpleListInput, SimpleTextInput
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Join Buffer Command
|
||||
class CodempJoinBufferCommand(sublime_plugin.WindowCommand):
|
||||
def is_enabled(self):
|
||||
available_workspaces = client.all_workspaces(self.window)
|
||||
return len(available_workspaces) > 0
|
||||
return len(workspaces.lookup(self.window)) > 0
|
||||
|
||||
def input_description(self) -> str:
|
||||
return "Attach: "
|
||||
|
||||
def input(self, args):
|
||||
if "workspace_id" not in args:
|
||||
wslist = session.get_workspaces(owned=True, invited=True)
|
||||
return SimpleListInput(
|
||||
("workspace_id", wslist),
|
||||
)
|
||||
|
||||
if "buffer_id" not in args:
|
||||
try: ws = workspaces.lookupId(args["workspace_id"])
|
||||
except KeyError:
|
||||
sublime.error_message("Workspace does not exists or is not active.")
|
||||
return None
|
||||
|
||||
bflist = ws.handle.fetch_buffers().wait()
|
||||
return SimpleListInput(
|
||||
("buffer_id", bflist),
|
||||
)
|
||||
|
||||
def run(self, workspace_id, buffer_id): # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
# A workspace has some Buffers inside of it (filetree)
|
||||
# some of those you are already attached to
|
||||
# If already attached to it return the same alredy existing bufferctl
|
||||
# if existing but not attached (attach)
|
||||
# if not existing ask for creation (create + attach)
|
||||
vws = client.workspace_from_id(workspace_id)
|
||||
assert vws is not None
|
||||
try: vws = workspaces.lookupId(workspace_id)
|
||||
except KeyError:
|
||||
logger.error(f"Can't create buffer: '{workspace_id}' does not exists or is not active.")
|
||||
return
|
||||
|
||||
# is the buffer already installed?
|
||||
if buffer_id in vws.codemp.buffer_list():
|
||||
logger.info("buffer already installed!")
|
||||
return # do nothing.
|
||||
try: # if it exists already, focus and listen
|
||||
buff = buffers.lookupId(buffer_id)
|
||||
safe_listener_detach(TEXT_LISTENER)
|
||||
safe_listener_attach(TEXT_LISTENER, buff.view.buffer())
|
||||
self.window.focus_view(buff.view)
|
||||
return
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if buffer_id not in vws.codemp.filetree(filter=buffer_id):
|
||||
create = sublime.ok_cancel_dialog(
|
||||
"There is no buffer named '{buffer_id}' in the workspace '{workspace_id}'.\n\
|
||||
# if doesn't exist in the workspace, ask for creation.
|
||||
if vws.handle.get_buffer(buffer_id) is None:
|
||||
if sublime.ok_cancel_dialog(
|
||||
f"There is no buffer named '{buffer_id}' in the workspace '{workspace_id}'.\n\
|
||||
Do you want to create it?",
|
||||
ok_title="yes",
|
||||
title="Create Buffer?",
|
||||
)
|
||||
if create:
|
||||
try:
|
||||
create_promise = vws.codemp.create(buffer_id)
|
||||
except Exception as e:
|
||||
logging.error(f"could not create buffer:\n\n {e}")
|
||||
return
|
||||
create_promise.wait()
|
||||
ok_title="yes", title="Create Buffer?",
|
||||
):
|
||||
sublime.run_command("codemp_create_buffer", {
|
||||
"workspace_id": workspace_id,
|
||||
"buffer_id": buffer_id
|
||||
})
|
||||
|
||||
# now we can defer the attaching process
|
||||
logger.debug(f"attempting to attach to {buffer_id}...")
|
||||
promise = vws.codemp.attach(buffer_id)
|
||||
promise = vws.handle.attach_buffer(buffer_id)
|
||||
|
||||
def deferred_attach(promise):
|
||||
def _():
|
||||
try:
|
||||
buff_ctl = promise.wait()
|
||||
logger.debug("attach successfull!")
|
||||
|
@ -58,151 +78,144 @@ class CodempJoinBufferCommand(sublime_plugin.WindowCommand):
|
|||
sublime.error_message(f"Could not attach to buffer '{buffer_id}'")
|
||||
return
|
||||
|
||||
vbuff = vws.install_buffer(buff_ctl, TEXT_LISTENER)
|
||||
client.register_buffer(vws, vbuff) # we need to keep track of it.
|
||||
safe_listener_detach(TEXT_LISTENER)
|
||||
vbuff = buffers.add(buff_ctl, vws)
|
||||
|
||||
# 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))
|
||||
|
||||
def input_description(self) -> str:
|
||||
return "Attach: "
|
||||
|
||||
def input(self, args):
|
||||
if "workspace_id" not in args:
|
||||
return ActiveWorkspacesIdList(self.window, buffer_list=True)
|
||||
|
||||
if "buffer_id" not in args:
|
||||
return BufferIdList(args["workspace_id"])
|
||||
if self.window.active_view() == vbuff.view:
|
||||
# if view is already active focusing it won't trigger `on_activate`.
|
||||
safe_listener_attach(TEXT_LISTENER, vbuff.view.buffer())
|
||||
else:
|
||||
self.window.focus_view(vbuff.view)
|
||||
sublime.set_timeout_async(_)
|
||||
|
||||
|
||||
# Leave Buffer Comand
|
||||
class CodempLeaveBufferCommand(sublime_plugin.WindowCommand):
|
||||
def is_enabled(self):
|
||||
return len(client.all_buffers()) > 0
|
||||
|
||||
def run(self, workspace_id, buffer_id): # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
vbuff = client.buffer_from_id(buffer_id)
|
||||
vws = client.workspace_from_id(workspace_id)
|
||||
|
||||
if vbuff is None or vws is None:
|
||||
sublime.error_message(f"You are not attached to the buffer '{id}'")
|
||||
logging.warning(f"You are not attached to the buffer '{id}'")
|
||||
return
|
||||
|
||||
def defer_detach():
|
||||
if vws.codemp.detach(buffer_id):
|
||||
vws.uninstall_buffer(vbuff)
|
||||
client.unregister_buffer(vbuff)
|
||||
|
||||
sublime.set_timeout_async(defer_detach)
|
||||
return len(buffers.lookup()) > 0
|
||||
|
||||
def input_description(self) -> str:
|
||||
return "Leave: "
|
||||
|
||||
def input(self, args):
|
||||
if "workspace_id" not in args:
|
||||
return ActiveWorkspacesIdList(self.window, buffer_list=True)
|
||||
wslist = session.client.active_workspaces()
|
||||
return SimpleListInput(
|
||||
("workspace_id", wslist),
|
||||
)
|
||||
|
||||
if "buffer_id" not in args:
|
||||
return BufferIdList(args["workspace_id"])
|
||||
bflist = [bf.id for bf in buffers.lookup(args["workspace_id"])]
|
||||
return SimpleListInput(
|
||||
("buffer_id", bflist)
|
||||
)
|
||||
|
||||
def run(self, workspace_id, buffer_id): # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
try:
|
||||
buffers.lookupId(buffer_id)
|
||||
vws = workspaces.lookupId(workspace_id)
|
||||
except KeyError:
|
||||
sublime.error_message(f"You are not attached to the buffer '{id}'")
|
||||
logging.warning(f"You are not attached to the buffer '{id}'")
|
||||
return
|
||||
|
||||
if not vws.handle.get_buffer(buffer_id):
|
||||
logging.error("The desired buffer is not managed by the workspace.")
|
||||
return
|
||||
|
||||
def _():
|
||||
try:
|
||||
buffers.remove(buffer_id)
|
||||
finally:
|
||||
if not vws.handle.detach_buffer(buffer_id):
|
||||
logger.error(f"could not leave the buffer {buffer_id}.")
|
||||
sublime.set_timeout_async(_)
|
||||
|
||||
# Leave Buffer Comand
|
||||
class CodempCreateBufferCommand(sublime_plugin.WindowCommand):
|
||||
def is_enabled(self):
|
||||
return len(client.all_workspaces(self.window)) > 0
|
||||
|
||||
def run(self, workspace_id, buffer_id):# pyright: ignore[reportIncompatibleMethodOverride]
|
||||
vws = client.workspace_from_id(workspace_id)
|
||||
|
||||
if vws is None:
|
||||
sublime.error_message(
|
||||
f"You are not attached to the workspace '{workspace_id}'"
|
||||
)
|
||||
logging.warning(f"You are not attached to the workspace '{workspace_id}'")
|
||||
return
|
||||
|
||||
vws.codemp.create(buffer_id)
|
||||
logging.info(
|
||||
"created buffer '{buffer_id}' in the workspace '{workspace_id}'.\n\
|
||||
To interact with it you need to attach to it with Codemp: Attach."
|
||||
)
|
||||
return len(workspaces.lookup()) > 0
|
||||
|
||||
def input_description(self) -> str:
|
||||
return "Create Buffer: "
|
||||
|
||||
def input(self, args):
|
||||
if "workspace_id" not in args:
|
||||
return ActiveWorkspacesIdList(self.window, buffer_text=True)
|
||||
wslist = session.client.active_workspaces()
|
||||
return SimpleListInput(
|
||||
("workspace_id", wslist),
|
||||
)
|
||||
|
||||
if "buffer_id" not in args:
|
||||
return SimpleTextInput(
|
||||
(("buffer_id", "new buffer")),
|
||||
("buffer_id", "new buffer name"),
|
||||
)
|
||||
|
||||
|
||||
class CodempDeleteBufferCommand(sublime_plugin.WindowCommand):
|
||||
def is_enabled(self):
|
||||
return client.codemp is not None and len(client.codemp.active_workspaces()) > 0
|
||||
|
||||
def run(self, workspace_id, buffer_id):# pyright: ignore[reportIncompatibleMethodOverride]
|
||||
vws = client.workspace_from_id(workspace_id)
|
||||
if vws is None:
|
||||
try: vws = workspaces.lookupId(workspace_id)
|
||||
except KeyError:
|
||||
sublime.error_message(
|
||||
f"You are not attached to the workspace '{workspace_id}'"
|
||||
)
|
||||
logging.warning(f"You are not attached to the workspace '{workspace_id}'")
|
||||
return
|
||||
|
||||
fetch_promise = vws.codemp.fetch_buffers()
|
||||
delete = sublime.ok_cancel_dialog(
|
||||
f"Confirm you want to delete the buffer '{buffer_id}'",
|
||||
ok_title="delete",
|
||||
title="Delete Buffer?",
|
||||
vws.handle.create_buffer(buffer_id)
|
||||
logging.info(
|
||||
"created buffer '{buffer_id}' in the workspace '{workspace_id}'.\n\
|
||||
To interact with it you need to attach to it with Codemp: Attach."
|
||||
)
|
||||
if not delete:
|
||||
return
|
||||
fetch_promise.wait()
|
||||
existing = vws.codemp.filetree(buffer_id)
|
||||
if len(existing) == 0:
|
||||
sublime.error_message(
|
||||
f"The buffer '{buffer_id}' does not exists in the workspace."
|
||||
)
|
||||
logging.info(f"The buffer '{buffer_id}' does not exists in the workspace.")
|
||||
return
|
||||
|
||||
def deferred_delete():
|
||||
try:
|
||||
vws.codemp.delete(buffer_id).wait()
|
||||
except Exception as e:
|
||||
logging.error(
|
||||
f"error when deleting the buffer '{buffer_id}':\n\n {e}", True
|
||||
)
|
||||
return
|
||||
|
||||
vbuff = client.buffer_from_id(buffer_id)
|
||||
if vbuff is None:
|
||||
# we are not attached to it!
|
||||
sublime.set_timeout_async(deferred_delete)
|
||||
else:
|
||||
if vws.codemp.detach(buffer_id):
|
||||
vws.uninstall_buffer(vbuff)
|
||||
sublime.set_timeout_async(deferred_delete)
|
||||
else:
|
||||
logging.error(
|
||||
f"error while detaching from buffer '{buffer_id}', aborting the delete."
|
||||
)
|
||||
return
|
||||
class CodempDeleteBufferCommand(sublime_plugin.WindowCommand):
|
||||
def is_enabled(self):
|
||||
return len(workspaces.lookup()) > 0
|
||||
|
||||
def input_description(self) -> str:
|
||||
return "Delete buffer: "
|
||||
|
||||
def input(self, args):
|
||||
if "workspace_id" not in args:
|
||||
return ActiveWorkspacesIdList(self.window, buffer_list=True)
|
||||
wslist = session.get_workspaces(owned=True, invited=False)
|
||||
return SimpleListInput(
|
||||
("workspace_id", wslist),
|
||||
)
|
||||
|
||||
if "buffer_id" not in args:
|
||||
return BufferIdList(args["workspace_id"])
|
||||
try: ws = workspaces.lookupId(args["workspace_id"])
|
||||
except KeyError:
|
||||
sublime.error_message("Workspace does not exists or is not attached.")
|
||||
return sublime_plugin.BackInputHandler()
|
||||
|
||||
bflist = ws.handle.fetch_buffers().wait()
|
||||
return SimpleListInput(
|
||||
("buffer_id", bflist),
|
||||
)
|
||||
|
||||
def run(self, workspace_id, buffer_id):# pyright: ignore[reportIncompatibleMethodOverride]
|
||||
try: vws = workspaces.lookupId(workspace_id)
|
||||
except KeyError:
|
||||
sublime.error_message(
|
||||
f"You are not attached to the workspace '{workspace_id}'"
|
||||
)
|
||||
logging.warning(f"You are not attached to the workspace '{workspace_id}'")
|
||||
return
|
||||
|
||||
if not sublime.ok_cancel_dialog(
|
||||
f"Confirm you want to delete the buffer '{buffer_id}'",
|
||||
ok_title="delete", title="Delete Buffer?",
|
||||
): return
|
||||
|
||||
try:
|
||||
buffers.lookupId(buffer_id)
|
||||
if not sublime.ok_cancel_dialog(
|
||||
"You are currently attached to '{buffer_id}'.\n\
|
||||
Do you want to detach and delete it?",
|
||||
ok_title="yes", title="Delete Buffer?",
|
||||
):
|
||||
return
|
||||
self.window.run_command(
|
||||
"codemp_leave_buffer",
|
||||
{ "workspace_id": workspace_id, "buffer_id": buffer_id })
|
||||
except KeyError: pass
|
||||
finally:
|
||||
vws.handle.delete_buffer(buffer_id).wait()
|
||||
|
|
|
@ -8,6 +8,7 @@ import sublime
|
|||
import os
|
||||
import logging
|
||||
|
||||
from codemp import TextChange
|
||||
from .. import globals as g
|
||||
from ..utils import populate_view
|
||||
from ..utils import safe_listener_attach
|
||||
|
@ -20,33 +21,40 @@ def bind_callback(v: sublime.View):
|
|||
def _callback(bufctl: codemp.BufferController):
|
||||
def _():
|
||||
change_id = v.change_id()
|
||||
while change := bufctl.try_recv().wait():
|
||||
while buffup := bufctl.try_recv().wait():
|
||||
logger.debug("received remote buffer change!")
|
||||
if change is None:
|
||||
if buffup is None:
|
||||
break
|
||||
|
||||
if change.is_empty():
|
||||
if buffup.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 v.id() == g.ACTIVE_CODEMP_VIEW:
|
||||
if v == sublime.active_window().active_view():
|
||||
v.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.
|
||||
v.run_command(
|
||||
"codemp_replace_text",
|
||||
{
|
||||
"start": change.start,
|
||||
"end": change.end,
|
||||
"content": change.content,
|
||||
"change_id": change_id,
|
||||
}, # pyright: ignore
|
||||
)
|
||||
try:
|
||||
change = buffup.change
|
||||
v.run_command(
|
||||
"codemp_replace_text",
|
||||
{
|
||||
"start": change.start,
|
||||
"end": change.end,
|
||||
"content": change.content,
|
||||
"change_id": change_id,
|
||||
}, # pyright: ignore
|
||||
)
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
bufctl.ack(buffup.version)
|
||||
sublime.set_timeout(_)
|
||||
return _callback
|
||||
|
||||
|
@ -61,7 +69,6 @@ class BufferManager():
|
|||
def __del__(self):
|
||||
logger.debug(f"dropping buffer {self.id}")
|
||||
self.handle.clear_callback()
|
||||
self.handle.stop()
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.id)
|
||||
|
@ -76,8 +83,9 @@ class BufferManager():
|
|||
region.begin(), region.end(), change.str
|
||||
)
|
||||
)
|
||||
|
||||
# we must block and wait the send request to make sure the change went through ok
|
||||
self.handle.send(region.begin(), region.end(), change.str).wait()
|
||||
self.handle.send(TextChange(start=region.begin(), end=region.end(), content=change.str))
|
||||
|
||||
def sync(self, text_listener):
|
||||
promise = self.handle.content()
|
||||
|
@ -95,32 +103,43 @@ class BufferRegistry():
|
|||
def lookup(self, ws: Optional[WorkspaceManager] = None) -> list[BufferManager]:
|
||||
if not ws:
|
||||
return list(self._buffers.keys())
|
||||
bf = self._buffers.inverse.get(ws)
|
||||
return bf if bf else []
|
||||
bfs = self._buffers.inverse.get(ws)
|
||||
return bfs if bfs else []
|
||||
|
||||
def lookupId(self, bid: str) -> Optional[BufferManager]:
|
||||
return next((bf for bf in self._buffers if bf.id == bid), None)
|
||||
def lookupParent(self, bf: BufferManager | str) -> WorkspaceManager:
|
||||
if isinstance(bf, str):
|
||||
bf = self.lookupId(bf)
|
||||
return self._buffers[bf]
|
||||
|
||||
def lookupId(self, bid: str) -> BufferManager:
|
||||
bfm = next((bf for bf in self._buffers if bf.id == bid), None)
|
||||
if not bfm: raise KeyError
|
||||
return bfm
|
||||
|
||||
def add(self, bhandle: codemp.BufferController, wsm: WorkspaceManager):
|
||||
bid = bhandle.path()
|
||||
tmpfile = os.path.join(wsm.rootdir, bid)
|
||||
open(tmpfile, "a").close()
|
||||
# tmpfile = os.path.join(wsm.rootdir, bid)
|
||||
# open(tmpfile, "a").close()
|
||||
content = bhandle.content()
|
||||
|
||||
win = sublime.active_window()
|
||||
view = win.open_file(bid)
|
||||
view.set_scratch(True)
|
||||
view.retarget(tmpfile)
|
||||
# view.retarget(tmpfile)
|
||||
view.settings().set(g.CODEMP_VIEW_TAG, True)
|
||||
view.settings().set(g.CODEMP_BUFFER_ID, bid)
|
||||
view.set_status(g.SUBLIME_STATUS_ID, "[Codemp]")
|
||||
populate_view(view, content.wait())
|
||||
|
||||
tmpfile = "DISABLE"
|
||||
bfm = BufferManager(bhandle, view, tmpfile)
|
||||
self._buffers[bfm] = wsm
|
||||
|
||||
def remove(self, bf: Optional[BufferManager | str]):
|
||||
return bfm
|
||||
|
||||
def remove(self, bf: BufferManager | str):
|
||||
if isinstance(bf, str):
|
||||
bf = self.lookupId(bf)
|
||||
if not bf: return
|
||||
|
||||
del self._buffers[bf]
|
||||
bf.view.close()
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import logging
|
||||
import codemp
|
||||
|
||||
from ..utils import some
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class SessionManager():
|
||||
|
@ -17,7 +19,7 @@ class SessionManager():
|
|||
|
||||
@property
|
||||
def client(self):
|
||||
return self._client
|
||||
return some(self._client)
|
||||
|
||||
def get_or_init(self) -> codemp.Driver:
|
||||
if self._driver:
|
||||
|
@ -48,9 +50,16 @@ class SessionManager():
|
|||
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})")
|
||||
self.config = config
|
||||
logger.debug(f"Connected to '{self.config.host}' as user {self._client.current_user().name} (id: {self._client.current_user().id})")
|
||||
return self._client
|
||||
|
||||
def get_workspaces(self, owned: bool = True, invited: bool = True):
|
||||
owned_wss = self.client.fetch_owned_workspaces().wait() if owned else []
|
||||
invited_wss = self.client.fetch_joined_workspaces().wait() if invited else []
|
||||
|
||||
return owned_wss + invited_wss
|
||||
|
||||
def drop_client(self):
|
||||
self._client = None
|
||||
|
||||
|
|
|
@ -2,14 +2,15 @@ from __future__ import annotations
|
|||
from typing import Optional, Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from ...main import CodempClientTextChangeListener
|
||||
import codemp
|
||||
|
||||
import sublime
|
||||
import shutil
|
||||
import tempfile
|
||||
import logging
|
||||
import gc
|
||||
|
||||
from codemp import Selection
|
||||
from .. import globals as g
|
||||
from ..utils import draw_cursor_region
|
||||
from ..utils import bidict
|
||||
|
@ -18,9 +19,9 @@ from .buffers import buffers
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
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
|
||||
proj = w.project_data()
|
||||
if not isinstance(proj, dict):
|
||||
proj = {"folders": []}
|
||||
|
||||
if name == "":
|
||||
entry = {"path": folder}
|
||||
|
@ -32,7 +33,7 @@ def add_project_folder(w: sublime.Window, folder: str, name: str = ""):
|
|||
w.set_project_data(proj)
|
||||
|
||||
def remove_project_folder(w: sublime.Window, filterstr: str):
|
||||
proj: dict = self.window.project_data() # type:ignore
|
||||
proj: dict = w.project_data() # type:ignore
|
||||
if proj is None:
|
||||
return
|
||||
|
||||
|
@ -51,10 +52,13 @@ def cursor_callback(ctl: codemp.CursorController):
|
|||
while event := ctl.try_recv().wait():
|
||||
if event is None: break
|
||||
|
||||
bfm = buffers.lookupId(event.buffer)
|
||||
if not bfm: continue
|
||||
try: bfm = buffers.lookupId(event.sel.buffer)
|
||||
except KeyError: continue
|
||||
|
||||
draw_cursor_region(bfm.view, event.start, event.end, event.user)
|
||||
region_start = (event.sel.start_row, event.sel.start_col)
|
||||
region_end = (event.sel.end_row, event.sel.end_col)
|
||||
|
||||
draw_cursor_region(bfm.view, region_start, region_end, event.user)
|
||||
sublime.set_timeout_async(_)
|
||||
|
||||
class WorkspaceManager():
|
||||
|
@ -70,8 +74,8 @@ class WorkspaceManager():
|
|||
logger.debug(f"dropping workspace {self.id}")
|
||||
self.curctl.clear_callback()
|
||||
|
||||
for buff in self.handle.buffer_list():
|
||||
if not self.handle.detach(buff):
|
||||
for buff in self.handle.active_buffers():
|
||||
if not self.handle.detach_buffer(buff):
|
||||
logger.warning(
|
||||
f"could not detach from '{buff}' for workspace '{self.id}'."
|
||||
)
|
||||
|
@ -82,7 +86,14 @@ class WorkspaceManager():
|
|||
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)
|
||||
sel = Selection(
|
||||
start_row=start[0],
|
||||
start_col=start[1],
|
||||
end_row=end[0],
|
||||
end_col=end[1],
|
||||
buffer=id
|
||||
)
|
||||
self.curctl.send(sel)
|
||||
|
||||
class WorkspaceRegistry():
|
||||
def __init__(self) -> None:
|
||||
|
@ -94,31 +105,37 @@ class WorkspaceRegistry():
|
|||
ws = self._workspaces.inverse.get(w)
|
||||
return ws if ws else []
|
||||
|
||||
def lookupId(self, wid: str) -> Optional[WorkspaceManager]:
|
||||
return next((ws for ws in self._workspaces if ws.id == wid), None)
|
||||
def lookupParent(self, ws: WorkspaceManager | str) -> sublime.Window:
|
||||
if isinstance(ws, str):
|
||||
wsm = self.lookupId(ws)
|
||||
return self._workspaces[ws]
|
||||
|
||||
def lookupId(self, wid: str) -> WorkspaceManager:
|
||||
wsm = next((ws for ws in self._workspaces if ws.id == wid), None)
|
||||
if not wsm: raise KeyError
|
||||
return wsm
|
||||
|
||||
def add(self, wshandle: codemp.Workspace) -> WorkspaceManager:
|
||||
win = sublime.active_window()
|
||||
|
||||
tmpdir = tempfile.mkdtemp(prefix="codemp_")
|
||||
name = f"{g.WORKSPACE_FOLDER_PREFIX}{wshandle.id()}"
|
||||
add_project_folder(win, tmpdir, name)
|
||||
# tmpdir = tempfile.mkdtemp(prefix="codemp_")
|
||||
# add_project_folder(win, tmpdir, f"{g.WORKSPACE_FOLDER_PREFIX}{wshandle.id()}")
|
||||
|
||||
tmpdir = "DISABLED"
|
||||
wm = WorkspaceManager(wshandle, win, tmpdir)
|
||||
self._workspaces[wm] = win
|
||||
return wm
|
||||
|
||||
def remove(self, ws: Optional[WorkspaceManager | str]):
|
||||
def remove(self, ws: WorkspaceManager | str):
|
||||
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)
|
||||
# remove_project_folder(ws.window, f"{g.WORKSPACE_FOLDER_PREFIX}{ws.id}")
|
||||
# shutil.rmtree(ws.rootdir, ignore_errors=True)
|
||||
del self._workspaces[ws]
|
||||
|
||||
|
||||
|
||||
workspaces = WorkspaceRegistry()
|
||||
|
||||
|
||||
|
|
|
@ -7,10 +7,9 @@ from typing import Tuple, Union, List
|
|||
############################################################
|
||||
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:]
|
||||
self.input, *self.next_inputs = args
|
||||
self.argname = self.input[0]
|
||||
self.default = self.input[1]
|
||||
|
||||
def initial_text(self):
|
||||
if isinstance(self.default, str):
|
||||
|
@ -32,9 +31,9 @@ class SimpleTextInput(sublime_plugin.TextInputHandler):
|
|||
|
||||
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:]
|
||||
self.input, *self.next_inputs = args
|
||||
self.argname = self.input[0]
|
||||
self.list = self.input[1]
|
||||
|
||||
def name(self):
|
||||
return self.argname
|
||||
|
@ -133,23 +132,23 @@ class SimpleListInput(sublime_plugin.ListInputHandler):
|
|||
# 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
|
||||
# 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 name(self):
|
||||
# return ""
|
||||
|
||||
def validate(self, text: str) -> bool:
|
||||
return not len(text) == 0
|
||||
# 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 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()
|
||||
# def next_input(self, args):
|
||||
# return sublime_plugin.BackInputHandler()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import sublime
|
||||
import sublime_plugin
|
||||
from typing import Dict, Generic, TypeVar
|
||||
from typing import Dict, Generic, TypeVar, Optional
|
||||
from . import globals as g
|
||||
|
||||
# bidirectional dictionary so that we can have bidirectional
|
||||
|
@ -13,7 +13,6 @@ D = TypeVar("D", Dict, dict)
|
|||
K = TypeVar("K")
|
||||
V = TypeVar("V")
|
||||
|
||||
|
||||
# using del bd.inverse[key] doesn't work since it can't be intercepted.
|
||||
# the only way is to iterate:
|
||||
# for key in bd.inverse[inverse_key]
|
||||
|
@ -94,7 +93,7 @@ def populate_view(view, content):
|
|||
)
|
||||
|
||||
|
||||
def get_view_from_local_path(path):
|
||||
def view_from_local_path(path):
|
||||
for window in sublime.windows():
|
||||
for view in window.views():
|
||||
if view.file_name() == path:
|
||||
|
@ -115,3 +114,8 @@ def draw_cursor_region(view, start, end, user):
|
|||
annotations=[user], # pyright: ignore
|
||||
annotation_color=g.PALETTE[user_hash % len(g.PALETTE)],
|
||||
)
|
||||
|
||||
T = TypeVar("T")
|
||||
def some(x: Optional[T]) -> T:
|
||||
assert x is not None
|
||||
return x
|
||||
|
|
Loading…
Reference in a new issue