split start into start_server and start_client
This commit is contained in:
parent
2059d2c6aa
commit
4f088e8185
12 changed files with 524 additions and 550 deletions
|
|
@ -4,4 +4,488 @@ pub mod pane_resizer;
|
||||||
pub mod panes;
|
pub mod panes;
|
||||||
pub mod tab;
|
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();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
//! `Tab`s holds multiple panes. It tracks their coordinates (x/y) and size,
|
//! `Tab`s holds multiple panes. It tracks their coordinates (x/y) and size,
|
||||||
//! as well as how they should be resized
|
//! as well as how they should be resized
|
||||||
|
|
||||||
use crate::client::pane_resizer::PaneResizer;
|
use crate::boundaries::colors;
|
||||||
use crate::common::{input::handler::parse_keys, AppInstruction, SenderWithContext};
|
use crate::client::AppInstruction;
|
||||||
|
use crate::common::{input::handler::parse_keys, SenderWithContext};
|
||||||
use crate::layout::Layout;
|
use crate::layout::Layout;
|
||||||
use crate::os_input_output::{ClientOsApi, ServerOsApiInstruction};
|
use crate::os_input_output::{ClientOsApi, ServerOsApiInstruction};
|
||||||
use crate::panes::{PaneId, PositionAndSize, TerminalPane};
|
use crate::panes::{PaneId, PositionAndSize, TerminalPane};
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
//! Error context system based on a thread-local representation of the call stack, itself based on
|
//! Error context system based on a thread-local representation of the call stack, itself based on
|
||||||
//! the instructions that are sent between threads.
|
//! the instructions that are sent between threads.
|
||||||
|
|
||||||
use super::{
|
use super::{os_input_output::ServerOsApiInstruction, ServerInstruction, OPENCALLS};
|
||||||
os_input_output::ServerOsApiInstruction, AppInstruction, ServerInstruction, OPENCALLS,
|
use crate::client::AppInstruction;
|
||||||
};
|
|
||||||
use crate::pty_bus::PtyInstruction;
|
use crate::pty_bus::PtyInstruction;
|
||||||
use crate::screen::ScreenInstruction;
|
use crate::screen::ScreenInstruction;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
//! Main input logic.
|
//! Main input logic.
|
||||||
|
|
||||||
use super::actions::Action;
|
use super::actions::Action;
|
||||||
use super::keybinds::Keybinds;
|
use super::keybinds::get_default_keybinds;
|
||||||
use crate::common::input::config::Config;
|
use crate::client::AppInstruction;
|
||||||
use crate::common::{AppInstruction, SenderWithContext, OPENCALLS};
|
use crate::common::{SenderWithContext, OPENCALLS};
|
||||||
use crate::errors::ContextType;
|
use crate::errors::ContextType;
|
||||||
use crate::os_input_output::ClientOsApi;
|
use crate::os_input_output::ClientOsApi;
|
||||||
use crate::pty_bus::PtyInstruction;
|
use crate::pty_bus::PtyInstruction;
|
||||||
|
|
|
||||||
|
|
@ -9,44 +9,11 @@ pub mod setup;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod wasm_vm;
|
pub mod wasm_vm;
|
||||||
|
|
||||||
|
use crate::panes::PaneId;
|
||||||
|
use crate::server::ServerInstruction;
|
||||||
|
use errors::ErrorContext;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::sync::mpsc;
|
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.
|
/// An [MPSC](mpsc) asynchronous channel with added error context.
|
||||||
pub type ChannelWithContext<T> = (
|
pub type ChannelWithContext<T> = (
|
||||||
|
|
@ -97,495 +64,5 @@ unsafe impl<T: Clone> Sync for SenderWithContext<T> {}
|
||||||
thread_local!(
|
thread_local!(
|
||||||
/// A key to some thread local storage (TLS) that holds a representation of the thread's call
|
/// A key to some thread local storage (TLS) that holds a representation of the thread's call
|
||||||
/// stack in the form of an [`ErrorContext`].
|
/// 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();
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ use std::path::PathBuf;
|
||||||
use std::process::{Child, Command};
|
use std::process::{Child, Command};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use crate::common::ClientInstruction;
|
use crate::client::ClientInstruction;
|
||||||
use crate::errors::ErrorContext;
|
use crate::errors::ErrorContext;
|
||||||
use crate::panes::PositionAndSize;
|
use crate::panes::PositionAndSize;
|
||||||
use crate::server::ServerInstruction;
|
use crate::server::ServerInstruction;
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,14 @@ use ::vte;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::{ScreenInstruction, SenderWithContext, OPENCALLS};
|
use super::{SenderWithContext, OPENCALLS};
|
||||||
use crate::layout::Layout;
|
use crate::layout::Layout;
|
||||||
use crate::os_input_output::ServerOsApi;
|
use crate::os_input_output::ServerOsApi;
|
||||||
use crate::utils::logging::debug_to_file;
|
use crate::utils::logging::debug_to_file;
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{ContextType, ErrorContext},
|
errors::{ContextType, ErrorContext},
|
||||||
panes::PaneId,
|
panes::PaneId,
|
||||||
|
screen::ScreenInstruction,
|
||||||
server::ServerInstruction,
|
server::ServerInstruction,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ use std::path::PathBuf;
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::sync::mpsc::Receiver;
|
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::os_input_output::ClientOsApi;
|
||||||
use crate::panes::PositionAndSize;
|
use crate::panes::PositionAndSize;
|
||||||
use crate::pty_bus::{PtyInstruction, VteBytes};
|
use crate::pty_bus::{PtyInstruction, VteBytes};
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,8 @@ use wasmer::{imports, Function, ImportObject, Store, WasmerEnv};
|
||||||
use wasmer_wasi::WasiEnv;
|
use wasmer_wasi::WasiEnv;
|
||||||
use zellij_tile::data::{Event, EventType, PluginIds};
|
use zellij_tile::data::{Event, EventType, PluginIds};
|
||||||
|
|
||||||
use super::{
|
use super::{pty_bus::PtyInstruction, screen::ScreenInstruction, PaneId, SenderWithContext};
|
||||||
pty_bus::PtyInstruction, screen::ScreenInstruction, AppInstruction, PaneId, SenderWithContext,
|
use crate::client::AppInstruction;
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum PluginInstruction {
|
pub enum PluginInstruction {
|
||||||
|
|
|
||||||
22
src/main.rs
22
src/main.rs
|
|
@ -3,18 +3,16 @@ mod client;
|
||||||
mod common;
|
mod common;
|
||||||
mod server;
|
mod server;
|
||||||
|
|
||||||
use client::{boundaries, layout, panes, tab};
|
use client::{boundaries, layout, panes, start_client, tab};
|
||||||
use common::{
|
use common::{command_is_executing, errors, os_input_output, pty_bus, screen, utils, wasm_vm};
|
||||||
command_is_executing, errors, os_input_output, pty_bus, screen, start, utils, wasm_vm,
|
|
||||||
};
|
|
||||||
use directories_next::ProjectDirs;
|
use directories_next::ProjectDirs;
|
||||||
use server::ServerInstruction;
|
use server::{start_server, ServerInstruction};
|
||||||
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
use crate::cli::CliArgs;
|
use crate::cli::CliArgs;
|
||||||
use crate::command_is_executing::CommandIsExecuting;
|
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::pty_bus::VteEvent;
|
||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
consts::{ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR},
|
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(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());
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::cli::CliArgs;
|
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::errors::{ContextType, ErrorContext, OsContext, PtyContext, ServerContext};
|
||||||
use crate::os_input_output::{ServerOsApi, ServerOsApiInstruction};
|
use crate::os_input_output::{ServerOsApi, ServerOsApiInstruction};
|
||||||
use crate::panes::PaneId;
|
use crate::panes::PaneId;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::{mpsc, Arc, Mutex};
|
use std::sync::{mpsc, Arc, Mutex};
|
||||||
use std::time::{Duration, Instant};
|
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::errors::ErrorContext;
|
||||||
use crate::os_input_output::{ClientOsApi, ServerOsApi};
|
use crate::os_input_output::{ClientOsApi, ServerOsApi};
|
||||||
use crate::server::ServerInstruction;
|
use crate::server::ServerInstruction;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue