split start into start_server and start_client

This commit is contained in:
Kunal Mohan 2021-03-27 19:55:22 +05:30
parent 2059d2c6aa
commit 4f088e8185
12 changed files with 524 additions and 550 deletions

View file

@ -4,4 +4,488 @@ pub mod pane_resizer;
pub mod panes;
pub mod tab;
pub fn _start_client() {}
use std::sync::mpsc;
use std::thread;
use std::{collections::HashMap, fs};
use std::{
collections::HashSet,
io::Write,
str::FromStr,
sync::{Arc, Mutex},
};
use directories_next::ProjectDirs;
use serde::{Deserialize, Serialize};
use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value};
use wasmer_wasi::{Pipe, WasiState};
use zellij_tile::data::{EventType, InputMode};
use crate::cli::CliArgs;
use crate::common::{
command_is_executing::CommandIsExecuting,
errors::{AppContext, ContextType, PluginContext, ScreenContext},
input::handler::input_loop,
os_input_output::{ClientOsApi, ServerOsApiInstruction},
pty_bus::PtyInstruction,
screen::{Screen, ScreenInstruction},
wasm_vm::{wasi_stdout, wasi_write_string, zellij_imports, PluginEnv, PluginInstruction},
ChannelWithContext, SenderType, SenderWithContext, SyncChannelWithContext, OPENCALLS,
};
use crate::layout::Layout;
use crate::server::ServerInstruction;
/// Instructions sent from server to client
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ClientInstruction {
ToScreen(ScreenInstruction),
ClosePluginPane(u32),
Error(String),
DoneClosingPane,
Exit,
}
/// Instructions related to the client-side application.
#[derive(Clone)]
pub enum AppInstruction {
Exit,
Error(String),
ToPty(PtyInstruction),
ToScreen(ScreenInstruction),
ToPlugin(PluginInstruction),
OsApi(ServerOsApiInstruction),
DoneClosingPane,
}
impl From<ClientInstruction> for AppInstruction {
fn from(item: ClientInstruction) -> Self {
match item {
ClientInstruction::ToScreen(s) => AppInstruction::ToScreen(s),
ClientInstruction::Error(e) => AppInstruction::Error(e),
ClientInstruction::ClosePluginPane(p) => {
AppInstruction::ToPlugin(PluginInstruction::Unload(p))
}
ClientInstruction::DoneClosingPane => AppInstruction::DoneClosingPane,
ClientInstruction::Exit => AppInstruction::Exit,
}
}
}
pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs) {
let take_snapshot = "\u{1b}[?1049h";
os_input.unset_raw_mode(0);
let _ = os_input
.get_stdout_writer()
.write(take_snapshot.as_bytes())
.unwrap();
let mut command_is_executing = CommandIsExecuting::new();
let full_screen_ws = os_input.get_terminal_size_using_fd(0);
os_input.set_raw_mode(0);
let (send_screen_instructions, receive_screen_instructions): ChannelWithContext<
ScreenInstruction,
> = mpsc::channel();
let err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
let mut send_screen_instructions =
SenderWithContext::new(err_ctx, SenderType::Sender(send_screen_instructions));
let (send_plugin_instructions, receive_plugin_instructions): ChannelWithContext<
PluginInstruction,
> = mpsc::channel();
let send_plugin_instructions =
SenderWithContext::new(err_ctx, SenderType::Sender(send_plugin_instructions));
let (send_app_instructions, receive_app_instructions): SyncChannelWithContext<AppInstruction> =
mpsc::sync_channel(500);
let mut send_app_instructions =
SenderWithContext::new(err_ctx, SenderType::SyncSender(send_app_instructions));
os_input.connect_to_server();
#[cfg(not(test))]
std::panic::set_hook({
use crate::errors::handle_panic;
let send_app_instructions = send_app_instructions.clone();
Box::new(move |info| {
handle_panic(info, &send_app_instructions);
})
});
let screen_thread = thread::Builder::new()
.name("screen".to_string())
.spawn({
let mut command_is_executing = command_is_executing.clone();
let os_input = os_input.clone();
let send_plugin_instructions = send_plugin_instructions.clone();
let send_app_instructions = send_app_instructions.clone();
let max_panes = opts.max_panes;
move || {
let mut screen = Screen::new(
receive_screen_instructions,
send_plugin_instructions,
send_app_instructions,
&full_screen_ws,
os_input,
max_panes,
InputMode::Normal,
);
loop {
let (event, mut err_ctx) = screen
.receiver
.recv()
.expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event)));
screen.send_app_instructions.update(err_ctx);
match event {
ScreenInstruction::Pty(pid, vte_event) => {
screen
.get_active_tab_mut()
.unwrap()
.handle_pty_event(pid, vte_event);
}
ScreenInstruction::Render => {
screen.render();
}
ScreenInstruction::NewPane(pid) => {
screen.get_active_tab_mut().unwrap().new_pane(pid);
command_is_executing.done_opening_new_pane();
}
ScreenInstruction::HorizontalSplit(pid) => {
screen.get_active_tab_mut().unwrap().horizontal_split(pid);
command_is_executing.done_opening_new_pane();
}
ScreenInstruction::VerticalSplit(pid) => {
screen.get_active_tab_mut().unwrap().vertical_split(pid);
command_is_executing.done_opening_new_pane();
}
ScreenInstruction::WriteCharacter(bytes) => {
screen
.get_active_tab_mut()
.unwrap()
.write_to_active_terminal(bytes);
}
ScreenInstruction::ResizeLeft => {
screen.get_active_tab_mut().unwrap().resize_left();
}
ScreenInstruction::ResizeRight => {
screen.get_active_tab_mut().unwrap().resize_right();
}
ScreenInstruction::ResizeDown => {
screen.get_active_tab_mut().unwrap().resize_down();
}
ScreenInstruction::ResizeUp => {
screen.get_active_tab_mut().unwrap().resize_up();
}
ScreenInstruction::MoveFocus => {
screen.get_active_tab_mut().unwrap().move_focus();
}
ScreenInstruction::MoveFocusLeft => {
screen.get_active_tab_mut().unwrap().move_focus_left();
}
ScreenInstruction::MoveFocusDown => {
screen.get_active_tab_mut().unwrap().move_focus_down();
}
ScreenInstruction::MoveFocusRight => {
screen.get_active_tab_mut().unwrap().move_focus_right();
}
ScreenInstruction::MoveFocusUp => {
screen.get_active_tab_mut().unwrap().move_focus_up();
}
ScreenInstruction::ScrollUp => {
screen
.get_active_tab_mut()
.unwrap()
.scroll_active_terminal_up();
}
ScreenInstruction::ScrollDown => {
screen
.get_active_tab_mut()
.unwrap()
.scroll_active_terminal_down();
}
ScreenInstruction::ClearScroll => {
screen
.get_active_tab_mut()
.unwrap()
.clear_active_terminal_scroll();
}
ScreenInstruction::CloseFocusedPane => {
screen.get_active_tab_mut().unwrap().close_focused_pane();
screen.render();
}
ScreenInstruction::SetSelectable(id, selectable) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_selectable(id, selectable);
}
ScreenInstruction::SetMaxHeight(id, max_height) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_max_height(id, max_height);
}
ScreenInstruction::SetInvisibleBorders(id, invisible_borders) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_invisible_borders(id, invisible_borders);
screen.render();
}
ScreenInstruction::ClosePane(id) => {
screen.get_active_tab_mut().unwrap().close_pane(id);
screen.render();
}
ScreenInstruction::ToggleActiveTerminalFullscreen => {
screen
.get_active_tab_mut()
.unwrap()
.toggle_active_pane_fullscreen();
}
ScreenInstruction::NewTab(pane_id) => {
screen.new_tab(pane_id);
command_is_executing.done_updating_tabs();
}
ScreenInstruction::SwitchTabNext => {
screen.switch_tab_next();
command_is_executing.done_updating_tabs();
}
ScreenInstruction::SwitchTabPrev => {
screen.switch_tab_prev();
command_is_executing.done_updating_tabs();
}
ScreenInstruction::CloseTab => {
screen.close_tab();
command_is_executing.done_updating_tabs();
}
ScreenInstruction::ApplyLayout((layout, new_pane_pids)) => {
screen.apply_layout(Layout::new(layout), new_pane_pids);
command_is_executing.done_updating_tabs();
}
ScreenInstruction::GoToTab(tab_index) => {
screen.go_to_tab(tab_index as usize);
command_is_executing.done_updating_tabs();
}
ScreenInstruction::UpdateTabName(c) => {
screen.update_active_tab_name(c);
command_is_executing.done_updating_tabs();
}
ScreenInstruction::ChangeInputMode(input_mode) => {
screen.change_input_mode(input_mode);
}
ScreenInstruction::Exit => {
break;
}
}
}
}
})
.unwrap();
let wasm_thread = thread::Builder::new()
.name("wasm".to_string())
.spawn({
let mut send_screen_instructions = send_screen_instructions.clone();
let mut send_app_instructions = send_app_instructions.clone();
let store = Store::default();
let mut plugin_id = 0;
let mut plugin_map = HashMap::new();
move || loop {
let (event, mut err_ctx) = receive_plugin_instructions
.recv()
.expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event)));
send_screen_instructions.update(err_ctx);
send_app_instructions.update(err_ctx);
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 wasm_bytes = fs::read(&path)
.or_else(|_| fs::read(&path.with_extension("wasm")))
.or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm")))
.unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display()));
// FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that
let module = Module::new(&store, &wasm_bytes).unwrap();
let output = Pipe::new();
let input = Pipe::new();
let mut wasi_env = WasiState::new("Zellij")
.env("CLICOLOR_FORCE", "1")
.preopen(|p| {
p.directory(".") // FIXME: Change this to a more meaningful dir
.alias(".")
.read(true)
.write(true)
.create(true)
})
.unwrap()
.stdin(Box::new(input))
.stdout(Box::new(output))
.finalize()
.unwrap();
let wasi = wasi_env.import_object(&module).unwrap();
let plugin_env = PluginEnv {
plugin_id,
send_screen_instructions: send_screen_instructions.clone(),
send_app_instructions: send_app_instructions.clone(),
wasi_env,
subscriptions: Arc::new(Mutex::new(HashSet::new())),
};
let zellij = zellij_imports(&store, &plugin_env);
let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap();
let start = instance.exports.get_function("_start").unwrap();
// This eventually calls the `.init()` method
start.call(&[]).unwrap();
plugin_map.insert(plugin_id, (instance, plugin_env));
pid_tx.send(plugin_id).unwrap();
plugin_id += 1;
}
PluginInstruction::Update(pid, event) => {
for (&i, (instance, plugin_env)) in &plugin_map {
let subs = plugin_env.subscriptions.lock().unwrap();
// FIXME: This is very janky... Maybe I should write my own macro for Event -> EventType?
let event_type = EventType::from_str(&event.to_string()).unwrap();
if (pid.is_none() || pid == Some(i)) && subs.contains(&event_type) {
let update = instance.exports.get_function("update").unwrap();
wasi_write_string(
&plugin_env.wasi_env,
&serde_json::to_string(&event).unwrap(),
);
update.call(&[]).unwrap();
}
}
drop(send_screen_instructions.send(ScreenInstruction::Render));
}
PluginInstruction::Render(buf_tx, pid, rows, cols) => {
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
let render = instance.exports.get_function("render").unwrap();
render
.call(&[Value::I32(rows as i32), Value::I32(cols as i32)])
.unwrap();
buf_tx.send(wasi_stdout(&plugin_env.wasi_env)).unwrap();
}
PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)),
PluginInstruction::Exit => break,
}
}
})
.unwrap();
let _stdin_thread = thread::Builder::new()
.name("stdin_handler".to_string())
.spawn({
let send_screen_instructions = send_screen_instructions.clone();
let send_plugin_instructions = send_plugin_instructions.clone();
let send_app_instructions = send_app_instructions.clone();
let command_is_executing = command_is_executing.clone();
let os_input = os_input.clone();
move || {
input_loop(
os_input,
command_is_executing,
send_screen_instructions,
send_plugin_instructions,
send_app_instructions,
)
}
});
let router_thread = thread::Builder::new()
.name("router".to_string())
.spawn({
let os_input = os_input.clone();
move || loop {
let (instruction, err_ctx) = os_input.client_recv();
send_app_instructions.update(err_ctx);
match instruction {
ClientInstruction::Exit => break,
_ => {
send_app_instructions
.send(AppInstruction::from(instruction))
.unwrap();
}
}
}
})
.unwrap();
#[warn(clippy::never_loop)]
loop {
let (app_instruction, mut err_ctx) = receive_app_instructions
.recv()
.expect("failed to receive app instruction on channel");
err_ctx.add_call(ContextType::App(AppContext::from(&app_instruction)));
send_screen_instructions.update(err_ctx);
os_input.update_senders(err_ctx);
match app_instruction {
AppInstruction::Exit => break,
AppInstruction::Error(backtrace) => {
let _ = os_input.send_to_server(ServerInstruction::ClientExit);
let _ = send_screen_instructions.send(ScreenInstruction::Exit);
let _ = send_plugin_instructions.send(PluginInstruction::Exit);
let _ = screen_thread.join();
let _ = wasm_thread.join();
os_input.unset_raw_mode(0);
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
let error = format!("{}\n{}", goto_start_of_last_line, backtrace);
let _ = os_input
.get_stdout_writer()
.write(error.as_bytes())
.unwrap();
std::process::exit(1);
}
AppInstruction::ToScreen(instruction) => {
send_screen_instructions.send(instruction).unwrap();
}
AppInstruction::ToPlugin(instruction) => {
send_plugin_instructions.send(instruction).unwrap();
}
AppInstruction::ToPty(instruction) => {
let _ = os_input.send_to_server(ServerInstruction::ToPty(instruction));
}
AppInstruction::OsApi(instruction) => {
let _ = os_input.send_to_server(ServerInstruction::OsApi(instruction));
}
AppInstruction::DoneClosingPane => command_is_executing.done_closing_pane(),
}
}
let _ = os_input.send_to_server(ServerInstruction::ClientExit);
let _ = send_screen_instructions.send(ScreenInstruction::Exit);
let _ = send_plugin_instructions.send(PluginInstruction::Exit);
screen_thread.join().unwrap();
wasm_thread.join().unwrap();
router_thread.join().unwrap();
// cleanup();
let reset_style = "\u{1b}[m";
let show_cursor = "\u{1b}[?25h";
let restore_snapshot = "\u{1b}[?1049l";
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
let goodbye_message = format!(
"{}\n{}{}{}Bye from Zellij!\n",
goto_start_of_last_line, restore_snapshot, reset_style, show_cursor
);
os_input.unset_raw_mode(0);
let _ = os_input
.get_stdout_writer()
.write(goodbye_message.as_bytes())
.unwrap();
os_input.get_stdout_writer().flush().unwrap();
}

View file

@ -1,8 +1,9 @@
//! `Tab`s holds multiple panes. It tracks their coordinates (x/y) and size,
//! as well as how they should be resized
use crate::client::pane_resizer::PaneResizer;
use crate::common::{input::handler::parse_keys, AppInstruction, SenderWithContext};
use crate::boundaries::colors;
use crate::client::AppInstruction;
use crate::common::{input::handler::parse_keys, SenderWithContext};
use crate::layout::Layout;
use crate::os_input_output::{ClientOsApi, ServerOsApiInstruction};
use crate::panes::{PaneId, PositionAndSize, TerminalPane};

View file

@ -1,9 +1,8 @@
//! Error context system based on a thread-local representation of the call stack, itself based on
//! the instructions that are sent between threads.
use super::{
os_input_output::ServerOsApiInstruction, AppInstruction, ServerInstruction, OPENCALLS,
};
use super::{os_input_output::ServerOsApiInstruction, ServerInstruction, OPENCALLS};
use crate::client::AppInstruction;
use crate::pty_bus::PtyInstruction;
use crate::screen::ScreenInstruction;
use serde::{Deserialize, Serialize};

View file

@ -1,9 +1,9 @@
//! Main input logic.
use super::actions::Action;
use super::keybinds::Keybinds;
use crate::common::input::config::Config;
use crate::common::{AppInstruction, SenderWithContext, OPENCALLS};
use super::keybinds::get_default_keybinds;
use crate::client::AppInstruction;
use crate::common::{SenderWithContext, OPENCALLS};
use crate::errors::ContextType;
use crate::os_input_output::ClientOsApi;
use crate::pty_bus::PtyInstruction;

