2023-08-17 18:39:47 +02:00
|
|
|
import sublime
|
|
|
|
import sublime_plugin
|
|
|
|
|
|
|
|
# import Codemp.codemp_client as codemp
|
|
|
|
from Codemp.src.codemp_client import *
|
|
|
|
import Codemp.ext.sublime_asyncio as sublime_asyncio
|
|
|
|
import asyncio
|
|
|
|
import time
|
|
|
|
|
|
|
|
# UGLYYYY, find a way to not have global variables laying around.
|
|
|
|
_tasks = []
|
2023-08-25 14:29:11 +02:00
|
|
|
_client = None
|
2023-08-17 18:39:47 +02:00
|
|
|
_cursor_controller = None
|
2023-08-25 14:29:11 +02:00
|
|
|
_buffer_controller = None
|
2023-08-17 18:39:47 +02:00
|
|
|
_setting_key = "codemp_buffer"
|
|
|
|
|
|
|
|
def store_task(name = None):
|
|
|
|
def store_named_task(task):
|
|
|
|
global _tasks
|
|
|
|
task.set_name(name)
|
|
|
|
_tasks.append(task)
|
|
|
|
|
|
|
|
return store_named_task
|
|
|
|
|
|
|
|
def plugin_loaded():
|
2023-08-25 14:29:11 +02:00
|
|
|
global _client
|
|
|
|
_client = CodempClient()
|
2023-08-17 18:39:47 +02:00
|
|
|
sublime_asyncio.acquire() # instantiate and start a global event loop.
|
|
|
|
|
|
|
|
class CodempClientViewEventListener(sublime_plugin.ViewEventListener):
|
|
|
|
@classmethod
|
|
|
|
def is_applicable(cls, settings):
|
|
|
|
return "codemp_buffer" in settings
|
|
|
|
|
|
|
|
def on_selection_modified_async(self):
|
|
|
|
global _cursor_controller
|
|
|
|
if _cursor_controller:
|
|
|
|
sublime_asyncio.dispatch(send_selection(self.view))
|
|
|
|
|
|
|
|
def on_close(self):
|
|
|
|
self.view.settings()["codemp_buffer"] = False
|
|
|
|
|
|
|
|
class CodempClientTextChangeListener(sublime_plugin.TextChangeListener):
|
|
|
|
@classmethod
|
|
|
|
def is_applicable(cls, buffer):
|
|
|
|
for view in buffer.views():
|
|
|
|
if "codemp_buffer" in view.settings():
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def on_text_changed(self, changes):
|
2023-08-25 14:29:11 +02:00
|
|
|
global _buffer_controller
|
|
|
|
if _buffer_controller:
|
2023-08-17 18:39:47 +02:00
|
|
|
for change in changes:
|
|
|
|
sublime_asyncio.dispatch(apply_changes(change))
|
|
|
|
|
|
|
|
async def apply_changes(change):
|
2023-08-25 14:29:11 +02:00
|
|
|
global _buffer_controller
|
2023-08-17 18:39:47 +02:00
|
|
|
|
|
|
|
text = change.str
|
|
|
|
skip = change.a.pt
|
|
|
|
if change.len_utf8 == 0: # we are inserting new text.
|
2023-08-25 14:29:11 +02:00
|
|
|
tail = len(_buffer_controller.get_content()) - skip
|
2023-08-17 18:39:47 +02:00
|
|
|
else: # we are changing an existing region of text of length len_utf8
|
2023-08-25 14:29:11 +02:00
|
|
|
tail = len(_buffer_controller.get_content()) - skip - change.len_utf8
|
2023-08-17 18:39:47 +02:00
|
|
|
|
2023-08-25 14:29:11 +02:00
|
|
|
tail_skip = len(_buffer_controller.get_content()) - tail
|
2023-08-17 18:39:47 +02:00
|
|
|
print("[buff change]", skip, text, tail_skip)
|
2023-08-25 14:29:11 +02:00
|
|
|
await _buffer_controller.apply(skip, text, tail)
|
2023-08-17 18:39:47 +02:00
|
|
|
|
|
|
|
async def make_connection(server_host):
|
|
|
|
global _client
|
|
|
|
|
|
|
|
if _client.ready:
|
|
|
|
sublime.message_dialog("A connection already exists.")
|
|
|
|
return
|
|
|
|
|
|
|
|
sublime.status_message("[codemp] Connecting to {}".format(server_host))
|
|
|
|
print("[codemp] Connecting to {}".format(server_host))
|
|
|
|
await _client.connect(server_host)
|
|
|
|
|
|
|
|
id = await _client.get_id()
|
|
|
|
sublime.status_message("[codemp] Connected with client ID: {}".format(id))
|
|
|
|
print("[codemp] Connected with client ID: ", id)
|
|
|
|
|
|
|
|
async def move_cursor(usr, caller, path, start, end):
|
|
|
|
print(usr, caller, start, end)
|
|
|
|
|
|
|
|
async def sync_buffer(caller, start, end, txt):
|
|
|
|
print("[buffer]", caller, start, end, txt)
|
|
|
|
|
|
|
|
async def share_buffer(buffer):
|
|
|
|
global _client
|
|
|
|
global _cursor_controller
|
2023-08-25 14:29:11 +02:00
|
|
|
global _buffer_controller
|
2023-08-17 18:39:47 +02:00
|
|
|
|
|
|
|
if not _client.ready:
|
|
|
|
sublime.error_message("No connected client.")
|
|
|
|
return
|
|
|
|
|
|
|
|
sublime.status_message("[codemp] Sharing buffer {}".format(buffer))
|
|
|
|
print("[codemp] Sharing buffer {}".format(buffer))
|
|
|
|
|
|
|
|
view = get_matching_view(buffer)
|
|
|
|
contents = get_contents(view)
|
|
|
|
created = await _client.create(view.file_name(), contents)
|
|
|
|
if not created:
|
|
|
|
sublime.error_message("Could not share buffer.")
|
|
|
|
return
|
|
|
|
|
2023-08-25 14:29:11 +02:00
|
|
|
_buffer_controller = await _client.attach(buffer)
|
|
|
|
_buffer_controller.callback(sync_buffer, _client.id)
|
2023-08-17 18:39:47 +02:00
|
|
|
|
|
|
|
_cursor_controller = await _client.listen()
|
|
|
|
_cursor_controller.callback(move_cursor, _client.id)
|
|
|
|
|
|
|
|
if not _cursor_controller:
|
|
|
|
sublime.error_message("Could not subsribe a listener.")
|
|
|
|
return
|
2023-08-25 14:29:11 +02:00
|
|
|
if not _buffer_controller:
|
2023-08-17 18:39:47 +02:00
|
|
|
sublime.error_message("Could not attach to the buffer.")
|
|
|
|
return
|
|
|
|
|
|
|
|
sublime.status_message("[codemp] Listening")
|
|
|
|
print("[codemp] Listening")
|
|
|
|
|
|
|
|
view.settings()["codemp_buffer"] = True
|
|
|
|
|
|
|
|
async def join_buffer(window, buffer):
|
|
|
|
global _client
|
|
|
|
global _cursor_controller
|
2023-08-25 14:29:11 +02:00
|
|
|
global _buffer_controller
|
2023-08-17 18:39:47 +02:00
|
|
|
|
|
|
|
if not _client.ready:
|
|
|
|
sublime.error_message("No connected client.")
|
|
|
|
return
|
|
|
|
|
|
|
|
view = get_matching_view(buffer)
|
|
|
|
|
|
|
|
sublime.status_message("[codemp] Joining buffer {}".format(buffer))
|
|
|
|
print("[codemp] Joining buffer {}".format(buffer))
|
|
|
|
|
2023-08-25 14:29:11 +02:00
|
|
|
_buffer_controller = await _client.attach(buffer)
|
|
|
|
content = _buffer_controller.get_content()
|
2023-08-17 18:39:47 +02:00
|
|
|
view.run_command("codemp_replace_view", {"content": content})
|
|
|
|
|
|
|
|
_cursor_controller = await _client.listen()
|
|
|
|
_cursor_controller.callback(move_cursor)
|
|
|
|
|
|
|
|
|
|
|
|
if not _cursor_controller:
|
|
|
|
sublime.error_message("Could not subsribe a listener.")
|
|
|
|
return
|
2023-08-25 14:29:11 +02:00
|
|
|
if not _buffer_controller:
|
2023-08-17 18:39:47 +02:00
|
|
|
sublime.error_message("Could not attach to the buffer.")
|
|
|
|
return
|
|
|
|
|
|
|
|
sublime.status_message("[codemp] Listening")
|
|
|
|
print("[codemp] Listening")
|
|
|
|
|
|
|
|
view.settings()["codemp_buffer"] = True
|
|
|
|
|
|
|
|
async def send_selection(view):
|
|
|
|
global _cursor_controller
|
|
|
|
|
|
|
|
path = view.file_name()
|
|
|
|
region = view.sel()[0] # TODO: only the last placed cursor/selection.
|
|
|
|
start = view.rowcol(region.begin()) #only counts UTF8 chars
|
|
|
|
end = view.rowcol(region.end())
|
|
|
|
|
|
|
|
await _cursor_controller.send(path, start, end)
|
|
|
|
|
|
|
|
def get_contents(view):
|
|
|
|
r = sublime.Region(0, view.size())
|
|
|
|
return view.substr(r)
|
|
|
|
|
|
|
|
def get_matching_view(path):
|
|
|
|
for window in sublime.windows():
|
|
|
|
for view in window.views():
|
|
|
|
if view.file_name() == path:
|
|
|
|
return view
|
|
|
|
|
|
|
|
|
|
|
|
# See the proxy command class at the bottom
|
|
|
|
class CodempConnectCommand(sublime_plugin.WindowCommand):
|
|
|
|
def run(self, server_host):
|
|
|
|
sublime_asyncio.dispatch(make_connection(server_host))
|
|
|
|
|
|
|
|
# see proxy command at the bottom
|
|
|
|
class CodempShareCommand(sublime_plugin.WindowCommand):
|
|
|
|
def run(self, buffer):
|
|
|
|
sublime_asyncio.dispatch(share_buffer(buffer))
|
|
|
|
|
|
|
|
# see proxy command at the bottom
|
|
|
|
class CodempJoinCommand(sublime_plugin.WindowCommand):
|
|
|
|
def run(self, buffer):
|
|
|
|
sublime_asyncio.dispatch(join_buffer(self.window, buffer))
|
|
|
|
|
|
|
|
class CodempPopulateView(sublime_plugin.TextCommand):
|
|
|
|
def run(self, edit, content):
|
|
|
|
self.view.replace(edit, sublime.Region(0, self.view.size()), content)
|
|
|
|
|
|
|
|
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:'
|
|
|
|
|
|
|
|
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 'buffer' not in args:
|
|
|
|
return BufferInputHandler()
|
|
|
|
|
|
|
|
def input_description(self):
|
|
|
|
return 'Share Buffer:'
|
|
|
|
|
|
|
|
|
|
|
|
class ProxyCodempJoinCommand(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_join", kwargs)
|
|
|
|
|
|
|
|
def input(self, args):
|
|
|
|
if 'buffer' not in args:
|
|
|
|
return BufferInputHandler()
|
|
|
|
|
|
|
|
def input_description(self):
|
|
|
|
return 'Join Buffer:'
|
|
|
|
|
|
|
|
class BufferInputHandler(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
|
|
|
|
|
|
|
|
class ServerHostInputHandler(sublime_plugin.TextInputHandler):
|
|
|
|
def initial_text(self):
|
|
|
|
return "http://[::1]:50051"
|