switched to more solid (but uglier) way to check for input focus.

Changed how to get from a sublime view to a virtual buffer by means of tags. (maybe slow)
added some syntactic sugar


Former-commit-id: 2003fe0838af7e47258c6d10d84e2142dfa3da1b
This commit is contained in:
Camillo Schenone 2024-02-23 17:49:26 +01:00
parent ff883c8e58
commit fa5f03bd6b
3 changed files with 48 additions and 22 deletions

View file

@ -84,15 +84,17 @@ class EventListener(sublime_plugin.EventListener):
class CodempClientViewEventListener(sublime_plugin.ViewEventListener): class CodempClientViewEventListener(sublime_plugin.ViewEventListener):
@classmethod @classmethod
def is_applicable(cls, settings): def is_applicable(cls, settings):
return settings.get(g.CODEMP_BUFFER_VIEW_TAG, False) return settings.get(g.CODEMP_BUFFER_TAG, False)
@classmethod @classmethod
def applies_to_primary_view_only(cls): def applies_to_primary_view_only(cls):
return False return False
def on_selection_modified_async(self): def on_selection_modified_async(self):
s = self.view.settings()
global CLIENT global CLIENT
vbuff = CLIENT.active_workspace.get_by_local(self.view.buffer_id()) vbuff = CLIENT[s[g.CODEMP_WORKSPACE_ID]].get_by_local(self.view.buffer_id())
if vbuff is not None: if vbuff is not None:
CLIENT.send_cursor(vbuff) CLIENT.send_cursor(vbuff)
@ -101,21 +103,26 @@ class CodempClientViewEventListener(sublime_plugin.ViewEventListener):
# When we defocus, we detach it. # When we defocus, we detach it.
def on_activated(self): def on_activated(self):
global TEXT_LISTENER global TEXT_LISTENER
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())
def on_deactivated(self): def on_deactivated(self):
global TEXT_LISTENER global TEXT_LISTENER
g.ACTIVE_CODEMP_VIEW = None
print("view {} deactivated".format(self.view.id())) print("view {} deactivated".format(self.view.id()))
safe_listener_detach(TEXT_LISTENER) safe_listener_detach(TEXT_LISTENER)
def on_pre_close(self): def on_pre_close(self):
global TEXT_LISTENER global TEXT_LISTENER
if is_active(self.view): if self.view.id() == g.ACTIVE_CODEMP_VIEW:
safe_listener_detach(TEXT_LISTENER) safe_listener_detach(TEXT_LISTENER)
global CLIENT global CLIENT
vbuff = CLIENT.active_workspace.get_by_local(self.view.buffer_id()) wsid = self.view.settings().get(g.CODEMP_WORKSPACE_ID)
vbuff = CLIENT[wsid].get_by_local(self.view.buffer_id())
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}")
@ -132,17 +139,14 @@ class CodempClientTextChangeListener(sublime_plugin.TextChangeListener):
# blocking :D # blocking :D
def on_text_changed(self, changes): def on_text_changed(self, changes):
if ( s = self.buffer.primary_view().settings()
self.buffer.primary_view() if s.get(g.CODEMP_IGNORE_NEXT_TEXT_CHANGE, None):
.settings()
.get(g.CODEMP_IGNORE_NEXT_TEXT_CHANGE, None)
):
status_log("ignoring echoing back the change.") status_log("ignoring echoing back the change.")
self.view.settings()[g.CODEMP_IGNORE_NEXT_TEXT_CHANGE] = False s[g.CODEMP_IGNORE_NEXT_TEXT_CHANGE] = False
return return
global CLIENT global CLIENT
vbuff = CLIENT.active_workspace.get_by_local(self.buffer.id()) vbuff = CLIENT[s[g.CODEMP_WORKSPACE_ID]].get_by_local(self.buffer.id())
CLIENT.send_buffer_change(changes, vbuff) CLIENT.send_buffer_change(changes, vbuff)
@ -250,7 +254,6 @@ class ListBufferIdInputHandler(sublime_plugin.ListInputHandler):
class CodempReplaceTextCommand(sublime_plugin.TextCommand): class CodempReplaceTextCommand(sublime_plugin.TextCommand):
def run(self, edit, start, end, content, change_id): def run(self, edit, start, end, content, change_id):
# we modify the region to account for any change that happened in the mean time # we modify the region to account for any change that happened in the mean time
print("running the replace command, launche manually.")
region = self.view.transform_region_from(sublime.Region(start, end), change_id) region = self.view.transform_region_from(sublime.Region(start, end), change_id)
self.view.replace(edit, region, content) self.view.replace(edit, region, content)

View file