View file

@ -9,44 +9,11 @@ pub mod setup;
pub mod utils;
pub mod wasm_vm;
use crate::panes::PaneId;
use crate::server::ServerInstruction;
use errors::ErrorContext;
use std::cell::RefCell;
use std::sync::mpsc;
use std::thread;
use std::{collections::HashMap, fs};
use std::{
collections::HashSet,
io::Write,
str::FromStr,
sync::{Arc, Mutex},
};
use crate::cli::CliArgs;
use crate::layout::Layout;
use crate::panes::PaneId;
use crate::server::{start_server, ServerInstruction};
use command_is_executing::CommandIsExecuting;
use directories_next::ProjectDirs;
use errors::{AppContext, ContextType, ErrorContext, PluginContext, ScreenContext};
use input::handler::input_loop;
use os_input_output::{ClientOsApi, ServerOsApi, ServerOsApiInstruction};
use pty_bus::PtyInstruction;
use screen::{Screen, ScreenInstruction};
use serde::{Deserialize, Serialize};
use wasm_vm::PluginEnv;
use wasm_vm::{wasi_stdout, wasi_write_string, zellij_imports, PluginInstruction};
use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value};
use wasmer_wasi::{Pipe, WasiState};
use zellij_tile::data::{EventType, InputMode};
/// Instructions sent from server to client
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ClientInstruction {
ToScreen(ScreenInstruction),
ClosePluginPane(u32),
Error(String),
DoneClosingPane,
Exit,
}
/// An [MPSC](mpsc) asynchronous channel with added error context.
pub type ChannelWithContext<T> = (
@ -97,495 +64,5 @@ unsafe impl<T: Clone> Sync for SenderWithContext<T> {}
thread_local!(
/// A key to some thread local storage (TLS) that holds a representation of the thread's call
/// stack in the form of an [`ErrorContext`].
static OPENCALLS: RefCell<ErrorContext> = RefCell::default()
pub static OPENCALLS: RefCell<ErrorContext> = RefCell::default()
);
/// Instructions related to the client-side application.
#[derive(Clone)]
pub enum AppInstruction {
Exit,
Error(String),
ToPty(PtyInstruction),
ToScreen(ScreenInstruction),
ToPlugin(PluginInstruction),
OsApi(ServerOsApiInstruction),
DoneClosingPane,
}
impl From<ClientInstruction> for AppInstruction {
fn from(item: ClientInstruction) -> Self {
match item {
ClientInstruction::ToScreen(s) => AppInstruction::ToScreen(s),
ClientInstruction::Error(e) => AppInstruction::Error(e),
ClientInstruction::ClosePluginPane(p) => {
AppInstruction::ToPlugin(PluginInstruction::Unload(p))
}
ClientInstruction::DoneClosingPane => AppInstruction::DoneClosingPane,
ClientInstruction::Exit => AppInstruction::Exit,
}
}
}
/// Start Zellij with the specified [`ClientOsApi`], [`ServerOsApi`] and command-line arguments.
// FIXME this should definitely be modularized and split into different functions.
pub fn start(
mut os_input: Box<dyn ClientOsApi>,
opts: CliArgs,
server_os_input: Box<dyn ServerOsApi>,
) {
let ipc_thread = start_server(server_os_input, opts.clone());
let take_snapshot = "\u{1b}[?1049h";
os_input.unset_raw_mode(0);
let _ = os_input
.get_stdout_writer()
.write(take_snapshot.as_bytes())
.unwrap();
let mut command_is_executing = CommandIsExecuting::new();
let full_screen_ws = os_input.get_terminal_size_using_fd(0);
os_input.set_raw_mode(0);
let (send_screen_instructions, receive_screen_instructions): ChannelWithContext<
ScreenInstruction,
> = mpsc::channel();
let send_screen_instructions =
SenderWithContext::new(SenderType::Sender(send_screen_instructions));
let (send_plugin_instructions, receive_plugin_instructions): ChannelWithContext<
PluginInstruction,
> = mpsc::channel();
let send_plugin_instructions =
SenderWithContext::new(SenderType::Sender(send_plugin_instructions));
let (send_app_instructions, receive_app_instructions): SyncChannelWithContext<AppInstruction> =
mpsc::sync_channel(500);
let mut send_app_instructions =
SenderWithContext::new(err_ctx, SenderType::SyncSender(send_app_instructions));
os_input.connect_to_server();
#[cfg(not(test))]
std::panic::set_hook({
use crate::errors::handle_panic;
let send_app_instructions = send_app_instructions.clone();
Box::new(move |info| {
handle_panic(info, &send_app_instructions);
})
});
let screen_thread = thread::Builder::new()
.name("screen".to_string())
.spawn({
let mut command_is_executing = command_is_executing.clone();
let os_input = os_input.clone();
let send_plugin_instructions = send_plugin_instructions.clone();
let send_app_instructions = send_app_instructions.clone();
let max_panes = opts.max_panes;
let colors = os_input.load_palette();
move || {
let mut screen = Screen::new(
receive_screen_instructions,
send_plugin_instructions,
send_app_instructions,
&full_screen_ws,
os_input,
max_panes,
ModeInfo {
palette: colors,
..ModeInfo::default()
},
InputMode::Normal,
colors,
);
loop {
let (event, mut err_ctx) = screen
.receiver
.recv()
.expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event)));
screen.send_app_instructions.update(err_ctx);
match event {
ScreenInstruction::PtyBytes(pid, vte_bytes) => {
let active_tab = screen.get_active_tab_mut().unwrap();
if active_tab.has_terminal_pid(pid) {
// it's most likely that this event is directed at the active tab
// look there first
active_tab.handle_pty_bytes(pid, vte_bytes);
} else {
// if this event wasn't directed at the active tab, start looking
// in other tabs
let all_tabs = screen.get_tabs_mut();
for tab in all_tabs.values_mut() {
if tab.has_terminal_pid(pid) {
tab.handle_pty_bytes(pid, vte_bytes);
break;
}
}
}
}
ScreenInstruction::Render => {
screen.render();
}
ScreenInstruction::NewPane(pid) => {
screen.get_active_tab_mut().unwrap().new_pane(pid);
command_is_executing.done_opening_new_pane();
}
ScreenInstruction::HorizontalSplit(pid) => {
screen.get_active_tab_mut().unwrap().horizontal_split(pid);
command_is_executing.done_opening_new_pane();
}
ScreenInstruction::VerticalSplit(pid) => {
screen.get_active_tab_mut().unwrap().vertical_split(pid);
command_is_executing.done_opening_new_pane();
}
ScreenInstruction::WriteCharacter(bytes) => {
let active_tab = screen.get_active_tab_mut().unwrap();
match active_tab.is_sync_panes_active() {
true => active_tab.write_to_terminals_on_current_tab(bytes),
false => active_tab.write_to_active_terminal(bytes),
}
}
ScreenInstruction::ResizeLeft => {
screen.get_active_tab_mut().unwrap().resize_left();
}
ScreenInstruction::ResizeRight => {
screen.get_active_tab_mut().unwrap().resize_right();
}
ScreenInstruction::ResizeDown => {
screen.get_active_tab_mut().unwrap().resize_down();
}
ScreenInstruction::ResizeUp => {
screen.get_active_tab_mut().unwrap().resize_up();
}
ScreenInstruction::SwitchFocus => {
screen.get_active_tab_mut().unwrap().move_focus();
}
ScreenInstruction::FocusNextPane => {
screen.get_active_tab_mut().unwrap().focus_next_pane();
}
ScreenInstruction::FocusPreviousPane => {
screen.get_active_tab_mut().unwrap().focus_previous_pane();
}
ScreenInstruction::MoveFocusLeft => {
screen.get_active_tab_mut().unwrap().move_focus_left();
}
ScreenInstruction::MoveFocusDown => {
screen.get_active_tab_mut().unwrap().move_focus_down();
}
ScreenInstruction::MoveFocusRight => {
screen.get_active_tab_mut().unwrap().move_focus_right();
}
ScreenInstruction::MoveFocusUp => {
screen.get_active_tab_mut().unwrap().move_focus_up();
}
ScreenInstruction::ScrollUp => {
screen
.get_active_tab_mut()
.unwrap()
.scroll_active_terminal_up();
}
ScreenInstruction::ScrollDown => {
screen
.get_active_tab_mut()
.unwrap()
.scroll_active_terminal_down();
}
ScreenInstruction::PageScrollUp => {
screen
.get_active_tab_mut()
.unwrap()
.scroll_active_terminal_up_page();
}
ScreenInstruction::PageScrollDown => {
screen
.get_active_tab_mut()
.unwrap()
.scroll_active_terminal_down_page();
}
ScreenInstruction::ClearScroll => {
screen
.get_active_tab_mut()
.unwrap()
.clear_active_terminal_scroll();
}
ScreenInstruction::CloseFocusedPane => {
screen.get_active_tab_mut().unwrap().close_focused_pane();
screen.render();
}
ScreenInstruction::SetSelectable(id, selectable) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_selectable(id, selectable);
}
ScreenInstruction::SetMaxHeight(id, max_height) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_max_height(id, max_height);
}
ScreenInstruction::SetInvisibleBorders(id, invisible_borders) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_invisible_borders(id, invisible_borders);
screen.render();
}
ScreenInstruction::ClosePane(id) => {
screen.get_active_tab_mut().unwrap().close_pane(id);
screen.render();
}
ScreenInstruction::ToggleActiveTerminalFullscreen => {
screen
.get_active_tab_mut()
.unwrap()
.toggle_active_pane_fullscreen();
}
ScreenInstruction::NewTab(pane_id) => {
screen.new_tab(pane_id);
command_is_executing.done_updating_tabs();
}
ScreenInstruction::SwitchTabNext => {
screen.switch_tab_next();
command_is_executing.done_updating_tabs();
}
ScreenInstruction::SwitchTabPrev => {
screen.switch_tab_prev();
command_is_executing.done_updating_tabs();
}
ScreenInstruction::CloseTab => {
screen.close_tab();
command_is_executing.done_updating_tabs();
}
ScreenInstruction::ApplyLayout((layout, new_pane_pids)) => {
screen.apply_layout(Layout::new(layout), new_pane_pids);
command_is_executing.done_updating_tabs();
}
ScreenInstruction::GoToTab(tab_index) => {
screen.go_to_tab(tab_index as usize);
command_is_executing.done_updating_tabs();
}
ScreenInstruction::UpdateTabName(c) => {
screen.update_active_tab_name(c);
command_is_executing.done_updating_tabs();
}
ScreenInstruction::ChangeInputMode(input_mode) => {
screen.change_input_mode(input_mode);
}
ScreenInstruction::Exit => {
break;
}
}
}
}
})
.unwrap();
let wasm_thread = thread::Builder::new()
.name("wasm".to_string())
.spawn({
let mut send_screen_instructions = send_screen_instructions.clone();
let mut send_app_instructions = send_app_instructions.clone();
let store = Store::default();
let mut plugin_id = 0;
let mut plugin_map = HashMap::new();
move || loop {
let (event, mut err_ctx) = receive_plugin_instructions
.recv()
.expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event)));
send_screen_instructions.update(err_ctx);
send_app_instructions.update(err_ctx);
match event {
PluginInstruction::Load(pid_tx, path) => {
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")))
.unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display()));
// FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that
let module = Module::new(&store, &wasm_bytes).unwrap();
let output = Pipe::new();
let input = Pipe::new();
let mut wasi_env = WasiState::new("Zellij")
.env("CLICOLOR_FORCE", "1")
.preopen(|p| {
p.directory(".") // FIXME: Change this to a more meaningful dir
.alias(".")
.read(true)
.write(true)
.create(true)
})
.unwrap()
.stdin(Box::new(input))
.stdout(Box::new(output))
.finalize()
.unwrap();
let wasi = wasi_env.import_object(&module).unwrap();
let plugin_env = PluginEnv {
plugin_id,
send_screen_instructions: send_screen_instructions.clone(),
send_app_instructions: send_app_instructions.clone(),
send_plugin_instructions: send_plugin_instructions.clone(),
wasi_env,
subscriptions: Arc::new(Mutex::new(HashSet::new())),
};
let zellij = zellij_exports(&store, &plugin_env);
let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap();
let start = instance.exports.get_function("_start").unwrap();
// This eventually calls the `.load()` method
start.call(&[]).unwrap();
plugin_map.insert(plugin_id, (instance, plugin_env));
pid_tx.send(plugin_id).unwrap();
plugin_id += 1;
}
PluginInstruction::Update(pid, event) => {
for (&i, (instance, plugin_env)) in &plugin_map {
let subs = plugin_env.subscriptions.lock().unwrap();
// FIXME: This is very janky... Maybe I should write my own macro for Event -> EventType?
let event_type = EventType::from_str(&event.to_string()).unwrap();
if (pid.is_none() || pid == Some(i)) && subs.contains(&event_type) {
let update = instance.exports.get_function("update").unwrap();
wasi_write_object(&plugin_env.wasi_env, &event);
update.call(&[]).unwrap();
}
}
drop(send_screen_instructions.send(ScreenInstruction::Render));
}
PluginInstruction::Render(buf_tx, pid, rows, cols) => {
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
let render = instance.exports.get_function("render").unwrap();
render
.call(&[Value::I32(rows as i32), Value::I32(cols as i32)])
.unwrap();
buf_tx.send(wasi_read_string(&plugin_env.wasi_env)).unwrap();
}
PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)),
PluginInstruction::Exit => break,
}
}
})
.unwrap();
let _stdin_thread = thread::Builder::new()
.name("stdin_handler".to_string())
.spawn({
let send_screen_instructions = send_screen_instructions.clone();
let send_plugin_instructions = send_plugin_instructions.clone();
let send_app_instructions = send_app_instructions.clone();
let command_is_executing = command_is_executing.clone();
let os_input = os_input.clone();
let config = config;
move || {
input_loop(
os_input,
config,
command_is_executing,
send_screen_instructions,
send_plugin_instructions,
send_app_instructions,
)
}
});
let router_thread = thread::Builder::new()
.name("router".to_string())
.spawn({
let os_input = os_input.clone();
move || loop {
let (instruction, err_ctx) = os_input.client_recv();
send_app_instructions.update(err_ctx);
match instruction {
ClientInstruction::Exit => break,
_ => {
send_app_instructions
.send(AppInstruction::from(instruction))
.unwrap();
}
}
}
})
.unwrap();
#[warn(clippy::never_loop)]
loop {
let (app_instruction, mut err_ctx) = receive_app_instructions
.recv()
.expect("failed to receive app instruction on channel");
err_ctx.add_call(ContextType::App(AppContext::from(&app_instruction)));
send_screen_instructions.update(err_ctx);
os_input.update_senders(err_ctx);
match app_instruction {
AppInstruction::Exit => break,
AppInstruction::Error(backtrace) => {
let _ = os_input.send_to_server(ServerInstruction::ClientExit);
let _ = send_screen_instructions.send(ScreenInstruction::Exit);
let _ = send_plugin_instructions.send(PluginInstruction::Exit);
let _ = screen_thread.join();
let _ = wasm_thread.join();
let _ = ipc_thread.join();
//let _ = router_thread.join();
os_input.unset_raw_mode(0);
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
let error = format!("{}\n{}", goto_start_of_last_line, backtrace);
let _ = os_input
.get_stdout_writer()
.write(error.as_bytes())
.unwrap();
std::process::exit(1);
}
AppInstruction::ToScreen(instruction) => {
send_screen_instructions.send(instruction).unwrap();
}
AppInstruction::ToPlugin(instruction) => {
send_plugin_instructions.send(instruction).unwrap();
}
AppInstruction::ToPty(instruction) => {
let _ = os_input.send_to_server(ServerInstruction::ToPty(instruction));
}
AppInstruction::OsApi(instruction) => {
let _ = os_input.send_to_server(ServerInstruction::OsApi(instruction));
}
AppInstruction::DoneClosingPane => command_is_executing.done_closing_pane(),
}
}
let _ = os_input.send_to_server(ServerInstruction::ClientExit);
let _ = send_screen_instructions.send(ScreenInstruction::Exit);
let _ = send_plugin_instructions.send(PluginInstruction::Exit);
screen_thread.join().unwrap();
wasm_thread.join().unwrap();
ipc_thread.join().unwrap();
router_thread.join().unwrap();
// cleanup();
let reset_style = "\u{1b}[m";
let show_cursor = "\u{1b}[?25h";
let restore_snapshot = "\u{1b}[?1049l";
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
let goodbye_message = format!(
"{}\n{}{}{}Bye from Zellij!\n",
goto_start_of_last_line, restore_snapshot, reset_style, show_cursor
);
os_input.unset_raw_mode(0);
let _ = os_input
.get_stdout_writer()
.write(goodbye_message.as_bytes())
.unwrap();
os_input.get_stdout_writer().flush().unwrap();
}

