diff --git a/dist/lua/annotations.lua b/dist/lua/annotations.lua new file mode 100644 index 0000000..380c604 --- /dev/null +++ b/dist/lua/annotations.lua @@ -0,0 +1,316 @@ +---@meta annotations +-- type annotations for codemp native lua library + +-- [[ ASYNC STUFF ]] + +-- TODO lua-language-server doesn't seem to support generic classes +-- https://github.com/LuaLS/lua-language-server/issues/1532 +-- so we need to expand every possible promise type... + +---@class Promise +---@field ready boolean true if promise completed + +---@class NilPromise : Promise +---@field await fun(self: NilPromise): nil block until promise is ready + +---@class StringArrayPromise : Promise +---@field await fun(self: StringArrayPromise): string[] block until promise is ready and return value + +---@class ClientPromise : Promise +---@field await fun(self: ClientPromise): Client block until promise is ready and return value + +---@class WorkspacePromise : Promise +---@field await fun(self: WorkspacePromise): Workspace block until promise is ready and return value + +---@class WorkspaceEventPromise : Promise +---@field await fun(self: WorkspaceEventPromise): WorkspaceEvent block until promise is ready and return value + +---@class BufferControllerPromise : Promise +---@field await fun(self: BufferControllerPromise): BufferController block until promise is ready and return value + +---@class CursorPromise : Promise +---@field await fun(self: CursorPromise): Cursor block until promise is ready and return value + +---@class MaybeCursorPromise : Promise +---@field await fun(self: MaybeCursorPromise): Cursor? block until promise is ready and return value + +---@class TextChangePromise : Promise +---@field await fun(self: TextChangePromise): TextChange block until promise is ready and return value + +---@class MaybeTextChangePromise : Promise +---@field await fun(self: MaybeTextChangePromise): TextChange? block until promise is ready and return value + +-- [[ END ASYNC STUFF ]] + + +---@class Client +---@field id string uuid of local user +---@field username string name of local user +---@field active_workspaces string[] array of all currently active workspace names +---the effective local client, handling connecting to codemp server +local Client = {} + +---@return NilPromise +---@async +---@nodiscard +---refresh current user token if possible +function Client:refresh() end + +---@param ws string workspace id to connect to +---@return WorkspacePromise +---@async +---@nodiscard +---join requested workspace if possible and subscribe to event bus +function Client:join_workspace(ws) end + +---@param ws string workspace id to create +---@return NilPromise +---@async +---@nodiscard +---create a new workspace with given id +function Client:join_workspace(ws) end + +---@param ws string workspace id to leave +---leave workspace with given id, detaching and disconnecting +function Client:leave_workspace(ws) end + +---@param ws string workspace id to delete +---@return NilPromise +---@async +---@nodiscard +---delete workspace with given id +function Client:delete_workspace(ws) end + +---@param ws string workspace id to delete +---@param user string user name to invite to given workspace +---@return NilPromise +---@async +---@nodiscard +---grant user acccess to workspace +function Client:invite_to_workspace(ws, user) end + +---@param owned boolean? list owned workspaces, default true +---@param invited boolean? list invited workspaces, default true +---@return StringArrayPromise +---@async +---@nodiscard +---grant user acccess to workspace +function Client:list_workspaces(owned, invited) end + +---@param ws string workspace id to get +---@return Workspace? +---get an active workspace by name +function Client:get_workspace(ws) end + + + +---@class Workspace +---@field name string workspace name +---@field cursor CursorController workspace cursor controller +---@field active_buffers string[] array of all currently active buffer names +---a joined codemp workspace +local Workspace = {} + +---@param path string relative path ("name") of new buffer +---@return NilPromise +---@async +---@nodiscard +---create a new empty buffer +function Workspace:create_buffer(path) end + +---@param path string relative path ("name") of buffer to delete +---@return NilPromise +---@async +---@nodiscard +---delete buffer from workspace +function Workspace:delete_buffer(path) end + +---@param path string relative path ("name") of buffer to get +---@return BufferController? +---get an active buffer controller by name +function Workspace:get_buffer(path) end + +---@param path string relative path ("name") of buffer to attach to +---@return BufferControllerPromise +---@async +---@nodiscard +---attach to a remote buffer, synching content and changes and returning its controller +function Workspace:attach_buffer(path) end + +---@param path string relative path ("name") of buffer to detach from +---@return boolean +---detach from an active buffer, closing all streams. returns false if buffer was no longer active +function Workspace:detach_buffer(path) end + +---@param filter string only return elements starting with given filter +---@return string[] +---return the list of available buffers in this workspace, as relative paths from workspace root +function Workspace:filetree(filter) end + +---@return NilPromise +---@async +---@nodiscard +---force refresh buffer list from workspace +function Workspace:fetch_buffers(path) end + +---@return NilPromise +---@async +---@nodiscard +---force refresh users list from workspace +function Workspace:fetch_users(path) end + +---@class WorkspaceEvent +---@field type string +---@field value string + +---@return WorkspaceEventPromise +---@async +---@nodiscard +---get next workspace event +function Workspace:event() end + + + + +---@class BufferController +---handle to a remote buffer, for async send/recv operations +local BufferController = {} + +---@class TextChange +---@field content string text content of change +---@field first integer start index of change +---@field last integer end index of change +---@field hash integer? optional hash of text buffer after this change, for sync checks +---@field apply fun(self: TextChange, other: string): string apply this text change to a string + +---@param first integer change start index +---@param last integer change end index +---@param content string change content +---@return NilPromise +---@async +---@nodiscard +---update buffer with a text change; note that to delete content should be empty but not span, while to insert span should be empty but not content (can insert and delete at the same time) +function BufferController:send(first, last, content) end + + +---@return MaybeTextChangePromise +---@async +---@nodiscard +---try to receive text changes, returning nil if none is available +function BufferController:try_recv() end + +---@return TextChangePromise +---@async +---@nodiscard +---block until next text change and return it +function BufferController:recv() end + +---@return NilPromise +---@async +---@nodiscard +---block until next text change without returning it +function BufferController:poll() end + +---@return boolean +---stop buffer worker and disconnect, returns false if was already stopped +function BufferController:stop() end + +---clears any previously registered buffer callback +function BufferController:clear_callback() end + +---@param cb fun(c: BufferController) callback to invoke on each text change from server +---register a new callback to be called on remote text changes (replaces any previously registered one) +function BufferController:callback(cb) end + + + + +---@class CursorController +---handle to a workspace's cursor channel, allowing send/recv operations +local CursorController = {} + +---@class RowCol +---@field row integer row number +---@field col integer column number +---row and column tuple + +---@class Cursor +---@field user string? id of user owning this cursor +---@field buffer string relative path ("name") of buffer on which this cursor is +---@field start RowCol cursor start position +---@field finish RowCol cursor end position +---a cursor position + +---@param buffer string buffer relative path ("name") to send this cursor on +---@param start_row integer cursor start row +---@param start_col integer cursor start col +---@param end_row integer cursor end row +---@param end_col integer cursor end col +---@return NilPromise +---@async +---@nodiscard +---update cursor position by sending a cursor event to server +function CursorController:send(buffer, start_row, start_col, end_row, end_col) end + + +---@return MaybeCursorPromise +---@async +---@nodiscard +---try to receive cursor events, returning nil if none is available +function CursorController:try_recv() end + +---@return CursorPromise +---@async +---@nodiscard +---block until next cursor event and return it +function CursorController:recv() end + +---@return NilPromise +---@async +---@nodiscard +---block until next cursor event without returning it +function CursorController:poll() end + +---@return boolean +---stop cursor worker and disconnect, returns false if was already stopped +function CursorController:stop() end + +---clears any previously registered cursor callback +function CursorController:clear_callback() end + +---@param cb fun(c: CursorController) callback to invoke on each cursor event from server +---register a new callback to be called on cursor events (replaces any previously registered one) +function CursorController:callback(cb) end + + + +---@class Codemp +---the codemp shared library +local Codemp = {} + +---@param host string server host to connect to +---@param username string username used to log in (usually email) +---@param password string password used to log in +---@return ClientPromise +---@async +---@nodiscard +---connect to codemp server, authenticate and return client +function Codemp.connect(host, username, password) end + +---@param data string +---@return integer +---use xxh3 hash, returns an i64 from any string +function Codemp.hash(data) end + +---@class RuntimeDriver +---@field stop fun(): boolean stops the runtime thread without deleting the runtime itself, returns false if driver was already stopped + +---@return RuntimeDriver +---spawns a background thread and uses it to run the codemp runtime +function Codemp.spawn_runtime_driver() end + +---@param printer string | fun(string) log sink used for printing, if string will go to file, otherwise use given function +---@param debug boolean? show more verbose debug logs, default false +---@return boolean true if logger was setup correctly, false otherwise +---setup a global logger for codemp, note that can only be done once +function Codemp.logger(printer, debug) end diff --git a/src/ffi/lua.rs b/src/ffi/lua.rs index b55b217..e0fa184 100644 --- a/src/ffi/lua.rs +++ b/src/ffi/lua.rs @@ -77,7 +77,7 @@ macro_rules! a_sync { }; } -fn runtime_drive_forever(_: &Lua, ():()) -> LuaResult { +fn spawn_runtime_driver(_: &Lua, ():()) -> LuaResult { let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); std::thread::spawn(move || tokio().block_on(async move { tracing::info!(" :: driving runtime..."); @@ -103,6 +103,7 @@ impl LuaUserData for CodempClient { fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { fields.add_field_method_get("id", |_, this| Ok(this.user().id.to_string())); fields.add_field_method_get("username", |_, this| Ok(this.user().name.clone())); + fields.add_field_method_get("active_workspaces", |_, this| Ok(this.active_workspaces())); } fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { @@ -137,7 +138,6 @@ impl LuaUserData for CodempClient { ); methods.add_method("get_workspace", |_, this, (ws,):(String,)| Ok(this.get_workspace(&ws))); - methods.add_method("active_workspaces", |_, this, ()| Ok(this.active_workspaces())); } } @@ -192,7 +192,7 @@ impl LuaUserData for CodempWorkspace { } fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { - fields.add_field_method_get("id", |_, this| Ok(this.id())); + fields.add_field_method_get("name", |_, this| Ok(this.id())); fields.add_field_method_get("cursor", |_, this| Ok(this.cursor())); fields.add_field_method_get("active_buffers", |_, this| Ok(this.buffer_list())); // fields.add_field_method_get("users", |_, this| Ok(this.0.users())); // TODO @@ -334,7 +334,7 @@ impl LuaUserData for CodempTextChange { // define module and exports #[mlua::lua_module] -fn codemp_lua(lua: &Lua) -> LuaResult { +fn codemp_native(lua: &Lua) -> LuaResult { let exports = lua.create_table()?; // entrypoint @@ -348,7 +348,7 @@ fn codemp_lua(lua: &Lua) -> LuaResult { )?)?; // runtime - exports.set("runtime_drive_forever", lua.create_function(runtime_drive_forever)?)?; + exports.set("spawn_runtime_driver", lua.create_function(spawn_runtime_driver)?)?; // logging exports.set("logger", lua.create_function(logger)?)?;