feat: Stacco nuova version.

feat: Added single Join command
chore: separated input handlers and commands, minor cleanup.

Former-commit-id: 29a49bd8dbdeaf24f988e0a382e74d7e14d957a8
This commit is contained in:
Camillo Schenone 2024-02-24 16:56:22 +01:00
parent 7db877622d
commit 9f126bffd4
5 changed files with 149 additions and 156 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "CodempClient-Sublime" name = "CodempClient-Sublime"
version = "0.3.0" version = "0.4.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View file

@ -26,9 +26,16 @@
"command": "codemp_connect", "command": "codemp_connect",
"args": { "args": {
// "server_host": "http://[::1]:50051" // "server_host": "http://[::1]:50051"
// 'session' : 'default' (the name of the workspace to join)
} }
}, },
{
"caption": "Codemp: Join",
"command": "codemp_join",
"arg": {
// 'workspace_id': 'asd'
// 'buffer_id': 'test'
},
},
{ {
"caption": "Codemp: Share", "caption": "Codemp: Share",
"command": "codemp_share", "command": "codemp_share",
@ -39,29 +46,21 @@
}, },
{ {
"caption": "Codemp: Join Workspace", "caption": "Codemp: Join Workspace",
"command": "codemp_join", "command": "codemp_join_workspace",
"arg": { "arg": {
// 'server_buffer' : 'test' // 'workspace_id' : 'asd'
}, },
}, },
{ {
"caption": "Codemp: Join buffer", "caption": "Codemp: Join buffer",
"command": "codemp_attach", "command": "codemp_join_buffer",
"arg": { "arg": {
// 'server_buffer' : 'test' // 'buffer_id' : 'test'
}, },
}, },
{
"caption": "Codemp: Disconnect Buffer",
"command": "codemp_disconnect_buffer",
"arg": {
// 'remote_name' : 'name of buffer to disconnect'
}
},
{ {
"caption": "Codemp: Disconnect Client", "caption": "Codemp: Disconnect Client",
"command": "codemp_disconnect", "command": "codemp_disconnect",
"arg": { "arg": {}
}
}, },
] ]

218
plugin.py
View file

@ -3,7 +3,11 @@ import sublime_plugin
from Codemp.src.codemp_client import VirtualClient from Codemp.src.codemp_client import VirtualClient
from Codemp.src.TaskManager import rt from Codemp.src.TaskManager import rt
from Codemp.src.utils import status_log, is_active, safe_listener_detach from Codemp.src.utils import status_log
from Codemp.src.utils import safe_listener_detach
from Codemp.src.utils import get_contents
from Codemp.src.utils import populate_view
from Codemp.src.utils import get_view_from_local_path
import Codemp.src.globals as g import Codemp.src.globals as g
CLIENT = None CLIENT = None
@ -34,7 +38,6 @@ async def disconnect_client():
for vws in CLIENT.workspaces.values(): for vws in CLIENT.workspaces.values():
vws.cleanup() vws.cleanup()
# fix me: allow riconnections
CLIENT = None CLIENT = None
@ -45,34 +48,6 @@ def plugin_unloaded():
status_log("plugin unloaded") status_log("plugin unloaded")
# Utils
##############################################################################
def get_contents(view):
r = sublime.Region(0, view.size())
return view.substr(r)
def populate_view(view, content):
view.run_command(
"codemp_replace_text",
{
"start": 0,
"end": view.size(),
"content": content,
"change_id": view.change_id(),
},
)
def get_view_from_local_path(path):
for window in sublime.windows():
for view in window.views():
if view.file_name() == path:
return view
# Listeners # Listeners
############################################################################## ##############################################################################
class EventListener(sublime_plugin.EventListener): class EventListener(sublime_plugin.EventListener):
@ -104,6 +79,8 @@ class CodempClientViewEventListener(sublime_plugin.ViewEventListener):
def on_activated(self): def on_activated(self):
global TEXT_LISTENER global TEXT_LISTENER
# sublime has no proper way to check if a view gained or lost input focus outside of this
# callback (i know right?), so we have to manually keep track of which view has the focus
g.ACTIVE_CODEMP_VIEW = self.view.id() g.ACTIVE_CODEMP_VIEW = self.view.id()
print("view {} activated".format(self.view.id())) print("view {} activated".format(self.view.id()))
TEXT_LISTENER.attach(self.view.buffer()) TEXT_LISTENER.attach(self.view.buffer())
@ -126,8 +103,6 @@ class CodempClientViewEventListener(sublime_plugin.ViewEventListener):
vbuff.cleanup() vbuff.cleanup()
CLIENT.tm.stop_and_pop(f"{g.BUFFCTL_TASK_PREFIX}-{vbuff.codemp_id}") CLIENT.tm.stop_and_pop(f"{g.BUFFCTL_TASK_PREFIX}-{vbuff.codemp_id}")
# have to run the detach logic in sync, to keep a valid reference to the view.
# sublime_asyncio.sync(buffer.detach(_client))
class CodempClientTextChangeListener(sublime_plugin.TextChangeListener): class CodempClientTextChangeListener(sublime_plugin.TextChangeListener):
@ -151,9 +126,14 @@ class CodempClientTextChangeListener(sublime_plugin.TextChangeListener):
# Commands: # Commands:
# codemp_connect: connect to a server. # codemp_connect: connect to a server.
# codemp_join: join a workspace with a given name within the server. # codemp_join: shortcut command if you already know both workspace id
# codemp_share: shares a buffer with a given name in the workspace. # and buffer id
# codemp_join_workspace: joins a specific workspace, without joining also a buffer
# codemp_join_buffer: joins a specific buffer within the current active workspace
# codemp_share: ??? todo!()
# codemp_disconnect: manually call the disconnection, triggering the cleanup and dropping
# the connection
# #
# Internal commands: # Internal commands:
# replace_text: swaps the content of a view with the given text. # replace_text: swaps the content of a view with the given text.
@ -167,75 +147,103 @@ 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 ServerHostInputHandler() return ServerHost()
def input_description(self): def input_description(self):
return "Server host:" return "Server host:"
class ServerHostInputHandler(sublime_plugin.TextInputHandler): # Generic Join Command
def initial_text(self): #############################################################################
return "http://127.0.0.1:50051" async def JoinCommand(client: VirtualClient, workspace_id: str, buffer_id: str):
vws = await client.join_workspace(workspace_id)
if vws is not None:
await vws.attach(buffer_id)
class CodempJoinCommand(sublime_plugin.WindowCommand):
def run(self, workspace_id, buffer_id):
global CLIENT
rt.dispatch(JoinCommand(CLIENT, workspace_id, buffer_id))
def input_description(self):
return "Join:"
def input(self, args):
if "workspace_id" not in args:
return WorkspaceIdAndFollowup()
# Join Workspace Command # Join Workspace Command
############################################################################# #############################################################################
class CodempJoinCommand(sublime_plugin.WindowCommand): class CodempJoinWorkspaceCommand(sublime_plugin.WindowCommand):
def run(self, workspace_id): def run(self, workspace_id):
global CLIENT global CLIENT
rt.dispatch(CLIENT.join_workspace(workspace_id)) rt.dispatch(CLIENT.join_workspace(workspace_id))
def input_description(self): def input_description(self):
return "Join Workspace:" return "Join specific workspace"
def input(self, args): def input(self, args):
if "workspace_id" not in args: if "workspace_id" not in args:
return WorkspaceIdInputHandler() return RawWorkspaceId()
class WorkspaceIdInputHandler(sublime_plugin.TextInputHandler):
def initial_text(self):
return "What workspace should I join?"
# Join Buffer Command # Join Buffer Command
############################################################################# #############################################################################
class CodempAttachCommand(sublime_plugin.WindowCommand): class CodempJoinBufferCommand(sublime_plugin.WindowCommand):
def run(self, buffer_id): def run(self, buffer_id):
global CLIENT global CLIENT
if CLIENT.active_workspace is not None: if CLIENT.active_workspace is not None:
rt.dispatch(CLIENT.active_workspace.attach(buffer_id))
else:
sublime.error_message( sublime.error_message(
"You haven't joined any worksapce yet. use `Codemp: Join Workspace`" "You haven't joined any worksapce yet. \
use `Codemp: Join Workspace` or `Codemp: Join`"
) )
return
rt.dispatch(CLIENT.active_workspace.attach(buffer_id))
def input_description(self): def input_description(self):
return "Join Buffer in workspace:" return "Join buffer in the active workspace"
# This is awful, fix it # This is awful, fix it
def input(self, args): def input(self, args):
global CLIENT global CLIENT
if CLIENT.active_workspace is not None: if CLIENT.active_workspace is None:
if "buffer_id" not in args:
existing_buffers = CLIENT.active_workspace.handle.filetree()
if len(existing_buffers) == 0:
return BufferIdInputHandler()
else:
return ListBufferIdInputHandler()
else:
sublime.error_message( sublime.error_message(
"You haven't joined any worksapce yet. use `Codemp: Join Workspace`" "You haven't joined any worksapce yet. \
use `Codemp: Join Workspace` or `Codemp: Join`"
) )
return return
if "buffer_id" not in args:
existing_buffers = CLIENT.active_workspace.handle.filetree()
if len(existing_buffers) == 0:
return RawBufferId()
else:
return ListBufferId()
# 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)
# Input Handlers
##############################################################################
class ServerHost(sublime_plugin.TextInputHandler):
def name(self):
return "server_host"
class BufferIdInputHandler(sublime_plugin.TextInputHandler):
def initial_text(self): def initial_text(self):
return "No buffers found in the workspace. Create new: " return "http://127.0.0.1:50051"
class ListBufferIdInputHandler(sublime_plugin.ListInputHandler): class ListBufferId(sublime_plugin.ListInputHandler):
def name(self): def name(self):
return "buffer_id" return "buffer_id"
@ -245,18 +253,35 @@ class ListBufferIdInputHandler(sublime_plugin.ListInputHandler):
def next_input(self, args): def next_input(self, args):
if "buffer_id" not in args: if "buffer_id" not in args:
return BufferIdInputHandler() return RawBufferId()
# Text Change Command class RawWorkspaceId(sublime_plugin.TextInputHandler):
############################################################################# def name(self):
# we call this command manually to have access to the edit token. return "workspace_id"
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)
def placeholder(self):
return "Workspace Id"
class WorkspaceIdAndFollowup(sublime_plugin.TextInputHandler):
def name(self):
return "workspace_id"
def placeholder(self):
return "Workspace Id"
def next_input(self, args):
if "buffer_id" not in args:
return RawBufferId()
class RawBufferId(sublime_plugin.TextInputHandler):
def name(self):
return "buffer_id"
def placeholder(self):
return "Buffer Id"
# Share Command # Share Command
# ############################################################################# # #############################################################################
@ -272,27 +297,6 @@ class CodempReplaceTextCommand(sublime_plugin.TextCommand):
# return "Share Buffer:" # return "Share Buffer:"
# class SublimeBufferPathInputHandler(sublime_plugin.ListInputHandler):
# def list_items(self):
# ret_list = []
# for window in sublime.windows():
# for view in window.views():
# if view.file_name():
# ret_list.append(view.file_name())
# return ret_list
# def next_input(self, args):
# if "server_id" not in args:
# return ServerIdInputHandler()
# class ServerIdInputHandler(sublime_plugin.TextInputHandler):
# def initial_text(self):
# return "Buffer name on server"
# Disconnect Command # Disconnect Command
############################################################################# #############################################################################
class CodempDisconnectCommand(sublime_plugin.WindowCommand): class CodempDisconnectCommand(sublime_plugin.WindowCommand):
@ -300,7 +304,7 @@ class CodempDisconnectCommand(sublime_plugin.WindowCommand):
rt.sync(disconnect_client()) rt.sync(disconnect_client())
# Proxy Commands ( NOT USED ) # Proxy Commands ( NOT USED, left just in case we need it again. )
############################################################################# #############################################################################
# class ProxyCodempShareCommand(sublime_plugin.WindowCommand): # class ProxyCodempShareCommand(sublime_plugin.WindowCommand):
# # on_window_command, does not trigger when called from the command palette # # on_window_command, does not trigger when called from the command palette
@ -314,30 +318,6 @@ class CodempDisconnectCommand(sublime_plugin.WindowCommand):
# #
# def input_description(self): # def input_description(self):
# return 'Share Buffer:' # return 'Share Buffer:'
#
# class ProxyCodempJoinCommand(sublime_plugin.WindowCommand):
# def run(self, **kwargs):
# self.window.run_command("codemp_join", kwargs)
#
# def input(self, args):
# if 'server_buffer' not in args:
# return ServerBufferInputHandler()
#
# def input_description(self):
# return 'Join Buffer:'
#
# class ProxyCodempConnectCommand(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_connect", kwargs)
#
# def input(self, args):
# if 'server_host' not in args:
# return ServerHostInputHandler()
#
# def input_description(self):
# return 'Server host:'
# NOT NEEDED ANYMORE # NOT NEEDED ANYMORE

