diff --git a/Cargo.lock b/Cargo.lock index 3a1c283..0c5363c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1139,6 +1139,7 @@ dependencies = [ "dirs", "lazy_static", "libproc", + "mlua", "notify", "regex", "serde", diff --git a/Cargo.toml b/Cargo.toml index 6e6ef1a..d002117 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,12 +32,16 @@ wax = "0.4.0" regex = "1.5" lazy_static = "1.4.0" shell-words = "1.1.0" +mlua = { version = "0.7.4", optional = true } # TODO: make libxcodebase lightweight # Use features to exclude stuff likce tracing and async-trait as well tokio # WARN: api totally changed on later versions. # It would make sense to create custom function # Breaks # gitignore = "1.0.7" +[features] +default = [ ] +lua = [ "mlua" ] [lib] name = "xcodebase" diff --git a/core/command.rs b/core/command.rs index b578d9c..23a7bc5 100644 --- a/core/command.rs +++ b/core/command.rs @@ -3,30 +3,29 @@ use anyhow::{bail, Result}; use async_trait::async_trait; mod build; +mod drop; mod register; mod rename_file; mod run; -mod unregister; pub use build::Build; +pub use drop::Drop; pub use register::Register; pub use rename_file::RenameFile; pub use run::Run; -pub use unregister::UnRegister; #[async_trait] pub trait DaemonCommand { async fn handle(&self, state: SharedState) -> anyhow::Result<()>; } -/// Rename file + class #[derive(Debug)] pub enum Command { Build(Build), Run(Run), RenameFile(RenameFile), Register(Register), - UnRegister(UnRegister), + Drop(Drop), } impl Command { @@ -36,7 +35,7 @@ impl Command { Command::Run(c) => c.handle(state).await, Command::RenameFile(c) => c.handle(state).await, Command::Register(c) => c.handle(state).await, - Command::UnRegister(c) => c.handle(state).await, + Command::Drop(c) => c.handle(state).await, } } @@ -48,7 +47,7 @@ impl Command { "run" => Ok(Self::Run(Run::new(args)?)), "rename_file" => Ok(Self::RenameFile(RenameFile::new(args)?)), "register" => Ok(Self::Register(Register::new(args)?)), - "unregister" => Ok(Self::UnRegister(UnRegister::new(args)?)), + "drop" => Ok(Self::Drop(Drop::new(args)?)), _ => bail!("Unknown command messsage: {cmd}"), } } diff --git a/core/command/unregister.rs b/core/command/drop.rs similarity index 66% rename from core/command/unregister.rs rename to core/command/drop.rs index 83d1ed4..5e25eaa 100644 --- a/core/command/unregister.rs +++ b/core/command/drop.rs @@ -1,17 +1,17 @@ use crate::state::SharedState; -use crate::{daemon, DaemonCommand}; +use crate::{Daemon, DaemonCommand}; use anyhow::{bail, Result}; use async_trait::async_trait; use tracing::trace; /// Register new client with workspace #[derive(Debug)] -pub struct UnRegister { +pub struct Drop { pub pid: i32, pub root: String, } -impl UnRegister { +impl Drop { pub fn new(args: Vec<&str>) -> Result { let pid = args.get(0); let root = args.get(1); @@ -27,12 +27,19 @@ impl UnRegister { } pub fn request(pid: i32, root: String) -> Result<()> { - daemon::execute(&["unregister", pid.to_string().as_str(), root.as_str()]) + Daemon::execute(&["drop", pid.to_string().as_str(), root.as_str()]) + } + + #[cfg(feature = "lua")] + pub fn lua(lua: &mlua::Lua, (pid, root): (i32, String)) -> mlua::Result<()> { + use crate::mlua::LuaExtension; + lua.trace(&format!("Added (pid: {pid} cwd: {root})"))?; + Self::request(pid, root).map_err(mlua::Error::external) } } #[async_trait] -impl DaemonCommand for UnRegister { +impl DaemonCommand for Drop { async fn handle(&self, state: SharedState) -> Result<()> { trace!("{:?}", self); state diff --git a/core/command/register.rs b/core/command/register.rs index 31d0996..5f2a3e7 100644 --- a/core/command/register.rs +++ b/core/command/register.rs @@ -1,5 +1,5 @@ use crate::state::SharedState; -use crate::{daemon, DaemonCommand}; +use crate::{Daemon, DaemonCommand}; use anyhow::{bail, Result}; use async_trait::async_trait; use tracing::trace; @@ -27,7 +27,14 @@ impl Register { } pub fn request(pid: i32, root: String) -> Result<()> { - daemon::execute(&["register", pid.to_string().as_str(), root.as_str()]) + Daemon::execute(&["register", pid.to_string().as_str(), root.as_str()]) + } + + #[cfg(feature = "lua")] + pub fn lua(lua: &mlua::Lua, (pid, root): (i32, String)) -> mlua::Result<()> { + use crate::mlua::LuaExtension; + lua.trace(&format!("Removed (pid: {pid} cwd: {root})"))?; + Self::request(pid, root).map_err(mlua::Error::external) } } diff --git a/core/command/rename_file.rs b/core/command/rename_file.rs index 2b032b1..01f6eca 100644 --- a/core/command/rename_file.rs +++ b/core/command/rename_file.rs @@ -1,5 +1,5 @@ use crate::state::SharedState; -use crate::{daemon, DaemonCommand}; +use crate::{Daemon, DaemonCommand}; use anyhow::{bail, Result}; use async_trait::async_trait; @@ -33,7 +33,7 @@ impl RenameFile { } pub fn request(path: &str, name: &str, new_name: &str) -> Result<()> { - daemon::execute(&["rename_file", path, name, new_name]) + Daemon::execute(&["rename_file", path, name, new_name]) } } diff --git a/core/command/run.rs b/core/command/run.rs index 1190906..e8ad0db 100644 --- a/core/command/run.rs +++ b/core/command/run.rs @@ -1,5 +1,5 @@ use crate::state::SharedState; -use crate::{daemon, DaemonCommand}; +use crate::{Daemon, DaemonCommand}; use anyhow::Result; use async_trait::async_trait; @@ -16,7 +16,7 @@ impl Run { } pub fn request(path: &str, name: &str, new_name: &str) -> Result<()> { - daemon::execute(&["run", path, name, new_name]) + Daemon::execute(&["run", path, name, new_name]) } } diff --git a/core/daemon.rs b/core/daemon.rs index 6154ac1..397abc7 100644 --- a/core/daemon.rs +++ b/core/daemon.rs @@ -1,28 +1,121 @@ use crate::constants::{DAEMON_BINARY, DAEMON_SOCKET_PATH}; -use anyhow::Result; -use std::io::Write; -use std::net::Shutdown; -use std::os::unix::net::UnixStream; -use std::process::Command; - -/// Check if Unix socket is running -pub fn is_running() -> bool { - match UnixStream::connect(DAEMON_SOCKET_PATH) { - Ok(s) => s.shutdown(Shutdown::Both).ok().is_some(), - Err(_) => false, +use anyhow::{bail, Context, Result}; + +pub struct Daemon { + state: std::sync::Arc>, + listener: tokio::net::UnixListener, +} + +impl Daemon { + pub fn default() -> Self { + if std::fs::metadata(DAEMON_SOCKET_PATH).is_ok() { + std::fs::remove_file(DAEMON_SOCKET_PATH).ok(); + } + + tracing::info!("Started"); + + Self { + state: Default::default(), + listener: tokio::net::UnixListener::bind(DAEMON_SOCKET_PATH).unwrap(), + } + } + + /// Run Main daemon server loop + pub async fn run(&mut self) -> ! { + use tokio::io::AsyncReadExt; + + loop { + let state = self.state.clone(); + let (mut s, _) = self.listener.accept().await.unwrap(); + tokio::spawn(async move { + // let mut current_state = state.lock().await; + // current_state.update_clients(); + + // trace!("Current State: {:?}", state.lock().await) + let mut string = String::default(); + + if let Err(e) = s.read_to_string(&mut string).await { + tracing::error!("[Read Error]: {:?}", e); + return; + }; + + if string.len() == 0 { + return; + } + + let msg = crate::Command::parse(string.as_str().trim()); + + if let Err(e) = msg { + tracing::error!("[Parse Error]: {:?}", e); + return; + }; + + let msg = msg.unwrap(); + if let Err(e) = msg.handle(state.clone()).await { + tracing::error!("[Handling Error]: {:?}", e); + return; + }; + + crate::watch::update(state, msg).await; + }); + } } } -/// Spawn new instance of the server via running binaray is a child process -pub fn spawn() -> Result<()> { - Command::new(DAEMON_BINARY).spawn()?; - Ok(()) +impl Daemon { + /// Spawn new instance of the server via running binaray is a child process + pub fn spawn() -> Result<()> { + std::process::Command::new(DAEMON_BINARY) + .spawn() + .context("Unable to start background instance using daemon binaray") + .map(|_| ()) + } + + /// Pass args to running daemon + pub fn execute(args: &[&str]) -> Result<()> { + use std::io::Write; + match std::os::unix::net::UnixStream::connect(DAEMON_SOCKET_PATH) { + Ok(mut stream) => { + stream.write_all(args.join(" ").as_str().as_ref())?; + stream.flush().context("Fail to flush stream!") + } + Err(e) => bail!("Fail to execute {:#?}: {e}", args), + } + } } -/// Execute argument in the runtime -pub fn execute(args: &[&str]) -> Result<()> { - let mut stream = UnixStream::connect(DAEMON_SOCKET_PATH)?; - stream.write_all(args.join(" ").as_str().as_ref())?; - stream.flush()?; - Ok(()) +#[cfg(feature = "lua")] +use crate::mlua::LuaExtension; + +#[cfg(feature = "lua")] +impl Daemon { + /// Representation of daemon table in lua + pub fn lua(lua: &mlua::Lua) -> mlua::Result { + let table = lua.create_table()?; + table.set("is_running", lua.create_function(Self::is_running)?)?; + table.set("ensure", lua.create_function(Self::ensure)?)?; + table.set("register", lua.create_function(crate::Register::lua)?)?; + table.set("drop", lua.create_function(crate::Drop::lua)?)?; + Ok(table) + } + + /// Check if Daemon is running + pub fn is_running(_: &mlua::Lua, _: ()) -> mlua::Result { + match std::os::unix::net::UnixStream::connect(DAEMON_SOCKET_PATH) { + Ok(stream) => Ok(stream.shutdown(std::net::Shutdown::Both).ok().is_some()), + Err(_) => Ok(false), + } + } + + /// Ensure that daemon is currently running in background + pub fn ensure(lua: &mlua::Lua, _: ()) -> mlua::Result { + if Self::is_running(lua, ()).unwrap() { + Ok(false) + } else if Self::spawn().is_ok() { + lua.info("Spawned Background Server")?; + Ok(true) + } else { + panic!("Unable to spawn background server"); + } + } } diff --git a/core/lib.rs b/core/lib.rs index ca082e1..4e4ebc5 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -2,10 +2,17 @@ pub mod constants; pub mod daemon; pub mod state; +#[cfg(feature = "lua")] +mod mlua; + +#[cfg(feature = "lua")] +pub use crate::mlua::LuaExtension; + mod command; mod project; mod xcode; pub use command::*; +pub use daemon::*; pub mod util; pub mod watch; diff --git a/nvim/src/extensions.rs b/core/mlua.rs similarity index 96% rename from nvim/src/extensions.rs rename to core/mlua.rs index 1631a59..502d9bb 100644 --- a/nvim/src/extensions.rs +++ b/core/mlua.rs @@ -1,6 +1,6 @@ use mlua::prelude::*; -pub trait LuaExt { +pub trait LuaExtension { fn print(&self, msg: &str); fn trace(&self, msg: &str) -> LuaResult<()>; fn error(&self, msg: &str) -> LuaResult<()>; @@ -18,7 +18,7 @@ fn log(lua: &Lua, level: &str, msg: &str) -> LuaResult<()> { .call::<_, ()>(msg.to_lua(lua)) } -impl LuaExt for Lua { +impl LuaExtension for Lua { fn print(&self, msg: &str) { self.globals() .get::<_, LuaFunction>("print") diff --git a/core/state.rs b/core/state.rs index db3e2a6..4a47629 100644 --- a/core/state.rs +++ b/core/state.rs @@ -8,7 +8,7 @@ use tokio::task::JoinHandle; use tracing::trace; /// Main state -#[derive(Debug)] +#[derive(Debug, Default)] pub struct State { /// Manged workspaces pub workspaces: HashMap, @@ -21,15 +21,6 @@ pub struct State { pub type SharedState = Arc>; impl State { - pub fn new() -> Result { - let state = State { - workspaces: HashMap::new(), - watchers: HashMap::new(), - clients: vec![], - }; - Ok(Arc::new(Mutex::new(state))) - } - pub fn update_clients(&mut self) { self.clients.retain(|&pid| { if proc_pid::name(pid).is_err() { diff --git a/daemon/src/main.rs b/daemon/src/main.rs index f41ca66..448a8b5 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -1,61 +1,5 @@ -use tracing::{error, info}; -use xcodebase::constants::DAEMON_SOCKET_PATH; -use xcodebase::state::{SharedState, State}; -use xcodebase::{watch, Command}; - -use tokio::io::AsyncReadExt; -use tokio::net::UnixListener; - -use anyhow::Result; -use std::fs; - -fn create_listener() -> std::io::Result { - if fs::metadata(DAEMON_SOCKET_PATH).is_ok() { - fs::remove_file(DAEMON_SOCKET_PATH)? - } - info!("Started"); - UnixListener::bind(DAEMON_SOCKET_PATH) -} - #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> anyhow::Result<()> { xcodebase::util::install_tracing("/tmp", "xcodebase-daemon.log")?; - - let state: SharedState = State::new()?; - let listener = create_listener()?; - loop { - let state = state.clone(); - let (mut s, _) = listener.accept().await.unwrap(); - tokio::spawn(async move { - // let mut current_state = state.lock().await; - // current_state.update_clients(); - - // trace!("Current State: {:?}", state.lock().await) - let mut string = String::default(); - - if let Err(e) = s.read_to_string(&mut string).await { - error!("[Read Error]: {:?}", e); - return; - }; - - if string.len() == 0 { - return; - } - - let msg = Command::parse(string.as_str().trim()); - - if let Err(e) = msg { - error!("[Parse Error]: {:?}", e); - return; - }; - - let msg = msg.unwrap(); - if let Err(e) = msg.handle(state.clone()).await { - error!("[Handling Error]: {:?}", e); - return; - }; - - watch::update(state, msg).await; - }); - } + xcodebase::Daemon::default().run().await } diff --git a/lua/xcodebase/init.lua b/lua/xcodebase/init.lua index c922b3e..66df826 100644 --- a/lua/xcodebase/init.lua +++ b/lua/xcodebase/init.lua @@ -1,4 +1,4 @@ -local xcodebase = {} +local M = {} local config = require "xcodebase.config" local lib = require "libxcodebase" @@ -6,7 +6,7 @@ local lib = require "libxcodebase" ---@field ensure fun():boolean: When the a new server started it should return true ---@field is_running fun():boolean ---@field register fun(pid: number, root: string):boolean -xcodebase.daemon = lib.daemon +M.daemon = lib.daemon ---@class XcodeBaseCommand local command = lib.command @@ -19,7 +19,7 @@ local command = lib.service ---@param root string: current working directory ---@param _ table: options to influence the result. ---@return boolean -xcodebase.should_register = function(root, _) +M.should_register = function(root, _) if vim.loop.fs_stat(root .. "/project.yml") then return true end @@ -30,14 +30,14 @@ end ---Tries to register vim instance as client for xcodebase server. ---Only register the vim instance when `xcodebase.should_attach` ---@see xcodebase.should_attach -xcodebase.try_register = function(opts) +M.try_register = function(opts) opts = opts or {} local root = vim.loop.cwd() - if xcodebase.should_register(root, opts) then - local _ = xcodebase.daemon.ensure() - xcodebase.daemon.register(vim.fn.getpid(), root) - vim.cmd [[ autocmd VimLeavePre * lua require'xcodebase'.daemon.unregister(vim.fn.getpid(), vim.loop.cwd())]] + if M.should_register(root, opts) then + local _ = M.daemon.ensure() + M.daemon.register(vim.fn.getpid(), root) + vim.cmd [[ autocmd VimLeavePre * lua require'xcodebase'.daemon.drop(vim.fn.getpid(), vim.loop.cwd())]] else return @@ -48,7 +48,7 @@ end ---Should ran once per neovim instance ---@param opts XcodeBaseOptions ---@overload fun() -xcodebase.setup = function(opts) +M.setup = function(opts) opts = opts or {} -- Mutate xcodebase configuration @@ -56,7 +56,7 @@ xcodebase.setup = function(opts) -- Try to register current vim instance -- NOTE: Should this register again on cwd change? - xcodebase.try_register(opts) + M.try_register(opts) end -return xcodebase +return M diff --git a/nvim/Cargo.toml b/nvim/Cargo.toml index a963b11..580f889 100644 --- a/nvim/Cargo.toml +++ b/nvim/Cargo.toml @@ -9,6 +9,6 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -xcodebase = { path = "../" } +xcodebase = { path = "../", features = [ "lua" ] } mlua = { version = "0.7.4", features = ["lua51", "module", "vendored", "send", "serialize", "async", "macros"] } anyhow = "^1.0.42" diff --git a/nvim/src/lib.rs b/nvim/src/lib.rs index 988ae36..15bd531 100644 --- a/nvim/src/lib.rs +++ b/nvim/src/lib.rs @@ -1,23 +1,12 @@ -mod extensions; -mod tables; - -use tables::{Daemon, DaemonClient}; - use mlua::lua_module; use mlua::prelude::*; +use xcodebase::Daemon; #[lua_module] fn libxcodebase(lua: &Lua) -> LuaResult { - let daemon = lua.create_table()?; - daemon.set("is_running", lua.create_function(Daemon::is_running)?)?; - daemon.set("ensure", lua.create_function(Daemon::ensure)?)?; - daemon.set("register", lua.create_function(DaemonClient::register)?)?; - daemon.set("unregister", lua.create_function(DaemonClient::unregister)?)?; - let commands = lua.create_table()?; - let module = lua.create_table()?; module.set("commands", commands)?; - module.set("daemon", daemon)?; + module.set("daemon", Daemon::lua(lua)?)?; Ok(module) } diff --git a/nvim/src/tables.rs b/nvim/src/tables.rs deleted file mode 100644 index 5a8588f..0000000 --- a/nvim/src/tables.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::extensions::LuaExt; - -use mlua::prelude::*; -use xcodebase::{daemon, Register, UnRegister}; - -pub struct Daemon; - -impl Daemon { - pub fn is_running(_: &Lua, _: ()) -> LuaResult { - Ok(daemon::is_running()) - } - - pub fn ensure(lua: &Lua, _: ()) -> LuaResult { - if daemon::is_running() { - return Ok(false); - } - - if daemon::spawn().is_ok() { - lua.info("Spawned Background Server")?; - return Ok(true); - } - - panic!("Unable to spawn background server"); - } -} - -pub struct DaemonClient; -impl DaemonClient { - pub fn register(lua: &Lua, (pid, root): (i32, String)) -> LuaResult<()> { - lua.trace(&format!("Added (pid: {pid} cwd: {root})"))?; - Register::request(pid, root).map_err(LuaError::external) - } - - pub fn unregister(lua: &Lua, (pid, root): (i32, String)) -> LuaResult<()> { - lua.trace(&format!("Removed (pid: {pid} cwd: {root})"))?; - UnRegister::request(pid, root).map_err(LuaError::external) - } -}