@ -7,6 +7,7 @@ import asyncio # noqa: F401
import typing # noqa: F401 import typing # noqa: F401
import tempfile import tempfile
import os import os
import shutil
import Codemp.src.globals as g import Codemp.src.globals as g
@ -42,13 +43,19 @@ class VirtualBuffer:
self.view.set_scratch(True) self.view.set_scratch(True)
# mark the view as a codemp view # mark the view as a codemp view
s = self.view.settings()
self.view.set_status(g.SUBLIME_STATUS_ID, "[Codemp]") self.view.set_status(g.SUBLIME_STATUS_ID, "[Codemp]")
self.view.settings()[g.CODEMP_BUFFER_VIEW_TAG] = True s[g.CODEMP_BUFFER_TAG] = True
s[g.CODEMP_REMOTE_ID] = self.codemp_id
s[g.CODEMP_WORKSPACE_ID] = self.workspace.id
def cleanup(self): def cleanup(self):
os.remove(self.tmpfile) os.remove(self.tmpfile)
# cleanup views # cleanup views
del self.view.settings()[g.CODEMP_BUFFER_VIEW_TAG] s = self.view.settings()
del s[g.CODEMP_BUFFER_TAG]
del s[g.CODEMP_REMOTE_ID]
del s[g.CODEMP_WORKSPACE_ID]
self.view.erase_status(g.SUBLIME_STATUS_ID) self.view.erase_status(g.SUBLIME_STATUS_ID)
# this does nothing for now. figure out a way later # this does nothing for now. figure out a way later
# self.view.erase_regions(g.SUBLIME_REGIONS_PREFIX) # self.view.erase_regions(g.SUBLIME_REGIONS_PREFIX)
@ -65,7 +72,7 @@ class VirtualWorkspace:
self.handle = handle self.handle = handle
self.curctl = handle.cursor() self.curctl = handle.cursor()
# mapping local buffer ids -> remote ids # mapping remote ids -> local ids
self.id_map: dict[str, str] = {} self.id_map: dict[str, str] = {}
self.active_buffers: dict[str, VirtualBuffer] = {} self.active_buffers: dict[str, VirtualBuffer] = {}
@ -85,8 +92,8 @@ class VirtualWorkspace:
self.sublime_window.set_project_data(proj_data) self.sublime_window.set_project_data(proj_data)
def add_buffer(self, remote_id: str, vbuff: VirtualBuffer): def add_buffer(self, remote_id: str, vbuff: VirtualBuffer):
self.id_map[vbuff.view.buffer_id()] = remote_id self.id_map[remote_id] = vbuff.view.buffer_id()
self.active_buffers[remote_id] = vbuff self.active_buffers[vbuff.view.buffer_id()] = vbuff
def cleanup(self): def cleanup(self):
# the worskpace only cares about closing the various open views on its buffers. # the worskpace only cares about closing the various open views on its buffers.
@ -104,13 +111,13 @@ class VirtualWorkspace:
d["folders"] = newf d["folders"] = newf
self.sublime_window.set_project_data(d) self.sublime_window.set_project_data(d)
status_log(f"cleaning up virtual workspace '{self.id}'") status_log(f"cleaning up virtual workspace '{self.id}'")
os.removedirs(self.rootdir) shutil.rmtree(self.rootdir, ignore_errors=True)
def get_by_local(self, local_id: str) -> Optional[VirtualBuffer]: def get_by_local(self, local_id: str) -> Optional[VirtualBuffer]:
return self.active_buffers.get(self.id_map.get(local_id)) return self.active_buffers.get(local_id)
def get_by_remote(self, remote_id: str) -> Optional[VirtualBuffer]: def get_by_remote(self, remote_id: str) -> Optional[VirtualBuffer]:
return self.active_buffers.get(remote_id) return self.active_buffers.get(self.id_map.get(remote_id))
async def attach(self, id: str): async def attach(self, id: str):
if id is None: if id is None:
@ -149,6 +156,9 @@ class VirtualClient:
self.active_workspace: VirtualWorkspace = None self.active_workspace: VirtualWorkspace = None
self.tm = TaskManager(on_exit) self.tm = TaskManager(on_exit)
def __getitem__(self, key: str):
return self.workspaces.get(key)
def make_active(self, ws: VirtualWorkspace): def make_active(self, ws: VirtualWorkspace):
# TODO: Logic to deal with swapping to and from workspaces, # TODO: Logic to deal with swapping to and from workspaces,
# what happens to the cursor tasks etc.. # what happens to the cursor tasks etc..
@ -157,6 +167,12 @@ 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:
@ -248,7 +264,10 @@ class VirtualClient:
# In case a change arrives to a background buffer, just apply it. # In case a change arrives to a background buffer, just apply it.
# We are not listening on it. Otherwise, interrupt the listening # We are not listening on it. Otherwise, interrupt the listening
# to avoid echoing back the change just received. # to avoid echoing back the change just received.
if is_active(vb.view): if vb.view.id() == g.ACTIVE_CODEMP_VIEW:
status_log(
"received a text change with view active, stopping the echo."
)
vb.view.settings()[g.CODEMP_IGNORE_NEXT_TEXT_CHANGE] = True vb.view.settings()[g.CODEMP_IGNORE_NEXT_TEXT_CHANGE] = True
# we need to go through a sublime text command, since the method, # we need to go through a sublime text command, since the method,

View file

@ -1,10 +1,14 @@
BUFFCTL_TASK_PREFIX = "buffer-ctl" BUFFCTL_TASK_PREFIX = "buffer-ctl"
CURCTL_TASK_PREFIX = "cursor-ctl" CURCTL_TASK_PREFIX = "cursor-ctl"
CODEMP_BUFFER_TAG = "codemp-buffer"
CODEMP_REMOTE_ID = "codemp-buffer-id"
CODEMP_WORKSPACE_ID = "codemp-workspace-id"
WORKSPACE_FOLDER_PREFIX = "CODEMP::" WORKSPACE_FOLDER_PREFIX = "CODEMP::"
SUBLIME_REGIONS_PREFIX = "codemp-cursors" SUBLIME_REGIONS_PREFIX = "codemp-cursors"
CODEMP_BUFFER_VIEW_TAG = "codemp-buffer"
SUBLIME_STATUS_ID = "z_codemp_buffer" SUBLIME_STATUS_ID = "z_codemp_buffer"
CODEMP_IGNORE_NEXT_TEXT_CHANGE = "codemp-skip-change-event" CODEMP_IGNORE_NEXT_TEXT_CHANGE = "codemp-skip-change-event"
ACTIVE_CODEMP_VIEW = None
PALETTE = [ PALETTE = [
"var(--redish)", "var(--redish)",