View file

@ -3,8 +3,7 @@ from typing import Optional, Callable
import sublime import sublime
import asyncio # noqa: F401 import asyncio
import typing # noqa: F401
import tempfile import tempfile
import os import os
import shutil import shutil
@ -12,7 +11,7 @@ import shutil
import Codemp.src.globals as g import Codemp.src.globals as g
from Codemp.src.wrappers import BufferController, Workspace, Client from Codemp.src.wrappers import BufferController, Workspace, Client
from Codemp.src.utils import status_log, is_active, rowcol_to_region from Codemp.src.utils import status_log, rowcol_to_region
from Codemp.src.TaskManager import TaskManager from Codemp.src.TaskManager import TaskManager
@ -167,45 +166,42 @@ class VirtualClient:
self.active_workspace = ws self.active_workspace = ws
self.spawn_cursor_manager(ws) self.spawn_cursor_manager(ws)
def get_by_local(self, id):
for vws in self.workspaces.values():
vbuff = vws.get_by_local(id)
if vbuff is not None:
return
async def connect(self, server_host: str): async def connect(self, server_host: str):
status_log(f"Connecting to {server_host}") status_log(f"Connecting to {server_host}")
try: try:
await self.handle.connect(server_host) await self.handle.connect(server_host)
except Exception: except Exception as e:
sublime.error_message("Could not connect:\n Make sure the server is up.") sublime.error_message(f"Could not connect:\n Make sure the server is up.\nerror: {e}")
return return
id = await self.handle.user_id() id = await self.handle.user_id()
print(f"TEST: {id}") status_log(f"Connected to '{server_host}' with user id: {id}")
async def join_workspace( async def join_workspace(
self, workspace_id: str, user="sublime", password="***REMOVED***" self, workspace_id: str, user="sublime", password="***REMOVED***"
): ) -> VirtualWorkspace:
try: try:
status_log(f"Logging into workspace: '{workspace_id}'") status_log(f"Logging into workspace: '{workspace_id}'")
await self.handle.login(user, password, workspace_id) await self.handle.login(user, password, workspace_id)
except Exception as e: except Exception as e:
sublime.error_message(f"Failed to login to workspace '{workspace_id}': {e}") status_log(f"Failed to login to workspace '{workspace_id}'.\nerror: {e}")
sublime.error_message(f"Failed to login to workspace '{workspace_id}'.\nerror: {e}")
return return
try: try:
status_log(f"Joining workspace: '{workspace_id}'") status_log(f"Joining workspace: '{workspace_id}'")
workspace_handle = await self.handle.join_workspace(workspace_id) workspace_handle = await self.handle.join_workspace(workspace_id)
except Exception as e: except Exception as e:
sublime.error_message(f"Could not join workspace '{workspace_id}': {e}") status_log(f"Could not join workspace '{workspace_id}'.\nerror: {e}")
sublime.error_message(f"Could not join workspace '{workspace_id}'.\nerror: {e}")
return return
vws = VirtualWorkspace(self, workspace_id, workspace_handle) vws = VirtualWorkspace(self, workspace_id, workspace_handle)
self.make_active(vws) self.make_active(vws)
self.workspaces[workspace_id] = vws self.workspaces[workspace_id] = vws
return vws
def spawn_cursor_manager(self, virtual_workspace: VirtualWorkspace): def spawn_cursor_manager(self, virtual_workspace: VirtualWorkspace):
async def move_cursor_task(vws): async def move_cursor_task(vws):
status_log(f"spinning up cursor worker for workspace '{vws.id}'...") status_log(f"spinning up cursor worker for workspace '{vws.id}'...")

View file

@ -13,12 +13,30 @@ def rowcol_to_region(view, start, end):
return sublime.Region(a, b) return sublime.Region(a, b)
def is_active(view):
if view.window().active_view() == view:
return True
return False
def safe_listener_detach(txt_listener: sublime_plugin.TextChangeListener): def safe_listener_detach(txt_listener: sublime_plugin.TextChangeListener):
if txt_listener.is_attached(): if txt_listener.is_attached():
txt_listener.detach() txt_listener.detach()
def get_contents(view):
r = sublime.Region(0, view.size())
return view.substr(r)
def populate_view(view, content):
view.run_command(
"codemp_replace_text",
{
"start": 0,
"end": view.size(),
"content": content,
"change_id": view.change_id(),
},
)
def get_view_from_local_path(path):
for window in sublime.windows():
for view in window.views():
if view.file_name() == path:
return view