mirror of
https://github.com/hexedtech/codemp-sublime.git
synced 2024-12-04 20:04:52 +01:00
feat: move the library to be bundled together with the repo directly.
This approach will allow us to install directly through package control by just specifying the repo! Former-commit-id: 3df245186298042dfd4d8e0bf65844a283a571dd
This commit is contained in:
parent
b844594a53
commit
742787b413
18 changed files with 263 additions and 170 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -22,7 +22,6 @@ build/
|
|||
develop-eggs/
|
||||
dist/
|
||||
eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
|
|
0
.no-sublime-package
Normal file
0
.no-sublime-package
Normal file
|
@ -32,7 +32,7 @@
|
|||
"caption": "Codemp: Connect",
|
||||
"command": "codemp_connect",
|
||||
"args": {
|
||||
// "server_host": "http://[::1]:50051"
|
||||
"server_host": "http://codemp.dev:50053",
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
f0cef32c2765ba5d1bcaca6e11544caacf717006
|
|
@ -1 +0,0 @@
|
|||
8ee9409baac472917d5a57fff2f6e584d90588a7
|
105
lib/codemp-0.0.5.dist-info/METADATA
Normal file
105
lib/codemp-0.0.5.dist-info/METADATA
Normal file
|
@ -0,0 +1,105 @@
|
|||
Metadata-Version: 2.3
|
||||
Name: codemp
|
||||
Version: 0.0.5
|
||||
Classifier: Programming Language :: Python
|
||||
Summary: code multiplexer
|
||||
Keywords: codemp,cooperative,rust,python
|
||||
Home-Page: https://codemp.dev
|
||||
Author: alemi <me@alemi.dev>, zaaarf <me@zaaarf.foo>, frelodev <frelodev@gmail.com>, cschen <cschen@codemp.dev>
|
||||
Author-email: cschen <cschen@codemp.dev>, alemi <me@alemi.dev>, zaaarf <me@zaaarf.foo>, frelodev <frelodev@gmail.com>
|
||||
Maintainer-email: cschen <cschen@codemp.dev>
|
||||
License: GPL-3.0-only
|
||||
Requires-Python: >=3.8
|
||||
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
||||
Project-URL: repository, https://github.com/hexedtech/codemp.git
|
||||
|
||||
[![codemp](https://codemp.dev/static/banner.png)](https://codemp.dev)
|
||||
|
||||
[![Actions Status](https://github.com/hexedtech/codemp/actions/workflows/ci.yml/badge.svg)](https://github.com/hexedtech/codemp/actions)
|
||||
[![docs.rs](https://img.shields.io/docsrs/codemp)](https://docs.rs/codemp/0.7.0-beta.2/codemp/)
|
||||
[![Crates.io Version](https://img.shields.io/crates/v/codemp)](https://crates.io/crates/codemp)
|
||||
[![NPM Version](https://img.shields.io/npm/v/codemp)](https://npmjs.org/package/codemp)
|
||||
[![PyPI - Version](https://img.shields.io/pypi/v/codemp)](https://pypi.org/project/codemp)
|
||||
[![Crates.io License](https://img.shields.io/crates/l/codemp)](https://github.com/hexedtech/codemp/blob/dev/LICENSE)
|
||||
[![Gitter](https://img.shields.io/gitter/room/hexedtech/codemp)](https://gitter.im/hexedtech/codemp)
|
||||
|
||||
> `codemp` is a **collaborative** text editing solution to work remotely.
|
||||
|
||||
It seamlessly integrates in your editor providing remote cursors and instant text synchronization,
|
||||
as well as a remote virtual workspace for you and your team.
|
||||
|
||||
> `codemp` is build with state-of-the-art CRDT technology, guaranteeing eventual consistency.
|
||||
|
||||
This means everyone in a workspace will always be working on the exact same file _eventually_:
|
||||
even under unreliable networks or constrained resources, the underlying CRDT will always reach a
|
||||
convergent state across all users. Even with this baseline, `codemp`'s protocol is optimized for speed
|
||||
and low network footprint, meaning even slow connections can provide stable real-time editing.
|
||||
|
||||
The full documentation is available on [docs.rs](https://docs.rs/codemp/0.7.0-beta.2/codemp/).
|
||||
|
||||
# Usage
|
||||
`codemp` is primarily used as a plugin in your editor of choice.
|
||||
|
||||
## Installation
|
||||
> [!IMPORTANT]
|
||||
> The editor plugins are in active development. Expect frequent changes.
|
||||
|
||||
`codemp` is available as a plugin for a growing number of text editors. Currently we support:
|
||||
- [NeoVim](https://github.com/hexedtech/codemp-nvim)
|
||||
- [VSCode](https://github.com/hexedtech/codemp-vscode)
|
||||
- [Sublime Text](https://github.com/hexedtech/codemp-sublime)
|
||||
<!-- - [IntelliJ Platform](https://github.com/hexedtech/codemp-intellij) -->
|
||||
|
||||
## Registration
|
||||
The `codemp` protocol is [openly available](https://github.com/hexedtech/codemp-proto/) and servers may be freely developed with it.
|
||||
|
||||
A reference instance is provided by hexed.technology at [codemp.dev](https://codemp.dev). You may create an account for it [here](https://codemp.dev/register).
|
||||
During the initial closed beta, registrations will require an invite code. Get in contact if interested.
|
||||
|
||||
An open beta is going to follow with free access to a single workspace per user.
|
||||
After such period, [codemp.dev](https://codemp.dev) will switch to a subscription-based model.
|
||||
|
||||
# Development
|
||||
This is the main client library for `codemp`. It provides a batteries-included fully-featured `Client`, managed by the library itself, and exposes a number of functions to interact with it. The host program can obtain a `Client` handle by connecting, and from that reference can retrieve every other necessary component.
|
||||
|
||||
`codemp` is primarily a rlib and can be used as such, but is also available in other languages via FFI.
|
||||
|
||||
Adding a dependency on `codemp` is **easy**:
|
||||
|
||||
### From Rust
|
||||
Just `cargo add codemp` and check the docs for some examples.
|
||||
|
||||
### From supported languages
|
||||
We provide first-class bindings for:
|
||||
- [JavaScript](./dist/js/README.md): available from `npm` as [`codemp`](https://npmjs.org/package/codemp)
|
||||
- [Python](./dist/lua/README.md): available from `PyPI` as [`codemp`](https://pypi.org/project/codemp)
|
||||
- [Lua](./dist/lua/README.md): run `cargo build --features=lua`
|
||||
- [Java](./dist/java/README.md): run `gradle build` in `dist/java/` (requires Gradle)
|
||||
|
||||
As a design philosophy, our binding APIs attempt to perfectly mimic their Rust counterparts, so the main documentation can still be referenced as source of truth.
|
||||
Refer to specific language documentation for specifics, differences and quirks.
|
||||
|
||||
### From other languages
|
||||
> [!IMPORTANT]
|
||||
> The common C bindings are not available yet!
|
||||
|
||||
Any other language with C FFI capabilities will be able to use `codemp` via its bare C bindings.
|
||||
This may be more complex and may require wrapping the native calls underneath.
|
||||
|
||||
# Get in Touch
|
||||
We love to hear back from users! Be it to give feedback, propose new features or highlight bugs, don't hesitate to reach out!
|
||||
|
||||
## Contacts
|
||||
We have a public [Gitter](https://gitter.im) room available on [gitter.im/hexedtech/codemp](https://gitter.im/hexedtech/codemp).
|
||||
It's possible to freely browse the room history, but to send new messages it will be necessary to sign in with your GitHub account.
|
||||
|
||||
If you have a [Matrix](https://matrix.org) account, you can join the gitter room directly at [#hexedtech_codemp:gitter.im](https://matrix.to/#/#hexedtech_codemp:gitter.im)
|
||||
|
||||
## Contributing
|
||||
If you find bugs or would like to see new features implemented, be sure to open an issue on this repository.
|
||||
|
||||
> [!WARNING]
|
||||
> The CLA necessary for code contributions is not yet available!
|
||||
|
||||
In case you wish to contribute code, that's great! We love external contributions, but we require you to sign our CLA first (available soon).
|
||||
|
7
lib/codemp-0.0.5.dist-info/RECORD
Normal file
7
lib/codemp-0.0.5.dist-info/RECORD
Normal file
|
@ -0,0 +1,7 @@
|
|||
codemp-0.0.5.dist-info/METADATA,sha256=9Gy6K7EREfjx1ozQv3OQPBhPEqsDphl2uOUzYXO5HXY,5819
|
||||
codemp-0.0.5.dist-info/WHEEL,sha256=Z7z_TcN-SqAJ4k-DAgSbQsbDhIg--F0f-wRIhoPNirs,102
|
||||
codemp/__init__.py,sha256=mu2xQJfcdjBHPYAGe4y3B-O1zKM0O6jqyutzs-XfgsE,107
|
||||
codemp/__init__.pyi,sha256=u_Yx1O12UzHwKtz97pfkT0mYbECDzzPEi8ewSb6yb9g,4155
|
||||
codemp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
codemp/codemp.abi3.so,sha256=OrEWjqxkDYxY0wN9O7T3Osg8JMvRu3Fj6-vVQFqPgv4,4193664
|
||||
codemp-0.0.5.dist-info/RECORD,,
|
4
lib/codemp-0.0.5.dist-info/WHEEL
Normal file
4
lib/codemp-0.0.5.dist-info/WHEEL
Normal file
|
@ -0,0 +1,4 @@
|
|||
Wheel-Version: 1.0
|
||||
Generator: maturin (1.7.0)
|
||||
Root-Is-Purelib: false
|
||||
Tag: cp38-abi3-macosx_11_0_arm64
|
5
lib/codemp/__init__.py
Normal file
5
lib/codemp/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from .codemp import *
|
||||
|
||||
__doc__ = codemp.__doc__
|
||||
if hasattr(codemp, "__all__"):
|
||||
__all__ = codemp.__all__
|
124
lib/codemp/__init__.pyi
Normal file
124
lib/codemp/__init__.pyi
Normal file
|
@ -0,0 +1,124 @@
|
|||
from typing import Tuple, Optional, Callable
|
||||
|
||||
class Driver:
|
||||
"""
|
||||
this is akin to a big red button with a white "STOP" on top of it.
|
||||
it is used to stop the runtime.
|
||||
"""
|
||||
def stop(self) -> None: ...
|
||||
|
||||
|
||||
def init() -> Driver: ...
|
||||
def set_logger(logger_cb: Callable[[str], None], debug: bool) -> bool: ...
|
||||
def connect(host: str, username: str, password: str) -> Promise[Client]: ...
|
||||
|
||||
class Promise[T]:
|
||||
"""
|
||||
This is a class akin to a future, which wraps a join handle from a spawned
|
||||
task on the rust side. you may call .pyawait() on this promise to block
|
||||
until we have a result, or return immediately if we already have one.
|
||||
This only goes one way rust -> python.
|
||||
|
||||
It can either be used directly or you can wrap it inside a future python side.
|
||||
"""
|
||||
def wait(self) -> T: ...
|
||||
def is_done(self) -> bool: ...
|
||||
|
||||
class Client:
|
||||
"""
|
||||
Handle to the actual client that manages the session. It manages the connection
|
||||
to a server and joining/creating new workspaces
|
||||
"""
|
||||
def join_workspace(self, workspace: str) -> Promise[Workspace]: ...
|
||||
def create_workspace(self, workspace: str) -> Promise[None]: ...
|
||||
def delete_workspace(self, workspace: str) -> Promise[None]: ...
|
||||
def invite_to_workspace(self, workspace: str, username: str) -> Promise[None]: ...
|
||||
def list_workspaces(self, owned: bool, invited: bool) -> Promise[list[str]]: ...
|
||||
def leave_workspace(self, workspace: str) -> bool: ...
|
||||
def get_workspace(self, id: str) -> Workspace: ...
|
||||
def active_workspaces(self) -> list[str]: ...
|
||||
def user_id(self) -> str: ...
|
||||
def user_name(self) -> str: ...
|
||||
def refresh(self) -> Promise[None]: ...
|
||||
|
||||
class Workspace:
|
||||
"""
|
||||
Handle to a workspace inside codemp. It manages buffers.
|
||||
A cursor is tied to the single workspace.
|
||||
"""
|
||||
def create(self, path: str) -> Promise[None]: ...
|
||||
def attach(self, path: str) -> Promise[BufferController]: ...
|
||||
def detach(self, path: str) -> bool: ...
|
||||
def fetch_buffers(self) -> Promise[None]: ...
|
||||
def fetch_users(self) -> Promise[None]: ...
|
||||
def list_buffer_users(self, path: str) -> Promise[list[str]]: ...
|
||||
def delete(self, path: str) -> Promise[None]: ...
|
||||
def id(self) -> str: ...
|
||||
def cursor(self) -> CursorController: ...
|
||||
def buffer_by_name(self, path: str) -> Optional[BufferController]: ...
|
||||
def buffer_list(self) -> list[str]: ...
|
||||
def filetree(self, filter: Optional[str]) -> list[str]: ...
|
||||
|
||||
class TextChange:
|
||||
"""
|
||||
Editor agnostic representation of a text change, it translate between internal
|
||||
codemp text operations and editor operations
|
||||
"""
|
||||
start: int
|
||||
end: int
|
||||
content: str
|
||||
|
||||
def is_delete(self) -> bool: ...
|
||||
def is_insert(self) -> bool: ...
|
||||
def is_empty(self) -> bool: ...
|
||||
def apply(self, txt: str) -> str: ...
|
||||
|
||||
|
||||
class BufferController:
|
||||
"""
|
||||
Handle to the controller for a specific buffer, which manages the back and forth
|
||||
of operations to and from other peers.
|
||||
"""
|
||||
def path(self) -> str: ...
|
||||
def content(self) -> Promise[str]: ...
|
||||
def send(self,
|
||||
start: int,
|
||||
end: int,
|
||||
txt: str) -> Promise[None]: ...
|
||||
def try_recv(self) -> Promise[Optional[TextChange]]: ...
|
||||
def recv(self) -> Promise[TextChange]: ...
|
||||
def poll(self) -> Promise[None]: ...
|
||||
def callback(self,
|
||||
cb: Callable[[BufferController], None]) -> None: ...
|
||||
def clear_callback(self) -> None: ...
|
||||
def stop(self) -> bool: ...
|
||||
|
||||
|
||||
|
||||
class Cursor:
|
||||
"""
|
||||
An Editor agnostic cursor position representation
|
||||
"""
|
||||
start: Tuple[int, int]
|
||||
end: Tuple[int, int]
|
||||
buffer: str
|
||||
user: Optional[str] # can be an empty string
|
||||
|
||||
|
||||
class CursorController:
|
||||
"""
|
||||
Handle to the controller for a workspace, which manages the back and forth of
|
||||
cursor movements to and from other peers
|
||||
"""
|
||||
def send(self,
|
||||
path: str,
|
||||
start: Tuple[int, int],
|
||||
end: Tuple[int, int]) -> Promise[None]: ...
|
||||
def try_recv(self) -> Promise[Optional[Cursor]]: ...
|
||||
def recv(self) -> Promise[Cursor]: ...
|
||||
def poll(self) -> Promise[None]: ...
|
||||
def callback(self,
|
||||
cb: Callable[[CursorController], None]) -> None: ...
|
||||
def clear_callback(self) -> None: ...
|
||||
def stop(self) -> bool: ...
|
||||
|
1
lib/codemp/codemp.abi3.so.REMOVED.git-id
Normal file
1
lib/codemp/codemp.abi3.so.REMOVED.git-id
Normal file
|
@ -0,0 +1 @@
|
|||
7145849af30523094153d307d57c79bb7a90680f
|
0
lib/codemp/py.typed
Normal file
0
lib/codemp/py.typed
Normal file
|
@ -5,10 +5,10 @@ import logging
|
|||
import random
|
||||
from typing import Tuple, Union
|
||||
|
||||
from Codemp.src.client import client
|
||||
from Codemp.src.utils import safe_listener_detach
|
||||
from Codemp.src.utils import safe_listener_attach
|
||||
from Codemp.src import globals as g
|
||||
from .src.client import client
|
||||
from .src.utils import safe_listener_detach
|
||||
from .src.utils import safe_listener_attach
|
||||
from .src import globals as g
|
||||
|
||||
LOG_LEVEL = logging.DEBUG
|
||||
handler = logging.StreamHandler()
|
||||
|
|
|
@ -4,9 +4,9 @@ import sublime
|
|||
import os
|
||||
import logging
|
||||
|
||||
import codemp
|
||||
from Codemp.src import globals as g
|
||||
from Codemp.src.utils import populate_view, safe_listener_attach, safe_listener_detach
|
||||
from . import globals as g
|
||||
from .utils import populate_view, safe_listener_attach, safe_listener_detach
|
||||
from ..lib import codemp
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -5,11 +5,10 @@ from typing import Optional
|
|||
import sublime
|
||||
import logging
|
||||
|
||||
import codemp
|
||||
from Codemp.src import globals as g
|
||||
from Codemp.src.workspace import VirtualWorkspace
|
||||
from Codemp.src.buffers import VirtualBuffer
|
||||
from Codemp.src.utils import bidict
|
||||
from ..lib import codemp
|
||||
from .workspace import VirtualWorkspace
|
||||
from .buffers import VirtualBuffer
|
||||
from .utils import bidict
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -1,147 +0,0 @@
|
|||
from typing import Optional, Callable, Any
|
||||
|
||||
import sublime
|
||||
import logging
|
||||
import asyncio
|
||||
import threading
|
||||
import concurrent.futures
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class sublimeWorkerThreadExecutor(concurrent.futures.Executor):
|
||||
def __init__(self):
|
||||
self._futures_pending = 0
|
||||
self._shutting_down = False
|
||||
|
||||
# reentrant lock: we either increment from the main thread (submit calls)
|
||||
# or we decrement from the worker thread (futures)
|
||||
self._condvar = threading.Condition()
|
||||
|
||||
def submit(
|
||||
self, fn: Callable[..., Any], *args: Any, **kwargs: Any
|
||||
) -> concurrent.futures.Future:
|
||||
if self._shutting_down:
|
||||
raise RuntimeError("Executor is shutting down")
|
||||
|
||||
with self._condvar:
|
||||
self._futures_pending += 1
|
||||
|
||||
logger.debug("Spawning a future in the main thread")
|
||||
future = concurrent.futures.Future()
|
||||
|
||||
def coro() -> None:
|
||||
logger.debug("Running a future from the worker thread")
|
||||
try:
|
||||
future.set_result(fn(*args, **kwargs))
|
||||
except BaseException as e:
|
||||
future.set_exception(e)
|
||||
with self._condvar:
|
||||
self._futures_pending -= 1
|
||||
|
||||
sublime.set_timeout_async(coro)
|
||||
return future
|
||||
|
||||
def shutdown(self, wait: bool = True) -> None:
|
||||
self._shutting_down = True
|
||||
if not wait:
|
||||
return
|
||||
|
||||
with self._condvar:
|
||||
self._condvar.wait_for(lambda: self._futures_pending == 0)
|
||||
|
||||
|
||||
class Runtime:
|
||||
def __init__(self):
|
||||
self.tasks = []
|
||||
self.loop = asyncio.new_event_loop()
|
||||
self.loop.set_default_executor(sublimeWorkerThreadExecutor())
|
||||
self.loop.set_debug(True)
|
||||
self.thread = threading.Thread(
|
||||
target=self.loop.run_forever, name="codemp-asyncio-loop"
|
||||
)
|
||||
logger.debug("spinning up even loop in its own thread.")
|
||||
self.thread.start()
|
||||
|
||||
def __del__(self):
|
||||
logger.debug("closing down the event loop.")
|
||||
for task in asyncio.all_tasks(self.loop):
|
||||
task.cancel()
|
||||
|
||||
self.stop_loop()
|
||||
|
||||
try:
|
||||
self.loop.run_until_complete(self.loop.shutdown_asyncgens())
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected crash while shutting down event loop: {e}")
|
||||
|
||||
self.thread.join()
|
||||
|
||||
def stop_loop(self):
|
||||
logger.debug("stopping event loop.")
|
||||
self.loop.call_soon_threadsafe(lambda: asyncio.get_running_loop().stop())
|
||||
|
||||
def run_blocking(self, fut, *args, **kwargs):
|
||||
return self.loop.run_in_executor(None, fut, *args, **kwargs)
|
||||
|
||||
def dispatch(self, coro, name=None):
|
||||
"""
|
||||
Dispatch a task on the event loop and returns the task itself.
|
||||
Similar to `run_coroutine_threadsafe` but returns the
|
||||
actual task running and not the result of the coroutine.
|
||||
|
||||
`run_coroutine_threadsafe` returns a concurrent.futures.Future
|
||||
which has a blocking .result so not really suited for long running
|
||||
coroutines
|
||||
"""
|
||||
logger.debug("dispatching coroutine...")
|
||||
|
||||
def make_task(fut):
|
||||
logger.debug("creating task on the loop.")
|
||||
try:
|
||||
fut.set_result(self.loop.create_task(coro))
|
||||
except Exception as e:
|
||||
fut.set_exception(e)
|
||||
|
||||
# create the future to populate with the task
|
||||
# we use the concurrent.futures.Future since it is thread safe
|
||||
# and the .result() call is blocking.
|
||||
fut = concurrent.futures.Future()
|
||||
self.loop.call_soon_threadsafe(make_task, fut)
|
||||
task = fut.result(None) # wait for the task to be created
|
||||
task.set_name(name)
|
||||
self.tasks.append(task) # save the reference
|
||||
return task
|
||||
|
||||
def block_on(self, coro, timeout=None):
|
||||
fut = asyncio.run_coroutine_threadsafe(coro, self.loop)
|
||||
try:
|
||||
return fut.result(timeout)
|
||||
except asyncio.CancelledError:
|
||||
logger.debug("future got cancelled.")
|
||||
raise
|
||||
except TimeoutError:
|
||||
logger.debug("future took too long to finish.")
|
||||
raise
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def get_task(self, name) -> Optional[asyncio.Task]:
|
||||
return next((t for t in self.tasks if t.get_name() == name), None)
|
||||
|
||||
def stop_task(self, name):
|
||||
task = self.get_task(name)
|
||||
if task is not None:
|
||||
self.dispatch(self.wait_for_cancel(task))
|
||||
|
||||
async def wait_for_cancel(self, task):
|
||||
task.cancel() # cancelling a task, merely requests a cancellation.
|
||||
try:
|
||||
await task
|
||||
except asyncio.CancelledError:
|
||||
return
|
||||
|
||||
|
||||
# store a global in the module so it acts as a singleton
|
||||
# (modules are loaded only once)
|
||||
# rt = Runtime()
|
|
@ -1,7 +1,7 @@
|
|||
import sublime
|
||||
import sublime_plugin
|
||||
from typing import Dict, Generic, TypeVar
|
||||
from Codemp.src import globals as g
|
||||
from . import globals as g
|
||||
|
||||
# bidirectional dictionary so that we can have bidirectional
|
||||
# lookup!
|
||||
|
|
|
@ -6,12 +6,10 @@ import shutil
|
|||
import tempfile
|
||||
import logging
|
||||
|
||||
import codemp
|
||||
from Codemp.src import globals as g
|
||||
from Codemp.src.buffers import VirtualBuffer
|
||||
from Codemp.src.utils import draw_cursor_region, safe_listener_attach, sublime_plugin
|
||||
from Codemp.src.utils import bidict
|
||||
|
||||
from ..lib import codemp
|
||||
from . import globals as g
|
||||
from .buffers import VirtualBuffer
|
||||
from .utils import draw_cursor_region, sublime_plugin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
Loading…
Reference in a new issue