2024-08-29 08:17:52 +02:00
|
|
|
from __future__ import annotations
|
2024-09-28 19:55:20 +02:00
|
|
|
from typing import Optional, TYPE_CHECKING
|
2024-09-18 16:57:26 +02:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
from .workspace import WorkspaceManager
|
2024-09-28 19:55:20 +02:00
|
|
|
import codemp
|
2024-08-29 08:17:52 +02:00
|
|
|
|
2024-08-09 15:54:12 +02:00
|
|
|
import sublime
|
|
|
|
import os
|
2024-08-09 19:20:58 +02:00
|
|
|
import logging
|
2024-11-19 19:50:31 +01:00
|
|
|
import threading
|
2024-08-09 15:54:12 +02:00
|
|
|
|
2024-11-02 18:26:37 +01:00
|
|
|
from codemp import TextChange
|
2024-09-18 16:57:26 +02:00
|
|
|
from .. import globals as g
|
|
|
|
from ..utils import populate_view
|
2024-11-19 19:50:31 +01:00
|
|
|
from ..utils import get_contents
|
2024-09-18 16:57:26 +02:00
|
|
|
from ..utils import safe_listener_attach
|
|
|
|
from ..utils import safe_listener_detach
|
|
|
|
from ..utils import bidict
|
2024-08-09 19:20:58 +02:00
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
2024-08-09 15:54:12 +02:00
|
|
|
|
2024-09-28 19:55:20 +02:00
|
|
|
def bind_callback(v: sublime.View):
|
2024-11-19 19:50:31 +01:00
|
|
|
# we need this lock to prevent multiple instance of try_recv() to spin up
|
|
|
|
# which would cause out of order insertion of changes.
|
|
|
|
multi_tryrecv_lock = threading.Lock()
|
|
|
|
|
2024-09-28 19:55:20 +02:00
|
|
|
def _callback(bufctl: codemp.BufferController):
|
2024-09-17 17:20:00 +02:00
|
|
|
def _():
|
2024-11-19 19:50:31 +01:00
|
|
|
try:
|
|
|
|
# change_id = v.change_id()
|
|
|
|
change_id = None
|
|
|
|
while buffup := bufctl.try_recv().wait():
|
|
|
|
logger.debug("received remote buffer change!")
|
|
|
|
if buffup is None:
|
|
|
|
break
|
|
|
|
|
|
|
|
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 == 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.
|
|
|
|
|
2024-11-02 18:26:37 +01:00
|
|
|
change = buffup.change
|
|
|
|
v.run_command(
|
|
|
|
"codemp_replace_text",
|
|
|
|
{
|
2024-11-19 19:50:31 +01:00
|
|
|
"start": change.start_idx,
|
|
|
|
"end": change.end_idx,
|
2024-11-02 18:26:37 +01:00
|
|
|
"content": change.content,
|
|
|
|
"change_id": change_id,
|
|
|
|
}, # pyright: ignore
|
|
|
|
)
|
|
|
|
|
2024-11-19 19:50:31 +01:00
|
|
|
bufctl.ack(buffup.version)
|
|
|
|
except Exception as e:
|
|
|
|
raise e
|
|
|
|
finally:
|
|
|
|
logger.debug("releasing lock")
|
|
|
|
multi_tryrecv_lock.release()
|
|
|
|
|
|
|
|
if multi_tryrecv_lock.acquire(blocking=False):
|
|
|
|
logger.debug("acquiring lock")
|
|
|
|
sublime.set_timeout(_)
|
2024-09-28 19:55:20 +02:00
|
|
|
return _callback
|
2024-08-09 15:54:12 +02:00
|
|
|
|
2024-09-18 16:57:26 +02:00
|
|
|
class BufferManager():
|
|
|
|
def __init__(self, handle: codemp.BufferController, v: sublime.View, filename: str):
|
|
|
|
self.handle: codemp.BufferController = handle
|
|
|
|
self.view: sublime.View = v
|
|
|
|
self.id = self.handle.path()
|
|
|
|
self.filename = filename
|
2024-09-28 19:55:20 +02:00
|
|
|
self.handle.callback(bind_callback(self.view))
|
2024-08-23 20:59:06 +02:00
|
|
|
|
2024-08-31 15:24:22 +02:00
|
|
|
def __del__(self):
|
2024-09-18 16:57:26 +02:00
|
|
|
logger.debug(f"dropping buffer {self.id}")
|
|
|
|
self.handle.clear_callback()
|
2024-09-02 11:38:11 +02:00
|
|
|
|
2024-09-18 16:57:26 +02:00
|
|
|
def __hash__(self):
|
2024-09-02 11:38:11 +02:00
|
|
|
return hash(self.id)
|
|
|
|
|
2024-09-18 16:57:26 +02:00
|
|
|
def send_change(self, changes):
|
|
|
|
# we do not do any index checking, and trust sublime with providing the correct
|
|
|
|
# sequential indexing, assuming the changes are applied in the order they are received.
|
|
|
|
for change in changes:
|
|
|
|
region = sublime.Region(change.a.pt, change.b.pt)
|
2024-11-19 19:50:31 +01:00
|
|
|
# logger.debug(
|
|
|
|
# "sending txt change: Reg({} {}) -> '{}'".format(
|
|
|
|
# region.begin(), region.end(), change.str
|
|
|
|
# )
|
|
|
|
# )
|
2024-11-02 18:26:37 +01:00
|
|
|
|
2024-09-18 16:57:26 +02:00
|
|
|
# we must block and wait the send request to make sure the change went through ok
|
2024-11-02 18:26:37 +01:00
|
|
|
self.handle.send(TextChange(start=region.begin(), end=region.end(), content=change.str))
|
2024-08-31 15:24:22 +02:00
|
|
|
|
2024-09-02 11:38:11 +02:00
|
|
|
def sync(self, text_listener):
|
2024-09-18 16:57:26 +02:00
|
|
|
promise = self.handle.content()
|
2024-09-17 17:20:00 +02:00
|
|
|
def _():
|
2024-08-31 15:24:22 +02:00
|
|
|
content = promise.wait()
|
2024-11-19 19:50:31 +01:00
|
|
|
current_contents = get_contents(self.view)
|
|
|
|
if content == current_contents:
|
|
|
|
return
|
|
|
|
|
2024-09-02 11:38:11 +02:00
|
|
|
safe_listener_detach(text_listener)
|
2024-08-31 15:24:22 +02:00
|
|
|
populate_view(self.view, content)
|
2024-09-02 11:38:11 +02:00
|
|
|
safe_listener_attach(text_listener, self.view.buffer())
|
2024-11-19 19:50:31 +01:00
|
|
|
sublime.status_message("Syncd contents.")
|
2024-09-17 17:20:00 +02:00
|
|
|
sublime.set_timeout_async(_)
|
2024-08-31 15:24:22 +02:00
|
|
|
|
2024-09-18 16:57:26 +02:00
|
|
|
class BufferRegistry():
|
|
|
|
def __init__(self):
|
|
|
|
self._buffers: bidict[BufferManager, WorkspaceManager] = bidict()
|
|
|
|
|
2024-09-28 19:55:20 +02:00
|
|
|
def lookup(self, ws: Optional[WorkspaceManager] = None) -> list[BufferManager]:
|
2024-09-18 16:57:26 +02:00
|
|
|
if not ws:
|
|
|
|
return list(self._buffers.keys())
|
2024-11-02 18:26:37 +01:00
|
|
|
bfs = self._buffers.inverse.get(ws)
|
|
|
|
return bfs if bfs else []
|
2024-09-18 16:57:26 +02:00
|
|
|
|
2024-11-02 18:26:37 +01:00
|
|
|
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
|
2024-09-18 16:57:26 +02:00
|
|
|
|
|
|
|
def add(self, bhandle: codemp.BufferController, wsm: WorkspaceManager):
|
|
|
|
bid = bhandle.path()
|
2024-11-02 18:26:37 +01:00
|
|
|
# tmpfile = os.path.join(wsm.rootdir, bid)
|
|
|
|
# open(tmpfile, "a").close()
|
2024-11-19 19:50:31 +01:00
|
|
|
|
2024-09-18 16:57:26 +02:00
|
|
|
win = sublime.active_window()
|
|
|
|
view = win.open_file(bid)
|
|
|
|
view.set_scratch(True)
|
2024-11-02 18:26:37 +01:00
|
|
|
# view.retarget(tmpfile)
|
2024-09-18 16:57:26 +02:00
|
|
|
view.settings().set(g.CODEMP_VIEW_TAG, True)
|
|
|
|
view.settings().set(g.CODEMP_BUFFER_ID, bid)
|
|
|
|
view.set_status(g.SUBLIME_STATUS_ID, "[Codemp]")
|
|
|
|
|
2024-11-02 18:26:37 +01:00
|
|
|
tmpfile = "DISABLE"
|
2024-09-18 16:57:26 +02:00
|
|
|
bfm = BufferManager(bhandle, view, tmpfile)
|
2024-09-28 19:55:20 +02:00
|
|
|
self._buffers[bfm] = wsm
|
2024-09-18 16:57:26 +02:00
|
|
|
|
2024-11-02 18:26:37 +01:00
|
|
|
return bfm
|
|
|
|
|
|
|
|
def remove(self, bf: BufferManager | str):
|
2024-09-18 16:57:26 +02:00
|
|
|
if isinstance(bf, str):
|
|
|
|
bf = self.lookupId(bf)
|
|
|
|
|
|
|
|
del self._buffers[bf]
|
|
|
|
bf.view.close()
|
|
|
|
|
2024-09-28 19:55:20 +02:00
|
|
|
buffers = BufferRegistry()
|
2024-09-18 16:57:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
2024-08-23 20:59:06 +02:00
|
|
|
|