View file

@ -16,7 +16,7 @@ use std::path::PathBuf;
use std::process::{Child, Command};
use std::sync::{Arc, Mutex};
use crate::common::ClientInstruction;
use crate::client::ClientInstruction;
use crate::errors::ErrorContext;
use crate::panes::PositionAndSize;
use crate::server::ServerInstruction;

View file

@ -10,13 +10,14 @@ use ::vte;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use super::{ScreenInstruction, SenderWithContext, OPENCALLS};
use super::{SenderWithContext, OPENCALLS};
use crate::layout::Layout;
use crate::os_input_output::ServerOsApi;
use crate::utils::logging::debug_to_file;
use crate::{
errors::{ContextType, ErrorContext},
panes::PaneId,
screen::ScreenInstruction,
server::ServerInstruction,
};

View file

@ -7,7 +7,8 @@ use std::path::PathBuf;
use std::str;
use std::sync::mpsc::Receiver;
use super::{AppInstruction, SenderWithContext};
use crate::client::AppInstruction;
use crate::common::SenderWithContext;
use crate::os_input_output::ClientOsApi;
use crate::panes::PositionAndSize;
use crate::pty_bus::{PtyInstruction, VteBytes};

View file

@ -11,9 +11,8 @@ use wasmer::{imports, Function, ImportObject, Store, WasmerEnv};
use wasmer_wasi::WasiEnv;
use zellij_tile::data::{Event, EventType, PluginIds};
use super::{
pty_bus::PtyInstruction, screen::ScreenInstruction, AppInstruction, PaneId, SenderWithContext,
};
use super::{pty_bus::PtyInstruction, screen::ScreenInstruction, PaneId, SenderWithContext};
use crate::client::AppInstruction;
#[derive(Clone, Debug)]
pub enum PluginInstruction {

View file

@ -3,18 +3,16 @@ mod client;
mod common;
mod server;
use client::{boundaries, layout, panes, tab};
use common::{
command_is_executing, errors, os_input_output, pty_bus, screen, start, utils, wasm_vm,
};
use client::{boundaries, layout, panes, start_client, tab};
use common::{command_is_executing, errors, os_input_output, pty_bus, screen, utils, wasm_vm};
use directories_next::ProjectDirs;
use server::ServerInstruction;
use server::{start_server, ServerInstruction};
use structopt::StructOpt;
use crate::cli::CliArgs;
use crate::command_is_executing::CommandIsExecuting;
use crate::os_input_output::{get_client_os_input, get_server_os_input, ClientOsApi};
use crate::os_input_output::{get_client_os_input, get_server_os_input, ClientOsApi, ServerOsApi};
use crate::pty_bus::VteEvent;
use crate::utils::{
consts::{ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR},
@ -83,3 +81,15 @@ pub fn main() {
start(Box::new(os_input), opts, Box::new(server_os_input));
}
}
/// Start Zellij with the specified [`ClientOsApi`], [`ServerOsApi`] and command-line arguments.
// FIXME this should definitely be modularized and split into different functions.
pub fn start(
client_os_input: Box<dyn ClientOsApi>,
opts: CliArgs,
server_os_input: Box<dyn ServerOsApi>,
) {
let ipc_thread = start_server(server_os_input, opts.clone());
start_client(client_os_input, opts);
drop(ipc_thread.join());
}

View file

@ -1,5 +1,6 @@
use crate::cli::CliArgs;
use crate::common::{ChannelWithContext, ClientInstruction, SenderType, SenderWithContext};
use crate::client::ClientInstruction;
use crate::common::{ChannelWithContext, SenderType, SenderWithContext};
use crate::errors::{ContextType, ErrorContext, OsContext, PtyContext, ServerContext};
use crate::os_input_output::{ServerOsApi, ServerOsApiInstruction};
use crate::panes::PaneId;

View file

@ -7,7 +7,8 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{mpsc, Arc, Mutex};
use std::time::{Duration, Instant};
use crate::common::{ChannelWithContext, ClientInstruction, SenderType, SenderWithContext};
use crate::client::ClientInstruction;
use crate::common::{ChannelWithContext, SenderType, SenderWithContext};
use crate::errors::ErrorContext;
use crate::os_input_output::{ClientOsApi, ServerOsApi};
use crate::server::ServerInstruction;