diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 532582fd..2443c060 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -20,8 +20,10 @@ jobs: run: rustup target add wasm32-wasi - name: Install cargo-make run: cargo install --debug cargo-make - - name: Build & Test - run: cargo make + - name: Build + run: cargo make build + - name: Test + run: cargo make test format: name: Check Formatting diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f833285a..6e3833f9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,9 +25,9 @@ cargo make cargo make format cargo make build cargo make test -# Run Zellij (optionally with a non-default layout) +# Run Zellij (optionally with additional arguments) cargo make run -cargo make run strider +cargo make run -- -l strider # Run Clippy (potentially with additional options) cargo make clippy cargo make clippy -W clippy::pedantic diff --git a/Cargo.lock b/Cargo.lock index b814071f..66310ea0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2194,7 +2194,7 @@ dependencies = [ [[package]] name = "zellij" -version = "0.3.1" +version = "0.3.2" dependencies = [ "ansi_term 0.12.1", "async-std", diff --git a/Cargo.toml b/Cargo.toml index e4db7573..6170016c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zellij" -version = "0.3.1" +version = "0.3.2" authors = ["Aram Drevekenin "] edition = "2018" description = "Terminal workspace (WIP)" diff --git a/Makefile.toml b/Makefile.toml index a4388ebc..4ee28112 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -1,6 +1,7 @@ # Global Settings [env] CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true +CARGO_TARGET_DIR = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target" SKIP_TEST = false # Add clippy to the default flow @@ -27,76 +28,34 @@ dependencies = ["pre-test"] [tasks.post-test] env = { "SKIP_TEST" = false } -# Running Zellij using patched layouts +# Running Zellij using the development data directory [tasks.run] workspace = false -dependencies = ["build-workspace", "patch-layouts"] +dependencies = ["build-workspace", "build-dev-data-dir"] run_task = "launch" [tasks.build-workspace] run_task = { name = "build", fork = true } -[tasks.patch-layouts] -script_runner = "@rust" -script = ''' -//! ```cargo -//! [dependencies] -//! yaml-rust = "0.4" -//! ``` -use std::{env, error::Error, fs, path::Path}; -use yaml_rust::{Yaml, YamlEmitter, YamlLoader}; - -fn main() -> Result<(), Box> { - let root = env::var("CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY")?; - let layout_path = Path::new(&root).join("assets/layouts"); - for layout in fs::read_dir(layout_path)? { - let layout = layout?.path(); - let yaml = fs::read_to_string(&layout)?; - let yaml = YamlLoader::load_from_str(&yaml)?.remove(0); - let yaml = patch_plugins(&root, yaml); - let new_layout = Path::new(&root) - .join("target") - .join(layout.file_name().unwrap()); - let mut new_yaml = String::new(); - let mut emitter = YamlEmitter::new(&mut new_yaml); - emitter.dump(&yaml)?; - fs::write(new_layout, new_yaml)?; - } - Ok(()) -} - -fn patch_plugins(root: &str, part: Yaml) -> Yaml { - let mut map = part.into_hash().unwrap(); - if let Some(plugin) = map.get_mut(&Yaml::from_str("plugin")) { - let new_plugin = Path::new(root) - .join("target/wasm32-wasi/debug") - .join(plugin.as_str().unwrap()); - *plugin = Yaml::String(new_plugin.to_string_lossy().into_owned()); - } - if let Some(parts) = map.get_mut(&Yaml::from_str("parts")) { - let new_parts = parts - .clone() - .into_iter() - .map(|p| patch_plugins(root, p)) - .collect(); - *parts = Yaml::Array(new_parts); - } - Yaml::Hash(map) -} -''' - -[tasks.pre-launch] +[tasks.build-dev-data-dir] script_runner = "@duckscript" script = ''' -if is_empty ${CARGO_MAKE_TASK_ARGS} - set_env CARGO_MAKE_TASK_ARGS default +asset_dir = set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/assets +target_dir = set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target +data_dir = set ${target_dir}/dev-data +rm -r ${data_dir} +cp ${asset_dir}/layouts ${data_dir}/ +plugins = glob_array ${target_dir}/wasm32-wasi/debug/*.wasm +for plugin in ${plugins} + plugin_name = basename ${plugin} + cp ${plugin} ${data_dir}/plugins/${plugin_name} end +writefile ${data_dir}/VERSION ${CARGO_MAKE_CRATE_VERSION} ''' [tasks.launch] -dependencies = ["pre-launch"] command = "cargo" -args = ["run", "--", "-l", "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${CARGO_MAKE_TASK_ARGS}.yaml"] +args = ["run", "--", "--data-dir", "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/dev-data/", "@@split(CARGO_MAKE_TASK_ARGS,;)"] # Simple clippy tweak [tasks.clippy] @@ -105,7 +64,7 @@ args = ["clippy", "--", "@@split(CARGO_MAKE_TASK_ARGS,;)"] # Release building and installing Zellij [tasks.install] workspace = false -dependencies = ["build-tiles-release", "wasm-opt-tiles", "build-release", "clear-data-directory"] +dependencies = ["build-tiles-release", "wasm-opt-tiles", "build-release"] script_runner = "@duckscript" script = ''' cp ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/release/${CARGO_MAKE_CRATE_NAME} ${CARGO_MAKE_TASK_ARGS} @@ -129,35 +88,15 @@ for tile in ${tiles} end ''' -# FIXME: Maybe this should be more generic? Or just blow away the whole directory? -[tasks.clear-data-directory] -script_runner = "@rust" -script = ''' -//! ```cargo -//! [dependencies] -//! directories-next = "2.0" -//! ``` -use directories_next::ProjectDirs; -use std::fs; - -fn main() { - let project_dirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap(); - let data_dir = project_dirs.data_dir(); - drop(fs::remove_file(data_dir.join("plugins/status-bar.wasm"))); - drop(fs::remove_file(data_dir.join("plugins/tab-bar.wasm"))); - drop(fs::remove_file(data_dir.join("plugins/strider.wasm"))); - drop(fs::remove_file(data_dir.join("layouts/default.yaml"))); - drop(fs::remove_file(data_dir.join("layouts/strider.yaml"))); -} -''' - # Publishing Zellij [tasks.publish] clear = true workspace = false -dependencies = ["build-tiles-release", "wasm-opt-tiles", "build-release", "publish-zellij-tile", "publish-zellij"] +dependencies = ["build-tiles-release", "wasm-opt-tiles", "build-release", "publish-zellij-tile"] +run_task = "publish-zellij" [tasks.publish-zellij-tile] +ignore_errors = true cwd = "zellij-tile" command = "cargo" args = ["publish"] diff --git a/README.md b/README.md index 8b05e1f5..af7e1bc4 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,6 @@ Zellij was initially called "Mosaic". ## How to use it? * Clone the project * Install cargo-make with `cargo install --force cargo-make` -* Make sure that the `~/.cargo/bin` directory is on your PATH * In the project folder, run: `cargo make run` (note that right now Zellij only supports linux and mac) diff --git a/assets/completions/_zellij b/assets/completions/_zellij index fc1cd00f..9eecf8e2 100644 --- a/assets/completions/_zellij +++ b/assets/completions/_zellij @@ -20,6 +20,7 @@ _zellij() { '-o+[Send "open file in new pane" to active zellij session]' \ '--open-file=[Send "open file in new pane" to active zellij session]' \ '--max-panes=[Maximum panes on screen, caution: opening more panes will close old ones]' \ +'--data-dir=[Change where zellij looks for layouts and plugins]' \ '-l+[Path to a layout yaml file]' \ '--layout=[Path to a layout yaml file]' \ '-m[Send "move focused pane" to active zellij session]' \ diff --git a/assets/completions/zellij.bash b/assets/completions/zellij.bash index 10c18d53..4f28140a 100644 --- a/assets/completions/zellij.bash +++ b/assets/completions/zellij.bash @@ -29,7 +29,7 @@ _zellij() { case "${cmd}" in zellij) - opts=" -m -d -h -V -s -o -l --move-focus --debug --help --version --split --open-file --max-panes --layout config help c c" + opts=" -m -d -h -V -s -o -l --move-focus --debug --help --version --split --open-file --max-panes --data-dir --layout config help c c" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -56,6 +56,10 @@ _zellij() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --data-dir) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --layout) COMPREPLY=($(compgen -f "${cur}")) return 0 diff --git a/assets/completions/zellij.fish b/assets/completions/zellij.fish index 36bab192..ec758be6 100644 --- a/assets/completions/zellij.fish +++ b/assets/completions/zellij.fish @@ -1,6 +1,7 @@ complete -c zellij -n "__fish_use_subcommand" -s s -l split -d 'Send "split (direction h == horizontal / v == vertical)" to active zellij session' complete -c zellij -n "__fish_use_subcommand" -s o -l open-file -d 'Send "open file in new pane" to active zellij session' complete -c zellij -n "__fish_use_subcommand" -l max-panes -d 'Maximum panes on screen, caution: opening more panes will close old ones' +complete -c zellij -n "__fish_use_subcommand" -l data-dir -d 'Change where zellij looks for layouts and plugins' complete -c zellij -n "__fish_use_subcommand" -s l -l layout -d 'Path to a layout yaml file' complete -c zellij -n "__fish_use_subcommand" -s m -l move-focus -d 'Send "move focused pane" to active zellij session' complete -c zellij -n "__fish_use_subcommand" -s d -l debug diff --git a/src/cli.rs b/src/cli.rs index b1fb8f38..43047dd3 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -20,6 +20,10 @@ pub struct CliArgs { #[structopt(long)] pub max_panes: Option, + /// Change where zellij looks for layouts and plugins + #[structopt(long)] + pub data_dir: Option, + /// Path to a layout yaml file #[structopt(short, long)] pub layout: Option, diff --git a/src/client/layout.rs b/src/client/layout.rs index 2524f894..e82f9045 100644 --- a/src/client/layout.rs +++ b/src/client/layout.rs @@ -1,6 +1,5 @@ -use directories_next::ProjectDirs; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::{fs::File, io::prelude::*}; use crate::panes::PositionAndSize; @@ -180,11 +179,9 @@ pub struct Layout { } impl Layout { - pub fn new(layout_path: PathBuf) -> Self { - let project_dirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap(); - let layout_dir = project_dirs.data_dir().join("layouts/"); + pub fn new(layout_path: &Path, data_dir: &Path) -> Self { + let layout_dir = data_dir.join("layouts/"); let mut layout_file = File::open(&layout_path) - .or_else(|_| File::open(&layout_path.with_extension("yaml"))) .or_else(|_| File::open(&layout_dir.join(&layout_path).with_extension("yaml"))) .unwrap_or_else(|_| panic!("cannot find layout {}", &layout_path.display())); diff --git a/src/common/install.rs b/src/common/install.rs index 8b070848..86fdd269 100644 --- a/src/common/install.rs +++ b/src/common/install.rs @@ -1,12 +1,41 @@ +use std::{fs, path::Path}; + +const VERSION: &str = env!("CARGO_PKG_VERSION"); + #[macro_export] macro_rules! asset_map { ($($src:literal => $dst:literal),+ $(,)?) => { { let mut assets = std::collections::HashMap::new(); $( - assets.insert($dst, include_bytes!(concat!("../", $src)).to_vec()); + assets.insert($dst, include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/", $src)).to_vec()); )+ assets } } } + +pub fn populate_data_dir(data_dir: &Path) { + // First run installation of default plugins & layouts + let mut assets = asset_map! { + "assets/layouts/default.yaml" => "layouts/default.yaml", + "assets/layouts/strider.yaml" => "layouts/strider.yaml", + }; + assets.extend(asset_map! { + "assets/plugins/status-bar.wasm" => "plugins/status-bar.wasm", + "assets/plugins/tab-bar.wasm" => "plugins/tab-bar.wasm", + "assets/plugins/strider.wasm" => "plugins/strider.wasm", + }); + assets.insert("VERSION", VERSION.as_bytes().to_vec()); + + let last_version = fs::read_to_string(data_dir.join("VERSION")).unwrap_or_default(); + let out_of_date = VERSION != last_version; + + for (path, bytes) in assets { + let path = data_dir.join(path); + fs::create_dir_all(path.parent().unwrap()).unwrap(); + if out_of_date || !path.exists() { + fs::write(path, bytes).expect("Failed to install default assets!"); + } + } +} diff --git a/src/common/mod.rs b/src/common/mod.rs index 88eb7214..860d12db 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -33,6 +33,7 @@ use errors::{ ScreenContext, }; use input::handler::input_loop; +use install::populate_data_dir; use os_input_output::OsApi; use pty_bus::{PtyBus, PtyInstruction}; use screen::{Screen, ScreenInstruction}; @@ -167,12 +168,22 @@ pub fn start(mut os_input: Box, opts: CliArgs) { opts.debug, ); + // Determine and initialize the data directory + let project_dirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap(); + let data_dir = opts + .data_dir + .unwrap_or_else(|| project_dirs.data_dir().to_path_buf()); + populate_data_dir(&data_dir); + // Don't use default layouts in tests, but do everywhere else #[cfg(not(test))] let default_layout = Some(PathBuf::from("default")); #[cfg(test)] let default_layout = None; - let maybe_layout = opts.layout.or(default_layout).map(Layout::new); + let maybe_layout = opts + .layout + .or(default_layout) + .map(|p| Layout::new(&p, &data_dir)); #[cfg(not(test))] std::panic::set_hook({ @@ -443,9 +454,7 @@ pub fn start(mut os_input: Box, opts: CliArgs) { err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event))); match event { PluginInstruction::Load(pid_tx, path) => { - let project_dirs = - ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap(); - let plugin_dir = project_dirs.data_dir().join("plugins/"); + let plugin_dir = data_dir.join("plugins/"); let wasm_bytes = fs::read(&path) .or_else(|_| fs::read(&path.with_extension("wasm"))) .or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm"))) diff --git a/src/main.rs b/src/main.rs index d6cbb05d..ec249e8a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,23 +1,10 @@ -#[cfg(test)] -mod tests; - mod cli; mod common; +#[cfg(test)] +mod tests; // TODO mod server; mod client; -use client::{boundaries, layout, panes, tab}; -use common::{ - command_is_executing, errors, os_input_output, pty_bus, screen, start, utils, wasm_vm, - ApiCommand, -}; -use directories_next::ProjectDirs; - -use std::os::unix::net::UnixStream; -use std::{fs, io::Write}; - -use structopt::StructOpt; - use crate::cli::CliArgs; use crate::command_is_executing::CommandIsExecuting; use crate::os_input_output::get_os_input; @@ -25,29 +12,16 @@ use crate::utils::{ consts::{ZELLIJ_IPC_PIPE, ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR}, logging::*, }; +use client::{boundaries, layout, panes, tab}; +use common::{ + command_is_executing, errors, os_input_output, pty_bus, screen, start, utils, wasm_vm, + ApiCommand, +}; +use std::io::Write; +use std::os::unix::net::UnixStream; +use structopt::StructOpt; pub fn main() { - // First run installation of default plugins & layouts - let project_dirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap(); - let data_dir = project_dirs.data_dir(); - let mut assets = asset_map! { - "assets/layouts/default.yaml" => "layouts/default.yaml", - "assets/layouts/strider.yaml" => "layouts/strider.yaml", - }; - assets.extend(asset_map! { - "assets/plugins/status-bar.wasm" => "plugins/status-bar.wasm", - "assets/plugins/tab-bar.wasm" => "plugins/tab-bar.wasm", - "assets/plugins/strider.wasm" => "plugins/strider.wasm", - }); - - for (path, bytes) in assets { - let path = data_dir.join(path); - fs::create_dir_all(path.parent().unwrap()).unwrap(); - if !path.exists() { - fs::write(path, bytes).expect("Failed to install default assets!"); - } - } - let opts = CliArgs::from_args(); if let Some(split_dir) = opts.split { match split_dir {