Move screen and plugins to client side.
Remove AppInstruction enum spawn pty thread, screen thread and plugin thread on demand
This commit is contained in:
parent
ee14d5f5dd
commit
223ee743e1
12 changed files with 921 additions and 1031 deletions
|
|
@ -4,66 +4,32 @@ pub mod pane_resizer;
|
||||||
pub mod panes;
|
pub mod panes;
|
||||||
pub mod tab;
|
pub mod tab;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::io::Write;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::thread;
|
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::{
|
use crate::common::{
|
||||||
command_is_executing::CommandIsExecuting,
|
command_is_executing::CommandIsExecuting,
|
||||||
errors::{AppContext, ContextType, PluginContext, ScreenContext},
|
errors::{ClientContext, ContextType},
|
||||||
input::handler::input_loop,
|
input::handler::input_loop,
|
||||||
os_input_output::ClientOsApi,
|
os_input_output::ClientOsApi,
|
||||||
pty_bus::PtyInstruction,
|
SenderType, SenderWithContext, SyncChannelWithContext, OPENCALLS,
|
||||||
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;
|
use crate::server::ServerInstruction;
|
||||||
|
|
||||||
/// Instructions sent from server to client
|
/// Instructions related to the client-side application and sent from server to client
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub enum ClientInstruction {
|
pub enum ClientInstruction {
|
||||||
ToScreen(ScreenInstruction),
|
|
||||||
ClosePluginPane(u32),
|
|
||||||
Error(String),
|
Error(String),
|
||||||
|
Render(String),
|
||||||
DoneClosingPane,
|
DoneClosingPane,
|
||||||
|
DoneOpeningNewPane,
|
||||||
|
DoneUpdatingTabs,
|
||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Instructions related to the client-side application.
|
pub fn start_client(mut os_input: Box<dyn ClientOsApi>) {
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum AppInstruction {
|
|
||||||
Exit,
|
|
||||||
Error(String),
|
|
||||||
ToPty(PtyInstruction),
|
|
||||||
DoneClosingPane,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ClientInstruction> for AppInstruction {
|
|
||||||
fn from(item: ClientInstruction) -> Self {
|
|
||||||
match item {
|
|
||||||
ClientInstruction::Error(e) => AppInstruction::Error(e),
|
|
||||||
ClientInstruction::DoneClosingPane => AppInstruction::DoneClosingPane,
|
|
||||||
_ => panic!("Unsupported AppInstruction"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs) {
|
|
||||||
let take_snapshot = "\u{1b}[?1049h";
|
let take_snapshot = "\u{1b}[?1049h";
|
||||||
os_input.unset_raw_mode(0);
|
os_input.unset_raw_mode(0);
|
||||||
let _ = os_input
|
let _ = os_input
|
||||||
|
|
@ -75,378 +41,68 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs) {
|
||||||
|
|
||||||
let full_screen_ws = os_input.get_terminal_size_using_fd(0);
|
let full_screen_ws = os_input.get_terminal_size_using_fd(0);
|
||||||
os_input.set_raw_mode(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 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<
|
let (send_client_instructions, receive_client_instructions): SyncChannelWithContext<
|
||||||
PluginInstruction,
|
ClientInstruction,
|
||||||
> = mpsc::channel();
|
> = mpsc::sync_channel(500);
|
||||||
let send_plugin_instructions =
|
let mut send_client_instructions =
|
||||||
SenderWithContext::new(err_ctx, SenderType::Sender(send_plugin_instructions));
|
SenderWithContext::new(err_ctx, SenderType::SyncSender(send_client_instructions));
|
||||||
|
|
||||||
let (send_app_instructions, receive_app_instructions): SyncChannelWithContext<AppInstruction> =
|
os_input.connect_to_server(full_screen_ws);
|
||||||
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))]
|
#[cfg(not(test))]
|
||||||
std::panic::set_hook({
|
std::panic::set_hook({
|
||||||
use crate::errors::handle_panic;
|
use crate::errors::handle_panic;
|
||||||
let send_app_instructions = send_app_instructions.clone();
|
let send_client_instructions = send_client_instructions.clone();
|
||||||
Box::new(move |info| {
|
Box::new(move |info| {
|
||||||
handle_panic(info, &send_app_instructions);
|
handle_panic(info, &send_client_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);
|
|
||||||
screen.os_api.update_senders(err_ctx);
|
|
||||||
if let Some(t) = screen.get_active_tab_mut() {
|
|
||||||
t.os_api.update_senders(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()
|
let _stdin_thread = thread::Builder::new()
|
||||||
.name("stdin_handler".to_string())
|
.name("stdin_handler".to_string())
|
||||||
.spawn({
|
.spawn({
|
||||||
let send_screen_instructions = send_screen_instructions.clone();
|
let send_client_instructions = send_client_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 command_is_executing = command_is_executing.clone();
|
||||||
let os_input = os_input.clone();
|
let os_input = os_input.clone();
|
||||||
move || {
|
move || input_loop(os_input, command_is_executing, send_client_instructions)
|
||||||
input_loop(
|
|
||||||
os_input,
|
|
||||||
command_is_executing,
|
|
||||||
send_screen_instructions,
|
|
||||||
send_plugin_instructions,
|
|
||||||
send_app_instructions,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let router_thread = thread::Builder::new()
|
let router_thread = thread::Builder::new()
|
||||||
.name("router".to_string())
|
.name("router".to_string())
|
||||||
.spawn({
|
.spawn({
|
||||||
let mut send_screen_instructions = send_screen_instructions.clone();
|
|
||||||
let mut send_plugin_instructions = send_plugin_instructions.clone();
|
|
||||||
let os_input = os_input.clone();
|
let os_input = os_input.clone();
|
||||||
move || loop {
|
move || {
|
||||||
let (instruction, err_ctx) = os_input.client_recv();
|
loop {
|
||||||
send_app_instructions.update(err_ctx);
|
let (instruction, err_ctx) = os_input.client_recv();
|
||||||
send_screen_instructions.update(err_ctx);
|
send_client_instructions.update(err_ctx);
|
||||||
send_plugin_instructions.update(err_ctx);
|
if let ClientInstruction::Exit = instruction {
|
||||||
match instruction {
|
break;
|
||||||
ClientInstruction::Exit => break,
|
|
||||||
ClientInstruction::ClosePluginPane(p) => {
|
|
||||||
send_plugin_instructions
|
|
||||||
.send(PluginInstruction::Unload(p))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
ClientInstruction::ToScreen(s) => send_screen_instructions.send(s).unwrap(),
|
|
||||||
_ => {
|
|
||||||
send_app_instructions
|
|
||||||
.send(AppInstruction::from(instruction))
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
send_client_instructions.send(instruction).unwrap();
|
||||||
}
|
}
|
||||||
|
send_client_instructions
|
||||||
|
.send(ClientInstruction::Exit)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
#[warn(clippy::never_loop)]
|
#[warn(clippy::never_loop)]
|
||||||
loop {
|
loop {
|
||||||
let (app_instruction, mut err_ctx) = receive_app_instructions
|
let (client_instruction, mut err_ctx) = receive_client_instructions
|
||||||
.recv()
|
.recv()
|
||||||
.expect("failed to receive app instruction on channel");
|
.expect("failed to receive app instruction on channel");
|
||||||
|
|
||||||
err_ctx.add_call(ContextType::App(AppContext::from(&app_instruction)));
|
err_ctx.add_call(ContextType::Client(ClientContext::from(
|
||||||
send_screen_instructions.update(err_ctx);
|
&client_instruction,
|
||||||
|
)));
|
||||||
os_input.update_senders(err_ctx);
|
os_input.update_senders(err_ctx);
|
||||||
match app_instruction {
|
match client_instruction {
|
||||||
AppInstruction::Exit => break,
|
ClientInstruction::Exit => break,
|
||||||
AppInstruction::Error(backtrace) => {
|
ClientInstruction::Error(backtrace) => {
|
||||||
let _ = os_input.send_to_server(ServerInstruction::ClientExit);
|
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);
|
os_input.unset_raw_mode(0);
|
||||||
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
|
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 error = format!("{}\n{}", goto_start_of_last_line, backtrace);
|
||||||
|
|
@ -456,18 +112,20 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
AppInstruction::ToPty(instruction) => {
|
ClientInstruction::Render(output) => {
|
||||||
os_input.send_to_server(ServerInstruction::ToPty(instruction));
|
let mut stdout = os_input.get_stdout_writer();
|
||||||
|
stdout
|
||||||
|
.write_all(&output.as_bytes())
|
||||||
|
.expect("cannot write to stdout");
|
||||||
|
stdout.flush().expect("could not flush");
|
||||||
}
|
}
|
||||||
AppInstruction::DoneClosingPane => command_is_executing.done_closing_pane(),
|
ClientInstruction::DoneClosingPane => command_is_executing.done_closing_pane(),
|
||||||
|
ClientInstruction::DoneOpeningNewPane => command_is_executing.done_opening_new_pane(),
|
||||||
|
ClientInstruction::DoneUpdatingTabs => command_is_executing.done_updating_tabs(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = os_input.send_to_server(ServerInstruction::ClientExit);
|
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();
|
router_thread.join().unwrap();
|
||||||
|
|
||||||
// cleanup();
|
// cleanup();
|
||||||
|
|
@ -481,9 +139,7 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs) {
|
||||||
);
|
);
|
||||||
|
|
||||||
os_input.unset_raw_mode(0);
|
os_input.unset_raw_mode(0);
|
||||||
let _ = os_input
|
let mut stdout = os_input.get_stdout_writer();
|
||||||
.get_stdout_writer()
|
let _ = stdout.write(goodbye_message.as_bytes()).unwrap();
|
||||||
.write(goodbye_message.as_bytes())
|
stdout.flush().unwrap();
|
||||||
.unwrap();
|
|
||||||
os_input.get_stdout_writer().flush().unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ pub enum PaneId {
|
||||||
|
|
||||||
/// Contains the position and size of a [`Pane`], or more generally of any terminal, measured
|
/// Contains the position and size of a [`Pane`], or more generally of any terminal, measured
|
||||||
/// in character rows and columns.
|
/// in character rows and columns.
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
|
||||||
pub struct PositionAndSize {
|
pub struct PositionAndSize {
|
||||||
pub x: usize,
|
pub x: usize,
|
||||||
pub y: usize,
|
pub y: usize,
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,9 @@
|
||||||
//! as well as how they should be resized
|
//! as well as how they should be resized
|
||||||
|
|
||||||
use crate::boundaries::colors;
|
use crate::boundaries::colors;
|
||||||
use crate::client::AppInstruction;
|
|
||||||
use crate::common::{input::handler::parse_keys, SenderWithContext};
|
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::ServerOsApi;
|
||||||
use crate::panes::{PaneId, PositionAndSize, TerminalPane};
|
use crate::panes::{PaneId, PositionAndSize, TerminalPane};
|
||||||
use crate::pty_bus::{PtyInstruction, VteEvent};
|
use crate::pty_bus::{PtyInstruction, VteEvent};
|
||||||
use crate::server::ServerInstruction;
|
use crate::server::ServerInstruction;
|
||||||
|
|
@ -13,13 +12,12 @@ use crate::utils::shared::pad_to_size;
|
||||||
use crate::wasm_vm::PluginInstruction;
|
use crate::wasm_vm::PluginInstruction;
|
||||||
use crate::{boundaries::Boundaries, panes::PluginPane};
|
use crate::{boundaries::Boundaries, panes::PluginPane};
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
use std::time::Instant;
|
use std::sync::mpsc::channel;
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Reverse,
|
cmp::Reverse,
|
||||||
collections::{BTreeMap, HashSet},
|
collections::{BTreeMap, HashSet},
|
||||||
};
|
};
|
||||||
use std::{io::Write, sync::mpsc::channel};
|
use zellij_tile::data::{Event, InputMode};
|
||||||
use zellij_tile::data::{Event, InputMode, ModeInfo, Palette};
|
|
||||||
|
|
||||||
const CURSOR_HEIGHT_WIDTH_RATIO: usize = 4; // this is not accurate and kind of a magic number, TODO: look into this
|
const CURSOR_HEIGHT_WIDTH_RATIO: usize = 4; // this is not accurate and kind of a magic number, TODO: look into this
|
||||||
|
|
||||||
|
|
@ -68,9 +66,11 @@ pub struct Tab {
|
||||||
max_panes: Option<usize>,
|
max_panes: Option<usize>,
|
||||||
full_screen_ws: PositionAndSize,
|
full_screen_ws: PositionAndSize,
|
||||||
fullscreen_is_active: bool,
|
fullscreen_is_active: bool,
|
||||||
pub os_api: Box<dyn ClientOsApi>,
|
os_api: Box<dyn ServerOsApi>,
|
||||||
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||||
pub send_app_instructions: SenderWithContext<AppInstruction>,
|
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||||
|
send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||||
|
expansion_boundary: Option<PositionAndSize>,
|
||||||
should_clear_display_before_rendering: bool,
|
should_clear_display_before_rendering: bool,
|
||||||
pub mode_info: ModeInfo,
|
pub mode_info: ModeInfo,
|
||||||
pub input_mode: InputMode,
|
pub input_mode: InputMode,
|
||||||
|
|
@ -224,9 +224,10 @@ impl Tab {
|
||||||
position: usize,
|
position: usize,
|
||||||
name: String,
|
name: String,
|
||||||
full_screen_ws: &PositionAndSize,
|
full_screen_ws: &PositionAndSize,
|
||||||
os_api: Box<dyn ClientOsApi>,
|
mut os_api: Box<dyn ServerOsApi>,
|
||||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||||
send_app_instructions: SenderWithContext<AppInstruction>,
|
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||||
|
send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||||
max_panes: Option<usize>,
|
max_panes: Option<usize>,
|
||||||
pane_id: Option<PaneId>,
|
pane_id: Option<PaneId>,
|
||||||
mode_info: ModeInfo,
|
mode_info: ModeInfo,
|
||||||
|
|
@ -235,13 +236,11 @@ impl Tab {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let panes = if let Some(PaneId::Terminal(pid)) = pane_id {
|
let panes = if let Some(PaneId::Terminal(pid)) = pane_id {
|
||||||
let new_terminal = TerminalPane::new(pid, *full_screen_ws);
|
let new_terminal = TerminalPane::new(pid, *full_screen_ws);
|
||||||
os_api.send_to_server(ServerInstruction::OsApi(
|
os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
new_terminal.pid,
|
||||||
new_terminal.pid,
|
new_terminal.columns() as u16,
|
||||||
new_terminal.columns() as u16,
|
new_terminal.rows() as u16,
|
||||||
new_terminal.rows() as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
let mut panes: BTreeMap<PaneId, Box<dyn Pane>> = BTreeMap::new();
|
let mut panes: BTreeMap<PaneId, Box<dyn Pane>> = BTreeMap::new();
|
||||||
panes.insert(PaneId::Terminal(pid), Box::new(new_terminal));
|
panes.insert(PaneId::Terminal(pid), Box::new(new_terminal));
|
||||||
panes
|
panes
|
||||||
|
|
@ -260,8 +259,10 @@ impl Tab {
|
||||||
fullscreen_is_active: false,
|
fullscreen_is_active: false,
|
||||||
synchronize_is_active: false,
|
synchronize_is_active: false,
|
||||||
os_api,
|
os_api,
|
||||||
send_app_instructions,
|
|
||||||
send_plugin_instructions,
|
send_plugin_instructions,
|
||||||
|
send_pty_instructions,
|
||||||
|
send_server_instructions,
|
||||||
|
expansion_boundary: None,
|
||||||
should_clear_display_before_rendering: false,
|
should_clear_display_before_rendering: false,
|
||||||
mode_info,
|
mode_info,
|
||||||
input_mode,
|
input_mode,
|
||||||
|
|
@ -294,13 +295,11 @@ impl Tab {
|
||||||
terminal_pane.set_max_width(max_columns);
|
terminal_pane.set_max_width(max_columns);
|
||||||
}
|
}
|
||||||
terminal_pane.change_pos_and_size(&position_and_size);
|
terminal_pane.change_pos_and_size(&position_and_size);
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
*pid,
|
||||||
*pid,
|
position_and_size.columns as u16,
|
||||||
position_and_size.columns as u16,
|
position_and_size.rows as u16,
|
||||||
position_and_size.rows as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// we filled the entire layout, no room for this pane
|
// we filled the entire layout, no room for this pane
|
||||||
|
|
@ -342,13 +341,11 @@ impl Tab {
|
||||||
// there are still panes left to fill, use the pids we received in this method
|
// there are still panes left to fill, use the pids we received in this method
|
||||||
let pid = new_pids.next().unwrap(); // if this crashes it means we got less pids than there are panes in this layout
|
let pid = new_pids.next().unwrap(); // if this crashes it means we got less pids than there are panes in this layout
|
||||||
let new_terminal = TerminalPane::new(*pid, *position_and_size);
|
let new_terminal = TerminalPane::new(*pid, *position_and_size);
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
new_terminal.pid,
|
||||||
new_terminal.pid,
|
new_terminal.columns() as u16,
|
||||||
new_terminal.columns() as u16,
|
new_terminal.rows() as u16,
|
||||||
new_terminal.rows() as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
self.panes
|
self.panes
|
||||||
.insert(PaneId::Terminal(*pid), Box::new(new_terminal));
|
.insert(PaneId::Terminal(*pid), Box::new(new_terminal));
|
||||||
}
|
}
|
||||||
|
|
@ -357,10 +354,9 @@ impl Tab {
|
||||||
// this is a bit of a hack and happens because we don't have any central location that
|
// this is a bit of a hack and happens because we don't have any central location that
|
||||||
// can query the screen as to how many panes it needs to create a layout
|
// can query the screen as to how many panes it needs to create a layout
|
||||||
// fixing this will require a bit of an architecture change
|
// fixing this will require a bit of an architecture change
|
||||||
self.os_api
|
self.send_pty_instructions
|
||||||
.send_to_server(ServerInstruction::pty_close_pane(PaneId::Terminal(
|
.send(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid)))
|
||||||
*unused_pid,
|
.unwrap();
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
self.active_terminal = self.panes.iter().map(|(id, _)| id.to_owned()).next();
|
self.active_terminal = self.panes.iter().map(|(id, _)| id.to_owned()).next();
|
||||||
self.render();
|
self.render();
|
||||||
|
|
@ -373,13 +369,11 @@ impl Tab {
|
||||||
if !self.has_panes() {
|
if !self.has_panes() {
|
||||||
if let PaneId::Terminal(term_pid) = pid {
|
if let PaneId::Terminal(term_pid) = pid {
|
||||||
let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws);
|
let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws);
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
new_terminal.pid,
|
||||||
new_terminal.pid,
|
new_terminal.columns() as u16,
|
||||||
new_terminal.columns() as u16,
|
new_terminal.rows() as u16,
|
||||||
new_terminal.rows() as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
self.panes.insert(pid, Box::new(new_terminal));
|
self.panes.insert(pid, Box::new(new_terminal));
|
||||||
self.active_terminal = Some(pid);
|
self.active_terminal = Some(pid);
|
||||||
}
|
}
|
||||||
|
|
@ -405,8 +399,9 @@ impl Tab {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if terminal_id_to_split.is_none() {
|
if terminal_id_to_split.is_none() {
|
||||||
self.os_api
|
self.send_pty_instructions
|
||||||
.send_to_server(ServerInstruction::pty_close_pane(pid)); // we can't open this pane, close the pty
|
.send(PtyInstruction::ClosePane(pid))
|
||||||
|
.unwrap(); // we can't open this pane, close the pty
|
||||||
return; // likely no terminal large enough to split
|
return; // likely no terminal large enough to split
|
||||||
}
|
}
|
||||||
let terminal_id_to_split = terminal_id_to_split.unwrap();
|
let terminal_id_to_split = terminal_id_to_split.unwrap();
|
||||||
|
|
@ -424,23 +419,19 @@ impl Tab {
|
||||||
if let PaneId::Terminal(term_pid) = pid {
|
if let PaneId::Terminal(term_pid) = pid {
|
||||||
let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&terminal_ws);
|
let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&terminal_ws);
|
||||||
let new_terminal = TerminalPane::new(term_pid, bottom_winsize);
|
let new_terminal = TerminalPane::new(term_pid, bottom_winsize);
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
new_terminal.pid,
|
||||||
new_terminal.pid,
|
bottom_winsize.columns as u16,
|
||||||
bottom_winsize.columns as u16,
|
bottom_winsize.rows as u16,
|
||||||
bottom_winsize.rows as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
terminal_to_split.change_pos_and_size(&top_winsize);
|
terminal_to_split.change_pos_and_size(&top_winsize);
|
||||||
self.panes.insert(pid, Box::new(new_terminal));
|
self.panes.insert(pid, Box::new(new_terminal));
|
||||||
if let PaneId::Terminal(terminal_id_to_split) = terminal_id_to_split {
|
if let PaneId::Terminal(terminal_id_to_split) = terminal_id_to_split {
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
terminal_id_to_split,
|
||||||
terminal_id_to_split,
|
top_winsize.columns as u16,
|
||||||
top_winsize.columns as u16,
|
top_winsize.rows as u16,
|
||||||
top_winsize.rows as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
self.active_terminal = Some(pid);
|
self.active_terminal = Some(pid);
|
||||||
}
|
}
|
||||||
|
|
@ -448,23 +439,19 @@ impl Tab {
|
||||||
if let PaneId::Terminal(term_pid) = pid {
|
if let PaneId::Terminal(term_pid) = pid {
|
||||||
let (left_winsize, right_winsize) = split_vertically_with_gap(&terminal_ws);
|
let (left_winsize, right_winsize) = split_vertically_with_gap(&terminal_ws);
|
||||||
let new_terminal = TerminalPane::new(term_pid, right_winsize);
|
let new_terminal = TerminalPane::new(term_pid, right_winsize);
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
new_terminal.pid,
|
||||||
new_terminal.pid,
|
right_winsize.columns as u16,
|
||||||
right_winsize.columns as u16,
|
right_winsize.rows as u16,
|
||||||
right_winsize.rows as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
terminal_to_split.change_pos_and_size(&left_winsize);
|
terminal_to_split.change_pos_and_size(&left_winsize);
|
||||||
self.panes.insert(pid, Box::new(new_terminal));
|
self.panes.insert(pid, Box::new(new_terminal));
|
||||||
if let PaneId::Terminal(terminal_id_to_split) = terminal_id_to_split {
|
if let PaneId::Terminal(terminal_id_to_split) = terminal_id_to_split {
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
terminal_id_to_split,
|
||||||
terminal_id_to_split,
|
left_winsize.columns as u16,
|
||||||
left_winsize.columns as u16,
|
left_winsize.rows as u16,
|
||||||
left_winsize.rows as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -480,13 +467,11 @@ impl Tab {
|
||||||
if !self.has_panes() {
|
if !self.has_panes() {
|
||||||
if let PaneId::Terminal(term_pid) = pid {
|
if let PaneId::Terminal(term_pid) = pid {
|
||||||
let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws);
|
let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws);
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
new_terminal.pid,
|
||||||
new_terminal.pid,
|
new_terminal.columns() as u16,
|
||||||
new_terminal.columns() as u16,
|
new_terminal.rows() as u16,
|
||||||
new_terminal.rows() as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
self.panes.insert(pid, Box::new(new_terminal));
|
self.panes.insert(pid, Box::new(new_terminal));
|
||||||
self.active_terminal = Some(pid);
|
self.active_terminal = Some(pid);
|
||||||
}
|
}
|
||||||
|
|
@ -495,8 +480,9 @@ impl Tab {
|
||||||
let active_pane_id = &self.get_active_pane_id().unwrap();
|
let active_pane_id = &self.get_active_pane_id().unwrap();
|
||||||
let active_pane = self.panes.get_mut(active_pane_id).unwrap();
|
let active_pane = self.panes.get_mut(active_pane_id).unwrap();
|
||||||
if active_pane.rows() < MIN_TERMINAL_HEIGHT * 2 + 1 {
|
if active_pane.rows() < MIN_TERMINAL_HEIGHT * 2 + 1 {
|
||||||
self.os_api
|
self.send_pty_instructions
|
||||||
.send_to_server(ServerInstruction::pty_close_pane(pid)); // we can't open this pane, close the pty
|
.send(PtyInstruction::ClosePane(pid))
|
||||||
|
.unwrap(); // we can't open this pane, close the pty
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let terminal_ws = PositionAndSize {
|
let terminal_ws = PositionAndSize {
|
||||||
|
|
@ -510,23 +496,19 @@ impl Tab {
|
||||||
active_pane.change_pos_and_size(&top_winsize);
|
active_pane.change_pos_and_size(&top_winsize);
|
||||||
|
|
||||||
let new_terminal = TerminalPane::new(term_pid, bottom_winsize);
|
let new_terminal = TerminalPane::new(term_pid, bottom_winsize);
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
new_terminal.pid,
|
||||||
new_terminal.pid,
|
bottom_winsize.columns as u16,
|
||||||
bottom_winsize.columns as u16,
|
bottom_winsize.rows as u16,
|
||||||
bottom_winsize.rows as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
self.panes.insert(pid, Box::new(new_terminal));
|
self.panes.insert(pid, Box::new(new_terminal));
|
||||||
|
|
||||||
if let PaneId::Terminal(active_terminal_pid) = active_pane_id {
|
if let PaneId::Terminal(active_terminal_pid) = active_pane_id {
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
*active_terminal_pid,
|
||||||
*active_terminal_pid,
|
top_winsize.columns as u16,
|
||||||
top_winsize.columns as u16,
|
top_winsize.rows as u16,
|
||||||
top_winsize.rows as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.active_terminal = Some(pid);
|
self.active_terminal = Some(pid);
|
||||||
|
|
@ -541,13 +523,11 @@ impl Tab {
|
||||||
if !self.has_panes() {
|
if !self.has_panes() {
|
||||||
if let PaneId::Terminal(term_pid) = pid {
|
if let PaneId::Terminal(term_pid) = pid {
|
||||||
let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws);
|
let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws);
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
new_terminal.pid,
|
||||||
new_terminal.pid,
|
new_terminal.columns() as u16,
|
||||||
new_terminal.columns() as u16,
|
new_terminal.rows() as u16,
|
||||||
new_terminal.rows() as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
self.panes.insert(pid, Box::new(new_terminal));
|
self.panes.insert(pid, Box::new(new_terminal));
|
||||||
self.active_terminal = Some(pid);
|
self.active_terminal = Some(pid);
|
||||||
}
|
}
|
||||||
|
|
@ -556,8 +536,9 @@ impl Tab {
|
||||||
let active_pane_id = &self.get_active_pane_id().unwrap();
|
let active_pane_id = &self.get_active_pane_id().unwrap();
|
||||||
let active_pane = self.panes.get_mut(active_pane_id).unwrap();
|
let active_pane = self.panes.get_mut(active_pane_id).unwrap();
|
||||||
if active_pane.columns() < MIN_TERMINAL_WIDTH * 2 + 1 {
|
if active_pane.columns() < MIN_TERMINAL_WIDTH * 2 + 1 {
|
||||||
self.os_api
|
self.send_pty_instructions
|
||||||
.send_to_server(ServerInstruction::pty_close_pane(pid)); // we can't open this pane, close the pty
|
.send(PtyInstruction::ClosePane(pid))
|
||||||
|
.unwrap(); // we can't open this pane, close the pty
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let terminal_ws = PositionAndSize {
|
let terminal_ws = PositionAndSize {
|
||||||
|
|
@ -571,23 +552,19 @@ impl Tab {
|
||||||
active_pane.change_pos_and_size(&left_winsize);
|
active_pane.change_pos_and_size(&left_winsize);
|
||||||
|
|
||||||
let new_terminal = TerminalPane::new(term_pid, right_winsize);
|
let new_terminal = TerminalPane::new(term_pid, right_winsize);
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
new_terminal.pid,
|
||||||
new_terminal.pid,
|
right_winsize.columns as u16,
|
||||||
right_winsize.columns as u16,
|
right_winsize.rows as u16,
|
||||||
right_winsize.rows as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
self.panes.insert(pid, Box::new(new_terminal));
|
self.panes.insert(pid, Box::new(new_terminal));
|
||||||
|
|
||||||
if let PaneId::Terminal(active_terminal_pid) = active_pane_id {
|
if let PaneId::Terminal(active_terminal_pid) = active_pane_id {
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
*active_terminal_pid,
|
||||||
*active_terminal_pid,
|
left_winsize.columns as u16,
|
||||||
left_winsize.columns as u16,
|
left_winsize.rows as u16,
|
||||||
left_winsize.rows as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.active_terminal = Some(pid);
|
self.active_terminal = Some(pid);
|
||||||
|
|
@ -644,13 +621,13 @@ impl Tab {
|
||||||
match self.get_active_pane_id() {
|
match self.get_active_pane_id() {
|
||||||
Some(PaneId::Terminal(active_terminal_id)) => {
|
Some(PaneId::Terminal(active_terminal_id)) => {
|
||||||
let active_terminal = self.get_active_pane().unwrap();
|
let active_terminal = self.get_active_pane().unwrap();
|
||||||
let adjusted_input = active_terminal.adjust_input_to_terminal(input_bytes);
|
let mut adjusted_input = active_terminal.adjust_input_to_terminal(input_bytes);
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api
|
||||||
ServerOsApiInstruction::WriteToTtyStdin(active_terminal_id, adjusted_input),
|
.write_to_tty_stdin(active_terminal_id, &mut adjusted_input)
|
||||||
));
|
.expect("failed to write to terminal");
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api
|
||||||
ServerOsApiInstruction::TcDrain(active_terminal_id),
|
.tcdrain(active_terminal_id)
|
||||||
));
|
.expect("failed to drain terminal");
|
||||||
}
|
}
|
||||||
Some(PaneId::Plugin(pid)) => {
|
Some(PaneId::Plugin(pid)) => {
|
||||||
for key in parse_keys(&input_bytes) {
|
for key in parse_keys(&input_bytes) {
|
||||||
|
|
@ -712,13 +689,11 @@ impl Tab {
|
||||||
}
|
}
|
||||||
let active_terminal = self.panes.get(&active_pane_id).unwrap();
|
let active_terminal = self.panes.get(&active_pane_id).unwrap();
|
||||||
if let PaneId::Terminal(active_pid) = active_pane_id {
|
if let PaneId::Terminal(active_pid) = active_pane_id {
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
active_pid,
|
||||||
active_pid,
|
active_terminal.columns() as u16,
|
||||||
active_terminal.columns() as u16,
|
active_terminal.rows() as u16,
|
||||||
active_terminal.rows() as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
self.render();
|
self.render();
|
||||||
self.toggle_fullscreen_is_active();
|
self.toggle_fullscreen_is_active();
|
||||||
|
|
@ -747,30 +722,16 @@ impl Tab {
|
||||||
// in that case, we should not render as the app is exiting
|
// in that case, we should not render as the app is exiting
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// if any pane contain widechar, all pane in the same row will messup. We should render them every time
|
let mut output = String::new();
|
||||||
// FIXME: remove this when we can handle widechars correctly
|
|
||||||
if self.panes_contain_widechar() {
|
|
||||||
self.set_force_render()
|
|
||||||
}
|
|
||||||
let mut stdout = self.os_api.get_stdout_writer();
|
|
||||||
let mut boundaries = Boundaries::new(
|
let mut boundaries = Boundaries::new(
|
||||||
self.full_screen_ws.columns as u16,
|
self.full_screen_ws.columns as u16,
|
||||||
self.full_screen_ws.rows as u16,
|
self.full_screen_ws.rows as u16,
|
||||||
);
|
);
|
||||||
let hide_cursor = "\u{1b}[?25l";
|
let hide_cursor = "\u{1b}[?25l";
|
||||||
stdout
|
output.push_str(hide_cursor);
|
||||||
.write_all(&hide_cursor.as_bytes())
|
for (kind, terminal) in self.panes.iter_mut() {
|
||||||
.expect("cannot write to stdout");
|
if !self.panes_to_hide.contains(&terminal.pid()) {
|
||||||
if self.should_clear_display_before_rendering {
|
match self.active_terminal.unwrap() == terminal.pid() {
|
||||||
let clear_display = "\u{1b}[2J";
|
|
||||||
stdout
|
|
||||||
.write_all(&clear_display.as_bytes())
|
|
||||||
.expect("cannot write to stdout");
|
|
||||||
self.should_clear_display_before_rendering = false;
|
|
||||||
}
|
|
||||||
for (kind, pane) in self.panes.iter_mut() {
|
|
||||||
if !self.panes_to_hide.contains(&pane.pid()) {
|
|
||||||
match self.active_terminal.unwrap() == pane.pid() {
|
|
||||||
true => {
|
true => {
|
||||||
pane.set_active_at(Instant::now());
|
pane.set_active_at(Instant::now());
|
||||||
boundaries.add_rect(pane.as_ref(), self.mode_info.mode, Some(self.colors))
|
boundaries.add_rect(pane.as_ref(), self.mode_info.mode, Some(self.colors))
|
||||||
|
|
@ -784,48 +745,39 @@ impl Tab {
|
||||||
adjust_to_size(&vte_output, pane.rows(), pane.columns())
|
adjust_to_size(&vte_output, pane.rows(), pane.columns())
|
||||||
};
|
};
|
||||||
// FIXME: Use Termion for cursor and style clearing?
|
// FIXME: Use Termion for cursor and style clearing?
|
||||||
write!(
|
output.push_str(&format!(
|
||||||
stdout,
|
|
||||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||||
pane.y() + 1,
|
pane.y() + 1,
|
||||||
pane.x() + 1,
|
pane.x() + 1,
|
||||||
vte_output
|
vte_output
|
||||||
)
|
));
|
||||||
.expect("cannot write to stdout");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: only render (and calculate) boundaries if there was a resize
|
// TODO: only render (and calculate) boundaries if there was a resize
|
||||||
let vte_output = boundaries.vte_output();
|
output.push_str(&boundaries.vte_output());
|
||||||
stdout
|
|
||||||
.write_all(&vte_output.as_bytes())
|
|
||||||
.expect("cannot write to stdout");
|
|
||||||
|
|
||||||
match self.get_active_terminal_cursor_position() {
|
match self.get_active_terminal_cursor_position() {
|
||||||
Some((cursor_position_x, cursor_position_y)) => {
|
Some((cursor_position_x, cursor_position_y)) => {
|
||||||
let show_cursor = "\u{1b}[?25h";
|
let show_cursor = "\u{1b}[?25h";
|
||||||
let goto_cursor_position = format!(
|
let goto_cursor_position = &format!(
|
||||||
"\u{1b}[{};{}H\u{1b}[m",
|
"\u{1b}[{};{}H\u{1b}[m",
|
||||||
cursor_position_y + 1,
|
cursor_position_y + 1,
|
||||||
cursor_position_x + 1
|
cursor_position_x + 1
|
||||||
); // goto row/col
|
); // goto row/col
|
||||||
stdout
|
output.push_str(show_cursor);
|
||||||
.write_all(&show_cursor.as_bytes())
|
output.push_str(goto_cursor_position);
|
||||||
.expect("cannot write to stdout");
|
|
||||||
stdout
|
|
||||||
.write_all(&goto_cursor_position.as_bytes())
|
|
||||||
.expect("cannot write to stdout");
|
|
||||||
stdout.flush().expect("could not flush");
|
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let hide_cursor = "\u{1b}[?25l";
|
let hide_cursor = "\u{1b}[?25l";
|
||||||
stdout
|
output.push_str(hide_cursor);
|
||||||
.write_all(&hide_cursor.as_bytes())
|
|
||||||
.expect("cannot write to stdout");
|
|
||||||
stdout.flush().expect("could not flush");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.send_server_instructions
|
||||||
|
.send(ServerInstruction::Render(output))
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
fn get_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> {
|
fn get_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> {
|
||||||
self.panes.iter()
|
self.panes.iter()
|
||||||
|
|
@ -1278,104 +1230,88 @@ impl Tab {
|
||||||
let terminal = self.panes.get_mut(id).unwrap();
|
let terminal = self.panes.get_mut(id).unwrap();
|
||||||
terminal.reduce_height_down(count);
|
terminal.reduce_height_down(count);
|
||||||
if let PaneId::Terminal(pid) = id {
|
if let PaneId::Terminal(pid) = id {
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
*pid,
|
||||||
*pid,
|
terminal.columns() as u16,
|
||||||
terminal.columns() as u16,
|
terminal.rows() as u16,
|
||||||
terminal.rows() as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn reduce_pane_height_up(&mut self, id: &PaneId, count: usize) {
|
fn reduce_pane_height_up(&mut self, id: &PaneId, count: usize) {
|
||||||
let terminal = self.panes.get_mut(id).unwrap();
|
let terminal = self.panes.get_mut(id).unwrap();
|
||||||
terminal.reduce_height_up(count);
|
terminal.reduce_height_up(count);
|
||||||
if let PaneId::Terminal(pid) = id {
|
if let PaneId::Terminal(pid) = id {
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
*pid,
|
||||||
*pid,
|
terminal.columns() as u16,
|
||||||
terminal.columns() as u16,
|
terminal.rows() as u16,
|
||||||
terminal.rows() as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn increase_pane_height_down(&mut self, id: &PaneId, count: usize) {
|
fn increase_pane_height_down(&mut self, id: &PaneId, count: usize) {
|
||||||
let terminal = self.panes.get_mut(id).unwrap();
|
let terminal = self.panes.get_mut(id).unwrap();
|
||||||
terminal.increase_height_down(count);
|
terminal.increase_height_down(count);
|
||||||
if let PaneId::Terminal(pid) = terminal.pid() {
|
if let PaneId::Terminal(pid) = terminal.pid() {
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
pid,
|
||||||
pid,
|
terminal.columns() as u16,
|
||||||
terminal.columns() as u16,
|
terminal.rows() as u16,
|
||||||
terminal.rows() as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn increase_pane_height_up(&mut self, id: &PaneId, count: usize) {
|
fn increase_pane_height_up(&mut self, id: &PaneId, count: usize) {
|
||||||
let terminal = self.panes.get_mut(id).unwrap();
|
let terminal = self.panes.get_mut(id).unwrap();
|
||||||
terminal.increase_height_up(count);
|
terminal.increase_height_up(count);
|
||||||
if let PaneId::Terminal(pid) = terminal.pid() {
|
if let PaneId::Terminal(pid) = terminal.pid() {
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
pid,
|
||||||
pid,
|
terminal.columns() as u16,
|
||||||
terminal.columns() as u16,
|
terminal.rows() as u16,
|
||||||
terminal.rows() as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn increase_pane_width_right(&mut self, id: &PaneId, count: usize) {
|
fn increase_pane_width_right(&mut self, id: &PaneId, count: usize) {
|
||||||
let terminal = self.panes.get_mut(id).unwrap();
|
let terminal = self.panes.get_mut(id).unwrap();
|
||||||
terminal.increase_width_right(count);
|
terminal.increase_width_right(count);
|
||||||
if let PaneId::Terminal(pid) = terminal.pid() {
|
if let PaneId::Terminal(pid) = terminal.pid() {
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
pid,
|
||||||
pid,
|
terminal.columns() as u16,
|
||||||
terminal.columns() as u16,
|
terminal.rows() as u16,
|
||||||
terminal.rows() as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn increase_pane_width_left(&mut self, id: &PaneId, count: usize) {
|
fn increase_pane_width_left(&mut self, id: &PaneId, count: usize) {
|
||||||
let terminal = self.panes.get_mut(id).unwrap();
|
let terminal = self.panes.get_mut(id).unwrap();
|
||||||
terminal.increase_width_left(count);
|
terminal.increase_width_left(count);
|
||||||
if let PaneId::Terminal(pid) = terminal.pid() {
|
if let PaneId::Terminal(pid) = terminal.pid() {
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
pid,
|
||||||
pid,
|
terminal.columns() as u16,
|
||||||
terminal.columns() as u16,
|
terminal.rows() as u16,
|
||||||
terminal.rows() as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn reduce_pane_width_right(&mut self, id: &PaneId, count: usize) {
|
fn reduce_pane_width_right(&mut self, id: &PaneId, count: usize) {
|
||||||
let terminal = self.panes.get_mut(id).unwrap();
|
let terminal = self.panes.get_mut(id).unwrap();
|
||||||
terminal.reduce_width_right(count);
|
terminal.reduce_width_right(count);
|
||||||
if let PaneId::Terminal(pid) = terminal.pid() {
|
if let PaneId::Terminal(pid) = terminal.pid() {
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
pid,
|
||||||
pid,
|
terminal.columns() as u16,
|
||||||
terminal.columns() as u16,
|
terminal.rows() as u16,
|
||||||
terminal.rows() as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn reduce_pane_width_left(&mut self, id: &PaneId, count: usize) {
|
fn reduce_pane_width_left(&mut self, id: &PaneId, count: usize) {
|
||||||
let terminal = self.panes.get_mut(id).unwrap();
|
let terminal = self.panes.get_mut(id).unwrap();
|
||||||
terminal.reduce_width_left(count);
|
terminal.reduce_width_left(count);
|
||||||
if let PaneId::Terminal(pid) = terminal.pid() {
|
if let PaneId::Terminal(pid) = terminal.pid() {
|
||||||
self.os_api.send_to_server(ServerInstruction::OsApi(
|
self.os_api.set_terminal_size_using_fd(
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
pid,
|
||||||
pid,
|
terminal.columns() as u16,
|
||||||
terminal.columns() as u16,
|
terminal.rows() as u16,
|
||||||
terminal.rows() as u16,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn pane_is_between_vertical_borders(
|
fn pane_is_between_vertical_borders(
|
||||||
|
|
@ -2144,8 +2080,9 @@ impl Tab {
|
||||||
if let Some(max_panes) = self.max_panes {
|
if let Some(max_panes) = self.max_panes {
|
||||||
let terminals = self.get_pane_ids();
|
let terminals = self.get_pane_ids();
|
||||||
for &pid in terminals.iter().skip(max_panes - 1) {
|
for &pid in terminals.iter().skip(max_panes - 1) {
|
||||||
self.os_api
|
self.send_pty_instructions
|
||||||
.send_to_server(ServerInstruction::pty_close_pane(pid));
|
.send(PtyInstruction::ClosePane(pid))
|
||||||
|
.unwrap();
|
||||||
self.close_pane_without_rerender(pid);
|
self.close_pane_without_rerender(pid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2255,8 +2192,9 @@ impl Tab {
|
||||||
pub fn close_focused_pane(&mut self) {
|
pub fn close_focused_pane(&mut self) {
|
||||||
if let Some(active_pane_id) = self.get_active_pane_id() {
|
if let Some(active_pane_id) = self.get_active_pane_id() {
|
||||||
self.close_pane(active_pane_id);
|
self.close_pane(active_pane_id);
|
||||||
self.os_api
|
self.send_pty_instructions
|
||||||
.send_to_server(ServerInstruction::pty_close_pane(active_pane_id));
|
.send(PtyInstruction::ClosePane(active_pane_id))
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn scroll_active_terminal_up(&mut self) {
|
pub fn scroll_active_terminal_up(&mut self) {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +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::{os_input_output::ServerOsApiInstruction, ServerInstruction, OPENCALLS};
|
use super::{ServerInstruction, OPENCALLS};
|
||||||
use crate::client::AppInstruction;
|
use crate::client::ClientInstruction;
|
||||||
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};
|
||||||
|
|
@ -21,7 +21,7 @@ use std::panic::PanicInfo;
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
pub fn handle_panic(
|
pub fn handle_panic(
|
||||||
info: &PanicInfo<'_>,
|
info: &PanicInfo<'_>,
|
||||||
send_app_instructions: &SenderWithContext<AppInstruction>,
|
send_app_instructions: &SenderWithContext<ClientInstruction>,
|
||||||
) {
|
) {
|
||||||
use backtrace::Backtrace;
|
use backtrace::Backtrace;
|
||||||
use std::{process, thread};
|
use std::{process, thread};
|
||||||
|
|
@ -68,9 +68,7 @@ pub fn handle_panic(
|
||||||
println!("{}", backtrace);
|
println!("{}", backtrace);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
} else {
|
} else {
|
||||||
send_app_instructions
|
let _ = send_app_instructions.send(ClientInstruction::Error(backtrace));
|
||||||
.send(AppInstruction::Error(backtrace))
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,19 +130,17 @@ impl Display for ErrorContext {
|
||||||
///
|
///
|
||||||
/// Complex variants store a variant of a related enum, whose variants can be built from
|
/// Complex variants store a variant of a related enum, whose variants can be built from
|
||||||
/// the corresponding Zellij MSPC instruction enum variants ([`ScreenInstruction`],
|
/// the corresponding Zellij MSPC instruction enum variants ([`ScreenInstruction`],
|
||||||
/// [`PtyInstruction`], [`AppInstruction`], etc).
|
/// [`PtyInstruction`], [`ClientInstruction`], etc).
|
||||||
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum ContextType {
|
pub enum ContextType {
|
||||||
/// A screen-related call.
|
/// A screen-related call.
|
||||||
Screen(ScreenContext),
|
Screen(ScreenContext),
|
||||||
/// A PTY-related call.
|
/// A PTY-related call.
|
||||||
Pty(PtyContext),
|
Pty(PtyContext),
|
||||||
/// An OS-related call.
|
|
||||||
Os(OsContext),
|
|
||||||
/// A plugin-related call.
|
/// A plugin-related call.
|
||||||
Plugin(PluginContext),
|
Plugin(PluginContext),
|
||||||
/// An app-related call.
|
/// An app-related call.
|
||||||
App(AppContext),
|
Client(ClientContext),
|
||||||
/// A server-related call.
|
/// A server-related call.
|
||||||
IPCServer(ServerContext),
|
IPCServer(ServerContext),
|
||||||
StdinHandler,
|
StdinHandler,
|
||||||
|
|
@ -162,9 +158,8 @@ impl Display for ContextType {
|
||||||
match *self {
|
match *self {
|
||||||
ContextType::Screen(c) => write!(f, "{}screen_thread: {}{:?}", purple, green, c),
|
ContextType::Screen(c) => write!(f, "{}screen_thread: {}{:?}", purple, green, c),
|
||||||
ContextType::Pty(c) => write!(f, "{}pty_thread: {}{:?}", purple, green, c),
|
ContextType::Pty(c) => write!(f, "{}pty_thread: {}{:?}", purple, green, c),
|
||||||
ContextType::Os(c) => write!(f, "{}os_thread: {}{:?}", purple, green, c),
|
|
||||||
ContextType::Plugin(c) => write!(f, "{}plugin_thread: {}{:?}", purple, green, c),
|
ContextType::Plugin(c) => write!(f, "{}plugin_thread: {}{:?}", purple, green, c),
|
||||||
ContextType::App(c) => write!(f, "{}main_thread: {}{:?}", purple, green, c),
|
ContextType::Client(c) => write!(f, "{}main_thread: {}{:?}", purple, green, c),
|
||||||
ContextType::IPCServer(c) => write!(f, "{}ipc_server: {}{:?}", purple, green, c),
|
ContextType::IPCServer(c) => write!(f, "{}ipc_server: {}{:?}", purple, green, c),
|
||||||
ContextType::StdinHandler => {
|
ContextType::StdinHandler => {
|
||||||
write!(f, "{}stdin_handler_thread: {}AcceptInput", purple, green)
|
write!(f, "{}stdin_handler_thread: {}AcceptInput", purple, green)
|
||||||
|
|
@ -257,7 +252,7 @@ impl From<&ScreenInstruction> for ScreenContext {
|
||||||
ScreenInstruction::SetInvisibleBorders(..) => ScreenContext::SetInvisibleBorders,
|
ScreenInstruction::SetInvisibleBorders(..) => ScreenContext::SetInvisibleBorders,
|
||||||
ScreenInstruction::SetMaxHeight(..) => ScreenContext::SetMaxHeight,
|
ScreenInstruction::SetMaxHeight(..) => ScreenContext::SetMaxHeight,
|
||||||
ScreenInstruction::ClosePane(_) => ScreenContext::ClosePane,
|
ScreenInstruction::ClosePane(_) => ScreenContext::ClosePane,
|
||||||
ScreenInstruction::ApplyLayout(_) => ScreenContext::ApplyLayout,
|
ScreenInstruction::ApplyLayout(..) => ScreenContext::ApplyLayout,
|
||||||
ScreenInstruction::NewTab(_) => ScreenContext::NewTab,
|
ScreenInstruction::NewTab(_) => ScreenContext::NewTab,
|
||||||
ScreenInstruction::SwitchTabNext => ScreenContext::SwitchTabNext,
|
ScreenInstruction::SwitchTabNext => ScreenContext::SwitchTabNext,
|
||||||
ScreenInstruction::SwitchTabPrev => ScreenContext::SwitchTabPrev,
|
ScreenInstruction::SwitchTabPrev => ScreenContext::SwitchTabPrev,
|
||||||
|
|
@ -297,28 +292,6 @@ impl From<&PtyInstruction> for PtyContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stack call representations corresponding to the different types of [`ServerOsApiInstruction`]s.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub enum OsContext {
|
|
||||||
SetTerminalSizeUsingFd,
|
|
||||||
WriteToTtyStdin,
|
|
||||||
TcDrain,
|
|
||||||
Exit,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&ServerOsApiInstruction> for OsContext {
|
|
||||||
fn from(os_instruction: &ServerOsApiInstruction) -> Self {
|
|
||||||
match *os_instruction {
|
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(_, _, _) => {
|
|
||||||
OsContext::SetTerminalSizeUsingFd
|
|
||||||
}
|
|
||||||
ServerOsApiInstruction::WriteToTtyStdin(_, _) => OsContext::WriteToTtyStdin,
|
|
||||||
ServerOsApiInstruction::TcDrain(_) => OsContext::TcDrain,
|
|
||||||
ServerOsApiInstruction::Exit => OsContext::Exit,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: This whole pattern *needs* a macro eventually, it's soul-crushing to write
|
// FIXME: This whole pattern *needs* a macro eventually, it's soul-crushing to write
|
||||||
|
|
||||||
use crate::wasm_vm::PluginInstruction;
|
use crate::wasm_vm::PluginInstruction;
|
||||||
|
|
@ -345,22 +318,26 @@ impl From<&PluginInstruction> for PluginContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stack call representations corresponding to the different types of [`AppInstruction`]s.
|
/// Stack call representations corresponding to the different types of [`ClientInstruction`]s.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum AppContext {
|
pub enum ClientContext {
|
||||||
Exit,
|
Exit,
|
||||||
Error,
|
Error,
|
||||||
DoneClosingPane,
|
DoneClosingPane,
|
||||||
ToPty,
|
DoneOpeningNewPane,
|
||||||
|
DoneUpdatingTabs,
|
||||||
|
Render,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&AppInstruction> for AppContext {
|
impl From<&ClientInstruction> for ClientContext {
|
||||||
fn from(app_instruction: &AppInstruction) -> Self {
|
fn from(client_instruction: &ClientInstruction) -> Self {
|
||||||
match *app_instruction {
|
match *client_instruction {
|
||||||
AppInstruction::Exit => AppContext::Exit,
|
ClientInstruction::Exit => ClientContext::Exit,
|
||||||
AppInstruction::Error(_) => AppContext::Error,
|
ClientInstruction::Error(_) => ClientContext::Error,
|
||||||
AppInstruction::DoneClosingPane => AppContext::DoneClosingPane,
|
ClientInstruction::Render(_) => ClientContext::Render,
|
||||||
AppInstruction::ToPty(_) => AppContext::ToPty,
|
ClientInstruction::DoneClosingPane => ClientContext::DoneClosingPane,
|
||||||
|
ClientInstruction::DoneOpeningNewPane => ClientContext::DoneOpeningNewPane,
|
||||||
|
ClientInstruction::DoneUpdatingTabs => ClientContext::DoneUpdatingTabs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -375,10 +352,13 @@ pub enum ServerContext {
|
||||||
NewClient,
|
NewClient,
|
||||||
ToPty,
|
ToPty,
|
||||||
ToScreen,
|
ToScreen,
|
||||||
OsApi,
|
Render,
|
||||||
|
PluginUpdate,
|
||||||
DoneClosingPane,
|
DoneClosingPane,
|
||||||
ClosePluginPane,
|
DoneOpeningNewPane,
|
||||||
|
DoneUpdatingTabs,
|
||||||
ClientExit,
|
ClientExit,
|
||||||
|
ClientShouldExit,
|
||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -389,13 +369,16 @@ impl From<&ServerInstruction> for ServerContext {
|
||||||
ServerInstruction::SplitHorizontally => ServerContext::SplitHorizontally,
|
ServerInstruction::SplitHorizontally => ServerContext::SplitHorizontally,
|
||||||
ServerInstruction::SplitVertically => ServerContext::SplitVertically,
|
ServerInstruction::SplitVertically => ServerContext::SplitVertically,
|
||||||
ServerInstruction::MoveFocus => ServerContext::MoveFocus,
|
ServerInstruction::MoveFocus => ServerContext::MoveFocus,
|
||||||
ServerInstruction::NewClient(_) => ServerContext::NewClient,
|
ServerInstruction::NewClient(..) => ServerContext::NewClient,
|
||||||
ServerInstruction::ToPty(_) => ServerContext::ToPty,
|
ServerInstruction::ToPty(_) => ServerContext::ToPty,
|
||||||
ServerInstruction::ToScreen(_) => ServerContext::ToScreen,
|
ServerInstruction::ToScreen(_) => ServerContext::ToScreen,
|
||||||
ServerInstruction::OsApi(_) => ServerContext::OsApi,
|
ServerInstruction::PluginUpdate(..) => ServerContext::PluginUpdate,
|
||||||
|
ServerInstruction::Render(_) => ServerContext::Render,
|
||||||
ServerInstruction::DoneClosingPane => ServerContext::DoneClosingPane,
|
ServerInstruction::DoneClosingPane => ServerContext::DoneClosingPane,
|
||||||
ServerInstruction::ClosePluginPane(_) => ServerContext::ClosePluginPane,
|
ServerInstruction::DoneOpeningNewPane => ServerContext::DoneOpeningNewPane,
|
||||||
|
ServerInstruction::DoneUpdatingTabs => ServerContext::DoneUpdatingTabs,
|
||||||
ServerInstruction::ClientExit => ServerContext::ClientExit,
|
ServerInstruction::ClientExit => ServerContext::ClientExit,
|
||||||
|
ServerInstruction::ClientShouldExit => ServerContext::ClientShouldExit,
|
||||||
ServerInstruction::Exit => ServerContext::Exit,
|
ServerInstruction::Exit => ServerContext::Exit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,12 @@
|
||||||
|
|
||||||
use super::actions::Action;
|
use super::actions::Action;
|
||||||
use super::keybinds::get_default_keybinds;
|
use super::keybinds::get_default_keybinds;
|
||||||
use crate::client::AppInstruction;
|
use crate::client::ClientInstruction;
|
||||||
use crate::common::{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;
|
||||||
use crate::screen::ScreenInstruction;
|
|
||||||
use crate::server::ServerInstruction;
|
use crate::server::ServerInstruction;
|
||||||
use crate::wasm_vm::PluginInstruction;
|
|
||||||
use crate::CommandIsExecuting;
|
use crate::CommandIsExecuting;
|
||||||
|
|
||||||
use termion::input::{TermRead, TermReadEventsAndRaw};
|
use termion::input::{TermRead, TermReadEventsAndRaw};
|
||||||
|
|
@ -22,10 +20,7 @@ struct InputHandler {
|
||||||
mode: InputMode,
|
mode: InputMode,
|
||||||
os_input: Box<dyn ClientOsApi>,
|
os_input: Box<dyn ClientOsApi>,
|
||||||
command_is_executing: CommandIsExecuting,
|
command_is_executing: CommandIsExecuting,
|
||||||
send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
send_client_instructions: SenderWithContext<ClientInstruction>,
|
||||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
|
||||||
send_app_instructions: SenderWithContext<AppInstruction>,
|
|
||||||
should_exit: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputHandler {
|
impl InputHandler {
|
||||||
|
|
@ -33,20 +28,14 @@ impl InputHandler {
|
||||||
fn new(
|
fn new(
|
||||||
os_input: Box<dyn ClientOsApi>,
|
os_input: Box<dyn ClientOsApi>,
|
||||||
command_is_executing: CommandIsExecuting,
|
command_is_executing: CommandIsExecuting,
|
||||||
config: Config,
|
send_client_instructions: SenderWithContext<ClientInstruction>,
|
||||||
send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
|
||||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
|
||||||
send_app_instructions: SenderWithContext<AppInstruction>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
InputHandler {
|
InputHandler {
|
||||||
mode: InputMode::Normal,
|
mode: InputMode::Normal,
|
||||||
os_input,
|
os_input,
|
||||||
config,
|
config,
|
||||||
command_is_executing,
|
command_is_executing,
|
||||||
send_screen_instructions,
|
send_client_instructions,
|
||||||
send_plugin_instructions,
|
|
||||||
send_app_instructions,
|
|
||||||
should_exit: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,8 +44,7 @@ impl InputHandler {
|
||||||
fn handle_input(&mut self) {
|
fn handle_input(&mut self) {
|
||||||
let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
|
let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
|
||||||
err_ctx.add_call(ContextType::StdinHandler);
|
err_ctx.add_call(ContextType::StdinHandler);
|
||||||
self.send_app_instructions.update(err_ctx);
|
self.send_client_instructions.update(err_ctx);
|
||||||
self.send_screen_instructions.update(err_ctx);
|
|
||||||
self.os_input.update_senders(err_ctx);
|
self.os_input.update_senders(err_ctx);
|
||||||
if let Ok(keybinds) = get_default_keybinds() {
|
if let Ok(keybinds) = get_default_keybinds() {
|
||||||
'input_loop: loop {
|
'input_loop: loop {
|
||||||
|
|
@ -122,12 +110,10 @@ impl InputHandler {
|
||||||
|
|
||||||
match action {
|
match action {
|
||||||
Action::Write(val) => {
|
Action::Write(val) => {
|
||||||
self.send_screen_instructions
|
self.os_input
|
||||||
.send(ScreenInstruction::ClearScroll)
|
.send_to_server(ServerInstruction::clear_scroll());
|
||||||
.unwrap();
|
self.os_input
|
||||||
self.send_screen_instructions
|
.send_to_server(ServerInstruction::write_character(val));
|
||||||
.send(ScreenInstruction::WriteCharacter(val))
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
Action::Quit => {
|
Action::Quit => {
|
||||||
self.exit();
|
self.exit();
|
||||||
|
|
@ -135,64 +121,42 @@ impl InputHandler {
|
||||||
}
|
}
|
||||||
Action::SwitchToMode(mode) => {
|
Action::SwitchToMode(mode) => {
|
||||||
self.mode = mode;
|
self.mode = mode;
|
||||||
self.send_plugin_instructions
|
self.os_input
|
||||||
.send(PluginInstruction::Update(
|
.send_to_server(ServerInstruction::PluginUpdate(
|
||||||
None,
|
None,
|
||||||
Event::ModeUpdate(get_mode_info(mode, self.os_input.load_palette())),
|
Event::ModeUpdate(get_mode_info(mode)),
|
||||||
))
|
));
|
||||||
.unwrap();
|
self.os_input
|
||||||
self.send_screen_instructions
|
.send_to_server(ServerInstruction::change_input_mode(mode));
|
||||||
.send(ScreenInstruction::ChangeMode(get_mode_info(
|
self.os_input.send_to_server(ServerInstruction::render());
|
||||||
mode,
|
|
||||||
self.os_input.load_palette(),
|
|
||||||
)))
|
|
||||||
.unwrap();
|
|
||||||
self.send_screen_instructions
|
|
||||||
.send(ScreenInstruction::Render)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
Action::Resize(direction) => {
|
Action::Resize(direction) => {
|
||||||
let screen_instr = match direction {
|
let screen_instr = match direction {
|
||||||
super::actions::Direction::Left => ScreenInstruction::ResizeLeft,
|
super::actions::Direction::Left => ServerInstruction::resize_left(),
|
||||||
super::actions::Direction::Right => ScreenInstruction::ResizeRight,
|
super::actions::Direction::Right => ServerInstruction::resize_right(),
|
||||||
super::actions::Direction::Up => ScreenInstruction::ResizeUp,
|
super::actions::Direction::Up => ServerInstruction::resize_up(),
|
||||||
super::actions::Direction::Down => ScreenInstruction::ResizeDown,
|
super::actions::Direction::Down => ServerInstruction::resize_down(),
|
||||||
};
|
};
|
||||||
self.send_screen_instructions.send(screen_instr).unwrap();
|
self.os_input.send_to_server(screen_instr);
|
||||||
}
|
}
|
||||||
Action::SwitchFocus => {
|
Action::SwitchFocus(_) => {
|
||||||
self.send_screen_instructions
|
self.os_input.send_to_server(ServerInstruction::MoveFocus);
|
||||||
.send(ScreenInstruction::SwitchFocus)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
Action::FocusNextPane => {
|
|
||||||
self.send_screen_instructions
|
|
||||||
.send(ScreenInstruction::FocusNextPane)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
Action::FocusPreviousPane => {
|
|
||||||
self.send_screen_instructions
|
|
||||||
.send(ScreenInstruction::FocusPreviousPane)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
Action::MoveFocus(direction) => {
|
Action::MoveFocus(direction) => {
|
||||||
let screen_instr = match direction {
|
let screen_instr = match direction {
|
||||||
super::actions::Direction::Left => ScreenInstruction::MoveFocusLeft,
|
super::actions::Direction::Left => ServerInstruction::move_focus_left(),
|
||||||
super::actions::Direction::Right => ScreenInstruction::MoveFocusRight,
|
super::actions::Direction::Right => ServerInstruction::move_focus_right(),
|
||||||
super::actions::Direction::Up => ScreenInstruction::MoveFocusUp,
|
super::actions::Direction::Up => ServerInstruction::move_focus_up(),
|
||||||
super::actions::Direction::Down => ScreenInstruction::MoveFocusDown,
|
super::actions::Direction::Down => ServerInstruction::move_focus_down(),
|
||||||
};
|
};
|
||||||
self.send_screen_instructions.send(screen_instr).unwrap();
|
self.os_input.send_to_server(screen_instr);
|
||||||
}
|
}
|
||||||
Action::ScrollUp => {
|
Action::ScrollUp => {
|
||||||
self.send_screen_instructions
|
self.os_input.send_to_server(ServerInstruction::scroll_up());
|
||||||
.send(ScreenInstruction::ScrollUp)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
Action::ScrollDown => {
|
Action::ScrollDown => {
|
||||||
self.send_screen_instructions
|
self.os_input
|
||||||
.send(ScreenInstruction::ScrollDown)
|
.send_to_server(ServerInstruction::scroll_down());
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
Action::PageScrollUp => {
|
Action::PageScrollUp => {
|
||||||
self.send_screen_instructions
|
self.send_screen_instructions
|
||||||
|
|
@ -205,9 +169,8 @@ impl InputHandler {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
Action::ToggleFocusFullscreen => {
|
Action::ToggleFocusFullscreen => {
|
||||||
self.send_screen_instructions
|
self.os_input
|
||||||
.send(ScreenInstruction::ToggleActiveTerminalFullscreen)
|
.send_to_server(ServerInstruction::toggle_active_terminal_fullscreen());
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
Action::NewPane(direction) => {
|
Action::NewPane(direction) => {
|
||||||
let pty_instr = match direction {
|
let pty_instr = match direction {
|
||||||
|
|
@ -233,9 +196,8 @@ impl InputHandler {
|
||||||
}
|
}
|
||||||
Action::CloseFocus => {
|
Action::CloseFocus => {
|
||||||
self.command_is_executing.closing_pane();
|
self.command_is_executing.closing_pane();
|
||||||
self.send_screen_instructions
|
self.os_input
|
||||||
.send(ScreenInstruction::CloseFocusedPane)
|
.send_to_server(ServerInstruction::close_focused_pane());
|
||||||
.unwrap();
|
|
||||||
self.command_is_executing.wait_until_pane_is_closed();
|
self.command_is_executing.wait_until_pane_is_closed();
|
||||||
}
|
}
|
||||||
Action::NewTab => {
|
Action::NewTab => {
|
||||||
|
|
@ -246,16 +208,14 @@ impl InputHandler {
|
||||||
}
|
}
|
||||||
Action::GoToNextTab => {
|
Action::GoToNextTab => {
|
||||||
self.command_is_executing.updating_tabs();
|
self.command_is_executing.updating_tabs();
|
||||||
self.send_screen_instructions
|
self.os_input
|
||||||
.send(ScreenInstruction::SwitchTabNext)
|
.send_to_server(ServerInstruction::switch_tab_next());
|
||||||
.unwrap();
|
|
||||||
self.command_is_executing.wait_until_tabs_are_updated();
|
self.command_is_executing.wait_until_tabs_are_updated();
|
||||||
}
|
}
|
||||||
Action::GoToPreviousTab => {
|
Action::GoToPreviousTab => {
|
||||||
self.command_is_executing.updating_tabs();
|
self.command_is_executing.updating_tabs();
|
||||||
self.send_screen_instructions
|
self.os_input
|
||||||
.send(ScreenInstruction::SwitchTabPrev)
|
.send_to_server(ServerInstruction::switch_tab_prev());
|
||||||
.unwrap();
|
|
||||||
self.command_is_executing.wait_until_tabs_are_updated();
|
self.command_is_executing.wait_until_tabs_are_updated();
|
||||||
}
|
}
|
||||||
Action::ToggleActiveSyncPanes => {
|
Action::ToggleActiveSyncPanes => {
|
||||||
|
|
@ -265,22 +225,19 @@ impl InputHandler {
|
||||||
}
|
}
|
||||||
Action::CloseTab => {
|
Action::CloseTab => {
|
||||||
self.command_is_executing.updating_tabs();
|
self.command_is_executing.updating_tabs();
|
||||||
self.send_screen_instructions
|
self.os_input
|
||||||
.send(ScreenInstruction::CloseTab)
|
.send_to_server(ServerInstruction::screen_close_tab());
|
||||||
.unwrap();
|
|
||||||
self.command_is_executing.wait_until_tabs_are_updated();
|
self.command_is_executing.wait_until_tabs_are_updated();
|
||||||
}
|
}
|
||||||
Action::GoToTab(i) => {
|
Action::GoToTab(i) => {
|
||||||
self.command_is_executing.updating_tabs();
|
self.command_is_executing.updating_tabs();
|
||||||
self.send_screen_instructions
|
self.os_input
|
||||||
.send(ScreenInstruction::GoToTab(i))
|
.send_to_server(ServerInstruction::go_to_tab(i));
|
||||||
.unwrap();
|
|
||||||
self.command_is_executing.wait_until_tabs_are_updated();
|
self.command_is_executing.wait_until_tabs_are_updated();
|
||||||
}
|
}
|
||||||
Action::TabNameInput(c) => {
|
Action::TabNameInput(c) => {
|
||||||
self.send_screen_instructions
|
self.os_input
|
||||||
.send(ScreenInstruction::UpdateTabName(c))
|
.send_to_server(ServerInstruction::update_tab_name(c));
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
Action::NoOp => {}
|
Action::NoOp => {}
|
||||||
}
|
}
|
||||||
|
|
@ -291,8 +248,8 @@ impl InputHandler {
|
||||||
/// Routine to be called when the input handler exits (at the moment this is the
|
/// Routine to be called when the input handler exits (at the moment this is the
|
||||||
/// same as quitting Zellij).
|
/// same as quitting Zellij).
|
||||||
fn exit(&mut self) {
|
fn exit(&mut self) {
|
||||||
self.send_app_instructions
|
self.send_client_instructions
|
||||||
.send(AppInstruction::Exit)
|
.send(ClientInstruction::Exit)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -343,19 +300,10 @@ pub fn get_mode_info(mode: InputMode, palette: Palette) -> ModeInfo {
|
||||||
pub fn input_loop(
|
pub fn input_loop(
|
||||||
os_input: Box<dyn ClientOsApi>,
|
os_input: Box<dyn ClientOsApi>,
|
||||||
command_is_executing: CommandIsExecuting,
|
command_is_executing: CommandIsExecuting,
|
||||||
send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
send_client_instructions: SenderWithContext<ClientInstruction>,
|
||||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
|
||||||
send_app_instructions: SenderWithContext<AppInstruction>,
|
|
||||||
) {
|
) {
|
||||||
let _handler = InputHandler::new(
|
let _handler =
|
||||||
os_input,
|
InputHandler::new(os_input, command_is_executing, send_client_instructions).handle_input();
|
||||||
command_is_executing,
|
|
||||||
config,
|
|
||||||
send_screen_instructions,
|
|
||||||
send_plugin_instructions,
|
|
||||||
send_app_instructions,
|
|
||||||
)
|
|
||||||
.handle_input();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_keys(input_bytes: &[u8]) -> Vec<Key> {
|
pub fn parse_keys(input_bytes: &[u8]) -> Vec<Key> {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use nix::sys::termios;
|
||||||
use nix::sys::wait::waitpid;
|
use nix::sys::wait::waitpid;
|
||||||
use nix::unistd;
|
use nix::unistd;
|
||||||
use nix::unistd::{ForkResult, Pid};
|
use nix::unistd::{ForkResult, Pid};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Serialize;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
|
@ -22,7 +22,7 @@ use crate::panes::PositionAndSize;
|
||||||
use crate::server::ServerInstruction;
|
use crate::server::ServerInstruction;
|
||||||
use crate::utils::consts::ZELLIJ_IPC_PIPE;
|
use crate::utils::consts::ZELLIJ_IPC_PIPE;
|
||||||
|
|
||||||
const IPC_BUFFER_SIZE: u32 = 8192;
|
const IPC_BUFFER_SIZE: u32 = 8388608;
|
||||||
|
|
||||||
fn into_raw_mode(pid: RawFd) {
|
fn into_raw_mode(pid: RawFd) {
|
||||||
let mut tio = termios::tcgetattr(pid).expect("could not get terminal attribute");
|
let mut tio = termios::tcgetattr(pid).expect("could not get terminal attribute");
|
||||||
|
|
@ -300,15 +300,6 @@ pub fn get_server_os_input() -> ServerOsInputOutput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// OS Instructions sent to the Server by clients
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub enum ServerOsApiInstruction {
|
|
||||||
SetTerminalSizeUsingFd(RawFd, u16, u16),
|
|
||||||
WriteToTtyStdin(RawFd, Vec<u8>),
|
|
||||||
TcDrain(RawFd),
|
|
||||||
Exit,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ClientOsInputOutput {
|
pub struct ClientOsInputOutput {
|
||||||
orig_termios: Arc<Mutex<termios::Termios>>,
|
orig_termios: Arc<Mutex<termios::Termios>>,
|
||||||
|
|
@ -342,7 +333,7 @@ pub trait ClientOsApi: Send + Sync {
|
||||||
// This should be called from the client-side router thread only.
|
// This should be called from the client-side router thread only.
|
||||||
fn client_recv(&self) -> (ClientInstruction, ErrorContext);
|
fn client_recv(&self) -> (ClientInstruction, ErrorContext);
|
||||||
/// Setup the client IpcChannel and notify server of new client
|
/// Setup the client IpcChannel and notify server of new client
|
||||||
fn connect_to_server(&mut self);
|
fn connect_to_server(&mut self, full_screen_ws: PositionAndSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientOsApi for ClientOsInputOutput {
|
impl ClientOsApi for ClientOsInputOutput {
|
||||||
|
|
@ -378,13 +369,16 @@ impl ClientOsApi for ClientOsInputOutput {
|
||||||
fn update_senders(&mut self, new_ctx: ErrorContext) {
|
fn update_senders(&mut self, new_ctx: ErrorContext) {
|
||||||
self.server_sender.update(new_ctx);
|
self.server_sender.update(new_ctx);
|
||||||
}
|
}
|
||||||
fn connect_to_server(&mut self) {
|
fn connect_to_server(&mut self, full_screen_ws: PositionAndSize) {
|
||||||
let (client_buffer_path, client_buffer) =
|
let (client_buffer_path, client_buffer) =
|
||||||
SharedRingBuffer::create_temp(IPC_BUFFER_SIZE).unwrap();
|
SharedRingBuffer::create_temp(IPC_BUFFER_SIZE).unwrap();
|
||||||
self.client_receiver = Some(Arc::new(Mutex::new(IpcReceiver::new(
|
self.client_receiver = Some(Arc::new(Mutex::new(IpcReceiver::new(
|
||||||
client_buffer.clone(),
|
client_buffer.clone(),
|
||||||
))));
|
))));
|
||||||
self.send_to_server(ServerInstruction::NewClient(client_buffer_path));
|
self.send_to_server(ServerInstruction::NewClient(
|
||||||
|
client_buffer_path,
|
||||||
|
full_screen_ws,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
fn client_recv(&self) -> (ClientInstruction, ErrorContext) {
|
fn client_recv(&self) -> (ClientInstruction, ErrorContext) {
|
||||||
self.client_receiver
|
self.client_receiver
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ use crate::{
|
||||||
errors::{ContextType, ErrorContext},
|
errors::{ContextType, ErrorContext},
|
||||||
panes::PaneId,
|
panes::PaneId,
|
||||||
screen::ScreenInstruction,
|
screen::ScreenInstruction,
|
||||||
server::ServerInstruction,
|
wasm_vm::PluginInstruction,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ReadFromPid {
|
pub struct ReadFromPid {
|
||||||
|
|
@ -82,35 +82,35 @@ pub enum VteEvent {
|
||||||
|
|
||||||
struct VteEventSender {
|
struct VteEventSender {
|
||||||
id: RawFd,
|
id: RawFd,
|
||||||
send_server_instructions: SenderWithContext<ServerInstruction>,
|
send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VteEventSender {
|
impl VteEventSender {
|
||||||
pub fn new(id: RawFd, send_server_instructions: SenderWithContext<ServerInstruction>) -> Self {
|
pub fn new(id: RawFd, send_screen_instructions: SenderWithContext<ScreenInstruction>) -> Self {
|
||||||
VteEventSender {
|
VteEventSender {
|
||||||
id,
|
id,
|
||||||
send_server_instructions,
|
send_screen_instructions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl vte::Perform for VteEventSender {
|
impl vte::Perform for VteEventSender {
|
||||||
fn print(&mut self, c: char) {
|
fn print(&mut self, c: char) {
|
||||||
self.send_server_instructions
|
self.send_screen_instructions
|
||||||
.send(ServerInstruction::pty(self.id, VteEvent::Print(c)))
|
.send(ScreenInstruction::Pty(self.id, VteEvent::Print(c)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
fn execute(&mut self, byte: u8) {
|
fn execute(&mut self, byte: u8) {
|
||||||
self.send_server_instructions
|
self.send_screen_instructions
|
||||||
.send(ServerInstruction::pty(self.id, VteEvent::Execute(byte)))
|
.send(ScreenInstruction::Pty(self.id, VteEvent::Execute(byte)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) {
|
fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) {
|
||||||
let params = params.iter().copied().collect();
|
let params = params.iter().copied().collect();
|
||||||
let intermediates = intermediates.iter().copied().collect();
|
let intermediates = intermediates.iter().copied().collect();
|
||||||
self.send_server_instructions
|
self.send_screen_instructions
|
||||||
.send(ServerInstruction::pty(
|
.send(ScreenInstruction::Pty(
|
||||||
self.id,
|
self.id,
|
||||||
VteEvent::Hook(params, intermediates, ignore, c),
|
VteEvent::Hook(params, intermediates, ignore, c),
|
||||||
))
|
))
|
||||||
|
|
@ -118,21 +118,21 @@ impl vte::Perform for VteEventSender {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn put(&mut self, byte: u8) {
|
fn put(&mut self, byte: u8) {
|
||||||
self.send_server_instructions
|
self.send_screen_instructions
|
||||||
.send(ServerInstruction::pty(self.id, VteEvent::Put(byte)))
|
.send(ScreenInstruction::Pty(self.id, VteEvent::Put(byte)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unhook(&mut self) {
|
fn unhook(&mut self) {
|
||||||
self.send_server_instructions
|
self.send_screen_instructions
|
||||||
.send(ServerInstruction::pty(self.id, VteEvent::Unhook))
|
.send(ScreenInstruction::Pty(self.id, VteEvent::Unhook))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
|
fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
|
||||||
let params = params.iter().map(|p| p.to_vec()).collect();
|
let params = params.iter().map(|p| p.to_vec()).collect();
|
||||||
self.send_server_instructions
|
self.send_screen_instructions
|
||||||
.send(ServerInstruction::pty(
|
.send(ScreenInstruction::Pty(
|
||||||
self.id,
|
self.id,
|
||||||
VteEvent::OscDispatch(params, bell_terminated),
|
VteEvent::OscDispatch(params, bell_terminated),
|
||||||
))
|
))
|
||||||
|
|
@ -142,8 +142,8 @@ impl vte::Perform for VteEventSender {
|
||||||
fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) {
|
fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) {
|
||||||
let params = params.iter().copied().collect();
|
let params = params.iter().copied().collect();
|
||||||
let intermediates = intermediates.iter().copied().collect();
|
let intermediates = intermediates.iter().copied().collect();
|
||||||
self.send_server_instructions
|
self.send_screen_instructions
|
||||||
.send(ServerInstruction::pty(
|
.send(ScreenInstruction::Pty(
|
||||||
self.id,
|
self.id,
|
||||||
VteEvent::CsiDispatch(params, intermediates, ignore, c),
|
VteEvent::CsiDispatch(params, intermediates, ignore, c),
|
||||||
))
|
))
|
||||||
|
|
@ -152,8 +152,8 @@ impl vte::Perform for VteEventSender {
|
||||||
|
|
||||||
fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
|
fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
|
||||||
let intermediates = intermediates.iter().copied().collect();
|
let intermediates = intermediates.iter().copied().collect();
|
||||||
self.send_server_instructions
|
self.send_screen_instructions
|
||||||
.send(ServerInstruction::pty(
|
.send(ScreenInstruction::Pty(
|
||||||
self.id,
|
self.id,
|
||||||
VteEvent::EscDispatch(intermediates, ignore, byte),
|
VteEvent::EscDispatch(intermediates, ignore, byte),
|
||||||
))
|
))
|
||||||
|
|
@ -176,7 +176,8 @@ pub enum PtyInstruction {
|
||||||
pub struct PtyBus {
|
pub struct PtyBus {
|
||||||
pub receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>,
|
pub receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>,
|
||||||
pub id_to_child_pid: HashMap<RawFd, RawFd>,
|
pub id_to_child_pid: HashMap<RawFd, RawFd>,
|
||||||
pub send_server_instructions: SenderWithContext<ServerInstruction>,
|
pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||||
|
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||||
os_input: Box<dyn ServerOsApi>,
|
os_input: Box<dyn ServerOsApi>,
|
||||||
debug_to_file: bool,
|
debug_to_file: bool,
|
||||||
task_handles: HashMap<RawFd, JoinHandle<()>>,
|
task_handles: HashMap<RawFd, JoinHandle<()>>,
|
||||||
|
|
@ -185,16 +186,16 @@ pub struct PtyBus {
|
||||||
fn stream_terminal_bytes(
|
fn stream_terminal_bytes(
|
||||||
pid: RawFd,
|
pid: RawFd,
|
||||||
os_input: Box<dyn ServerOsApi>,
|
os_input: Box<dyn ServerOsApi>,
|
||||||
mut send_server_instructions: SenderWithContext<ServerInstruction>,
|
mut send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||||
debug: bool,
|
debug: bool,
|
||||||
) -> JoinHandle<()> {
|
) -> JoinHandle<()> {
|
||||||
let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
|
let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
|
||||||
task::spawn({
|
task::spawn({
|
||||||
async move {
|
async move {
|
||||||
err_ctx.add_call(ContextType::AsyncTask);
|
err_ctx.add_call(ContextType::AsyncTask);
|
||||||
send_server_instructions.update(err_ctx);
|
send_screen_instructions.update(err_ctx);
|
||||||
let mut vte_parser = vte::Parser::new();
|
let mut vte_parser = vte::Parser::new();
|
||||||
let mut vte_event_sender = VteEventSender::new(pid, send_server_instructions.clone());
|
let mut vte_event_sender = VteEventSender::new(pid, send_screen_instructions.clone());
|
||||||
let mut terminal_bytes = ReadFromPid::new(&pid, os_input.clone());
|
let mut terminal_bytes = ReadFromPid::new(&pid, os_input.clone());
|
||||||
|
|
||||||
let mut last_byte_receive_time: Option<Instant> = None;
|
let mut last_byte_receive_time: Option<Instant> = None;
|
||||||
|
|
@ -220,8 +221,8 @@ fn stream_terminal_bytes(
|
||||||
Some(receive_time) => {
|
Some(receive_time) => {
|
||||||
if receive_time.elapsed() > max_render_pause {
|
if receive_time.elapsed() > max_render_pause {
|
||||||
pending_render = false;
|
pending_render = false;
|
||||||
send_server_instructions
|
send_screen_instructions
|
||||||
.send(ServerInstruction::render())
|
.send(ScreenInstruction::Render)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
last_byte_receive_time = Some(Instant::now());
|
last_byte_receive_time = Some(Instant::now());
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -236,23 +237,23 @@ fn stream_terminal_bytes(
|
||||||
} else {
|
} else {
|
||||||
if pending_render {
|
if pending_render {
|
||||||
pending_render = false;
|
pending_render = false;
|
||||||
send_server_instructions
|
send_screen_instructions
|
||||||
.send(ServerInstruction::render())
|
.send(ScreenInstruction::Render)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
last_byte_receive_time = None;
|
last_byte_receive_time = None;
|
||||||
task::sleep(::std::time::Duration::from_millis(10)).await;
|
task::sleep(::std::time::Duration::from_millis(10)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
send_server_instructions
|
send_screen_instructions
|
||||||
.send(ServerInstruction::render())
|
.send(ScreenInstruction::Render)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
// this is a little hacky, and is because the tests end the file as soon as
|
// this is a little hacky, and is because the tests end the file as soon as
|
||||||
// we read everything, rather than hanging until there is new data
|
// we read everything, rather than hanging until there is new data
|
||||||
// a better solution would be to fix the test fakes, but this will do for now
|
// a better solution would be to fix the test fakes, but this will do for now
|
||||||
send_server_instructions
|
send_screen_instructions
|
||||||
.send(ServerInstruction::screen_close_pane(PaneId::Terminal(pid)))
|
.send(ScreenInstruction::ClosePane(PaneId::Terminal(pid)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -262,14 +263,16 @@ impl PtyBus {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>,
|
receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>,
|
||||||
os_input: Box<dyn ServerOsApi>,
|
os_input: Box<dyn ServerOsApi>,
|
||||||
send_server_instructions: SenderWithContext<ServerInstruction>,
|
send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||||
|
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||||
debug_to_file: bool,
|
debug_to_file: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
PtyBus {
|
PtyBus {
|
||||||
receive_pty_instructions,
|
receive_pty_instructions,
|
||||||
os_input,
|
os_input,
|
||||||
id_to_child_pid: HashMap::new(),
|
id_to_child_pid: HashMap::new(),
|
||||||
send_server_instructions,
|
send_screen_instructions,
|
||||||
|
send_plugin_instructions,
|
||||||
debug_to_file,
|
debug_to_file,
|
||||||
task_handles: HashMap::new(),
|
task_handles: HashMap::new(),
|
||||||
}
|
}
|
||||||
|
|
@ -280,7 +283,7 @@ impl PtyBus {
|
||||||
let task_handle = stream_terminal_bytes(
|
let task_handle = stream_terminal_bytes(
|
||||||
pid_primary,
|
pid_primary,
|
||||||
self.os_input.clone(),
|
self.os_input.clone(),
|
||||||
self.send_server_instructions.clone(),
|
self.send_screen_instructions.clone(),
|
||||||
self.debug_to_file,
|
self.debug_to_file,
|
||||||
);
|
);
|
||||||
self.task_handles.insert(pid_primary, task_handle);
|
self.task_handles.insert(pid_primary, task_handle);
|
||||||
|
|
@ -296,17 +299,17 @@ impl PtyBus {
|
||||||
self.id_to_child_pid.insert(pid_primary, pid_secondary);
|
self.id_to_child_pid.insert(pid_primary, pid_secondary);
|
||||||
new_pane_pids.push(pid_primary);
|
new_pane_pids.push(pid_primary);
|
||||||
}
|
}
|
||||||
self.send_server_instructions
|
self.send_screen_instructions
|
||||||
.send(ServerInstruction::apply_layout((
|
.send(ScreenInstruction::ApplyLayout(
|
||||||
layout_path,
|
layout_path,
|
||||||
new_pane_pids.clone(),
|
new_pane_pids.clone(),
|
||||||
)))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
for id in new_pane_pids {
|
for id in new_pane_pids {
|
||||||
let task_handle = stream_terminal_bytes(
|
let task_handle = stream_terminal_bytes(
|
||||||
id,
|
id,
|
||||||
self.os_input.clone(),
|
self.os_input.clone(),
|
||||||
self.send_server_instructions.clone(),
|
self.send_screen_instructions.clone(),
|
||||||
self.debug_to_file,
|
self.debug_to_file,
|
||||||
);
|
);
|
||||||
self.task_handles.insert(id, task_handle);
|
self.task_handles.insert(id, task_handle);
|
||||||
|
|
@ -323,8 +326,8 @@ impl PtyBus {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
PaneId::Plugin(pid) => self
|
PaneId::Plugin(pid) => self
|
||||||
.send_server_instructions
|
.send_plugin_instructions
|
||||||
.send(ServerInstruction::ClosePluginPane(pid))
|
.send(PluginInstruction::Unload(pid))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,8 @@ use std::path::PathBuf;
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
|
|
||||||
use crate::client::AppInstruction;
|
|
||||||
use crate::common::SenderWithContext;
|
use crate::common::SenderWithContext;
|
||||||
use crate::os_input_output::ClientOsApi;
|
use crate::os_input_output::ServerOsApi;
|
||||||
use crate::panes::PositionAndSize;
|
use crate::panes::PositionAndSize;
|
||||||
use crate::pty_bus::{PtyInstruction, VteEvent};
|
use crate::pty_bus::{PtyInstruction, VteEvent};
|
||||||
use crate::server::ServerInstruction;
|
use crate::server::ServerInstruction;
|
||||||
|
|
@ -51,7 +50,7 @@ pub enum ScreenInstruction {
|
||||||
SetMaxHeight(PaneId, usize),
|
SetMaxHeight(PaneId, usize),
|
||||||
SetInvisibleBorders(PaneId, bool),
|
SetInvisibleBorders(PaneId, bool),
|
||||||
ClosePane(PaneId),
|
ClosePane(PaneId),
|
||||||
ApplyLayout((PathBuf, Vec<RawFd>)),
|
ApplyLayout(PathBuf, Vec<RawFd>),
|
||||||
NewTab(RawFd),
|
NewTab(RawFd),
|
||||||
SwitchTabNext,
|
SwitchTabNext,
|
||||||
SwitchTabPrev,
|
SwitchTabPrev,
|
||||||
|
|
@ -74,14 +73,16 @@ pub struct Screen {
|
||||||
tabs: BTreeMap<usize, Tab>,
|
tabs: BTreeMap<usize, Tab>,
|
||||||
/// A [`PluginInstruction`] and [`ErrorContext`] sender.
|
/// A [`PluginInstruction`] and [`ErrorContext`] sender.
|
||||||
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||||
/// An [`AppInstruction`] and [`ErrorContext`] sender.
|
/// An [`PtyInstruction`] and [`ErrorContext`] sender.
|
||||||
pub send_app_instructions: SenderWithContext<AppInstruction>,
|
pub send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||||
|
/// An [`ServerInstruction`] and [`ErrorContext`] sender.
|
||||||
|
pub send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||||
/// The full size of this [`Screen`].
|
/// The full size of this [`Screen`].
|
||||||
full_screen_ws: PositionAndSize,
|
full_screen_ws: PositionAndSize,
|
||||||
/// The index of this [`Screen`]'s active [`Tab`].
|
/// The index of this [`Screen`]'s active [`Tab`].
|
||||||
active_tab_index: Option<usize>,
|
active_tab_index: Option<usize>,
|
||||||
/// The [`ClientOsApi`] this [`Screen`] uses.
|
/// The [`ClientOsApi`] this [`Screen`] uses.
|
||||||
pub os_api: Box<dyn ClientOsApi>,
|
os_api: Box<dyn ServerOsApi>,
|
||||||
input_mode: InputMode,
|
input_mode: InputMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,9 +93,10 @@ impl Screen {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
receive_screen_instructions: Receiver<(ScreenInstruction, ErrorContext)>,
|
receive_screen_instructions: Receiver<(ScreenInstruction, ErrorContext)>,
|
||||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||||
send_app_instructions: SenderWithContext<AppInstruction>,
|
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||||
|
send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||||
full_screen_ws: &PositionAndSize,
|
full_screen_ws: &PositionAndSize,
|
||||||
os_api: Box<dyn ClientOsApi>,
|
os_api: Box<dyn ServerOsApi>,
|
||||||
max_panes: Option<usize>,
|
max_panes: Option<usize>,
|
||||||
mode_info: ModeInfo,
|
mode_info: ModeInfo,
|
||||||
input_mode: InputMode,
|
input_mode: InputMode,
|
||||||
|
|
@ -104,7 +106,8 @@ impl Screen {
|
||||||
receiver: receive_screen_instructions,
|
receiver: receive_screen_instructions,
|
||||||
max_panes,
|
max_panes,
|
||||||
send_plugin_instructions,
|
send_plugin_instructions,
|
||||||
send_app_instructions,
|
send_pty_instructions,
|
||||||
|
send_server_instructions,
|
||||||
full_screen_ws: *full_screen_ws,
|
full_screen_ws: *full_screen_ws,
|
||||||
active_tab_index: None,
|
active_tab_index: None,
|
||||||
tabs: BTreeMap::new(),
|
tabs: BTreeMap::new(),
|
||||||
|
|
@ -127,7 +130,8 @@ impl Screen {
|
||||||
&self.full_screen_ws,
|
&self.full_screen_ws,
|
||||||
self.os_api.clone(),
|
self.os_api.clone(),
|
||||||
self.send_plugin_instructions.clone(),
|
self.send_plugin_instructions.clone(),
|
||||||
self.send_app_instructions.clone(),
|
self.send_pty_instructions.clone(),
|
||||||
|
self.send_server_instructions.clone(),
|
||||||
self.max_panes,
|
self.max_panes,
|
||||||
Some(PaneId::Terminal(pane_id)),
|
Some(PaneId::Terminal(pane_id)),
|
||||||
self.mode_info.clone(),
|
self.mode_info.clone(),
|
||||||
|
|
@ -211,12 +215,13 @@ impl Screen {
|
||||||
// below we don't check the result of sending the CloseTab instruction to the pty thread
|
// below we don't check the result of sending the CloseTab instruction to the pty thread
|
||||||
// because this might be happening when the app is closing, at which point the pty thread
|
// because this might be happening when the app is closing, at which point the pty thread
|
||||||
// has already closed and this would result in an error
|
// has already closed and this would result in an error
|
||||||
self.os_api
|
self.send_pty_instructions
|
||||||
.send_to_server(ServerInstruction::pty_close_tab(pane_ids));
|
.send(PtyInstruction::CloseTab(pane_ids))
|
||||||
|
.unwrap();
|
||||||
if self.tabs.is_empty() {
|
if self.tabs.is_empty() {
|
||||||
self.active_tab_index = None;
|
self.active_tab_index = None;
|
||||||
self.send_app_instructions
|
self.send_server_instructions
|
||||||
.send(AppInstruction::Exit)
|
.send(ServerInstruction::ClientShouldExit)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else {
|
} else {
|
||||||
for t in self.tabs.values_mut() {
|
for t in self.tabs.values_mut() {
|
||||||
|
|
@ -282,7 +287,8 @@ impl Screen {
|
||||||
&self.full_screen_ws,
|
&self.full_screen_ws,
|
||||||
self.os_api.clone(),
|
self.os_api.clone(),
|
||||||
self.send_plugin_instructions.clone(),
|
self.send_plugin_instructions.clone(),
|
||||||
self.send_app_instructions.clone(),
|
self.send_pty_instructions.clone(),
|
||||||
|
self.send_server_instructions.clone(),
|
||||||
self.max_panes,
|
self.max_panes,
|
||||||
None,
|
None,
|
||||||
self.mode_info.clone(),
|
self.mode_info.clone(),
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ use wasmer_wasi::WasiEnv;
|
||||||
use zellij_tile::data::{Event, EventType, PluginIds};
|
use zellij_tile::data::{Event, EventType, PluginIds};
|
||||||
|
|
||||||
use super::{pty_bus::PtyInstruction, screen::ScreenInstruction, PaneId, SenderWithContext};
|
use super::{pty_bus::PtyInstruction, screen::ScreenInstruction, PaneId, SenderWithContext};
|
||||||
use crate::client::AppInstruction;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum PluginInstruction {
|
pub enum PluginInstruction {
|
||||||
|
|
@ -28,7 +27,7 @@ pub struct PluginEnv {
|
||||||
pub plugin_id: u32,
|
pub plugin_id: u32,
|
||||||
// FIXME: This should be a big bundle of all of the channels
|
// FIXME: This should be a big bundle of all of the channels
|
||||||
pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||||
pub send_app_instructions: SenderWithContext<AppInstruction>, // FIXME: This should be a big bundle of all of the channels
|
pub send_pty_instructions: SenderWithContext<PtyInstruction>, // FIXME: This should be a big bundle of all of the channels
|
||||||
pub wasi_env: WasiEnv,
|
pub wasi_env: WasiEnv,
|
||||||
pub subscriptions: Arc<Mutex<HashSet<EventType>>>,
|
pub subscriptions: Arc<Mutex<HashSet<EventType>>>,
|
||||||
}
|
}
|
||||||
|
|
@ -74,10 +73,8 @@ fn host_unsubscribe(plugin_env: &PluginEnv) {
|
||||||
fn host_open_file(plugin_env: &PluginEnv) {
|
fn host_open_file(plugin_env: &PluginEnv) {
|
||||||
let path = PathBuf::from(wasi_stdout(&plugin_env.wasi_env).lines().next().unwrap());
|
let path = PathBuf::from(wasi_stdout(&plugin_env.wasi_env).lines().next().unwrap());
|
||||||
plugin_env
|
plugin_env
|
||||||
.send_app_instructions
|
.send_pty_instructions
|
||||||
.send(AppInstruction::ToPty(PtyInstruction::SpawnTerminal(Some(
|
.send(PtyInstruction::SpawnTerminal(Some(path)))
|
||||||
path,
|
|
||||||
))))
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ pub fn start(
|
||||||
opts: CliArgs,
|
opts: CliArgs,
|
||||||
server_os_input: Box<dyn ServerOsApi>,
|
server_os_input: Box<dyn ServerOsApi>,
|
||||||
) {
|
) {
|
||||||
let ipc_thread = start_server(server_os_input, opts.clone());
|
let ipc_thread = start_server(server_os_input, opts);
|
||||||
start_client(client_os_input, opts);
|
start_client(client_os_input);
|
||||||
drop(ipc_thread.join());
|
drop(ipc_thread.join());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,33 @@
|
||||||
use crate::client::ClientInstruction;
|
use directories_next::ProjectDirs;
|
||||||
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;
|
|
||||||
use crate::pty_bus::{PtyBus, PtyInstruction};
|
|
||||||
use crate::screen::ScreenInstruction;
|
|
||||||
use crate::{cli::CliArgs, common::pty_bus::VteEvent};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::channel;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use zellij_tile::prelude::InputMode;
|
use std::{collections::HashMap, fs};
|
||||||
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
str::FromStr,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value};
|
||||||
|
use wasmer_wasi::{Pipe, WasiState};
|
||||||
|
use zellij_tile::data::{Event, EventType, InputMode};
|
||||||
|
|
||||||
|
use crate::cli::CliArgs;
|
||||||
|
use crate::client::ClientInstruction;
|
||||||
|
use crate::common::pty_bus::VteEvent;
|
||||||
|
use crate::common::{
|
||||||
|
errors::{ContextType, ErrorContext, PluginContext, PtyContext, ScreenContext, ServerContext},
|
||||||
|
os_input_output::ServerOsApi,
|
||||||
|
pty_bus::{PtyBus, PtyInstruction},
|
||||||
|
screen::{Screen, ScreenInstruction},
|
||||||
|
wasm_vm::{wasi_stdout, wasi_write_string, zellij_imports, PluginEnv, PluginInstruction},
|
||||||
|
ChannelWithContext, SenderType, SenderWithContext, OPENCALLS,
|
||||||
|
};
|
||||||
|
use crate::layout::Layout;
|
||||||
|
use crate::panes::PaneId;
|
||||||
|
use crate::panes::PositionAndSize;
|
||||||
|
|
||||||
/// Instructions related to server-side application including the
|
/// Instructions related to server-side application including the
|
||||||
/// ones sent by client to server
|
/// ones sent by client to server
|
||||||
|
|
@ -21,13 +37,17 @@ pub enum ServerInstruction {
|
||||||
SplitHorizontally,
|
SplitHorizontally,
|
||||||
SplitVertically,
|
SplitVertically,
|
||||||
MoveFocus,
|
MoveFocus,
|
||||||
NewClient(String),
|
NewClient(String, PositionAndSize),
|
||||||
ToPty(PtyInstruction),
|
ToPty(PtyInstruction),
|
||||||
ToScreen(ScreenInstruction),
|
ToScreen(ScreenInstruction),
|
||||||
OsApi(ServerOsApiInstruction),
|
Render(String),
|
||||||
|
PluginUpdate(Option<u32>, Event),
|
||||||
DoneClosingPane,
|
DoneClosingPane,
|
||||||
ClosePluginPane(u32),
|
DoneOpeningNewPane,
|
||||||
|
DoneUpdatingTabs,
|
||||||
ClientExit,
|
ClientExit,
|
||||||
|
ClientShouldExit,
|
||||||
|
// notify router thread to exit
|
||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
impl ServerInstruction {
|
impl ServerInstruction {
|
||||||
|
|
@ -127,8 +147,8 @@ impl ServerInstruction {
|
||||||
pub fn screen_close_pane(pane_id: PaneId) -> Self {
|
pub fn screen_close_pane(pane_id: PaneId) -> Self {
|
||||||
Self::ToScreen(ScreenInstruction::ClosePane(pane_id))
|
Self::ToScreen(ScreenInstruction::ClosePane(pane_id))
|
||||||
}
|
}
|
||||||
pub fn apply_layout(layout: (PathBuf, Vec<RawFd>)) -> Self {
|
pub fn apply_layout(layout: PathBuf, pids: Vec<RawFd>) -> Self {
|
||||||
Self::ToScreen(ScreenInstruction::ApplyLayout(layout))
|
Self::ToScreen(ScreenInstruction::ApplyLayout(layout, pids))
|
||||||
}
|
}
|
||||||
pub fn screen_new_tab(fd: RawFd) -> Self {
|
pub fn screen_new_tab(fd: RawFd) -> Self {
|
||||||
Self::ToScreen(ScreenInstruction::NewTab(fd))
|
Self::ToScreen(ScreenInstruction::NewTab(fd))
|
||||||
|
|
@ -154,169 +174,54 @@ impl ServerInstruction {
|
||||||
pub fn pty(fd: RawFd, event: VteEvent) -> Self {
|
pub fn pty(fd: RawFd, event: VteEvent) -> Self {
|
||||||
Self::ToScreen(ScreenInstruction::Pty(fd, event))
|
Self::ToScreen(ScreenInstruction::Pty(fd, event))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// OsApi
|
struct ClientMetaData {
|
||||||
pub fn set_terminal_size_using_fd(fd: RawFd, cols: u16, rows: u16) -> Self {
|
pub send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||||
Self::OsApi(ServerOsApiInstruction::SetTerminalSizeUsingFd(
|
pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||||
fd, cols, rows,
|
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||||
))
|
screen_thread: Option<thread::JoinHandle<()>>,
|
||||||
|
pty_thread: Option<thread::JoinHandle<()>>,
|
||||||
|
wasm_thread: Option<thread::JoinHandle<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientMetaData {
|
||||||
|
fn update(&mut self, err_ctx: ErrorContext) {
|
||||||
|
self.send_plugin_instructions.update(err_ctx);
|
||||||
|
self.send_screen_instructions.update(err_ctx);
|
||||||
|
self.send_pty_instructions.update(err_ctx);
|
||||||
}
|
}
|
||||||
pub fn write_to_tty_stdin(fd: RawFd, buf: Vec<u8>) -> Self {
|
}
|
||||||
Self::OsApi(ServerOsApiInstruction::WriteToTtyStdin(fd, buf))
|
|
||||||
}
|
impl Drop for ClientMetaData {
|
||||||
pub fn tc_drain(fd: RawFd) -> Self {
|
fn drop(&mut self) {
|
||||||
Self::OsApi(ServerOsApiInstruction::TcDrain(fd))
|
let _ = self.send_pty_instructions.send(PtyInstruction::Exit);
|
||||||
}
|
let _ = self.send_screen_instructions.send(ScreenInstruction::Exit);
|
||||||
pub fn os_exit() -> Self {
|
let _ = self.send_plugin_instructions.send(PluginInstruction::Exit);
|
||||||
Self::OsApi(ServerOsApiInstruction::Exit)
|
let _ = self.screen_thread.take().unwrap().join();
|
||||||
|
let _ = self.pty_thread.take().unwrap().join();
|
||||||
|
let _ = self.wasm_thread.take().unwrap().join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_server(mut os_input: Box<dyn ServerOsApi>, opts: CliArgs) -> thread::JoinHandle<()> {
|
pub fn start_server(mut os_input: Box<dyn ServerOsApi>, opts: CliArgs) -> thread::JoinHandle<()> {
|
||||||
let (send_pty_instructions, receive_pty_instructions): ChannelWithContext<PtyInstruction> =
|
|
||||||
channel();
|
|
||||||
let mut send_pty_instructions = SenderWithContext::new(
|
|
||||||
ErrorContext::new(),
|
|
||||||
SenderType::Sender(send_pty_instructions),
|
|
||||||
);
|
|
||||||
|
|
||||||
let (send_os_instructions, receive_os_instructions): ChannelWithContext<
|
|
||||||
ServerOsApiInstruction,
|
|
||||||
> = channel();
|
|
||||||
let mut send_os_instructions = SenderWithContext::new(
|
|
||||||
ErrorContext::new(),
|
|
||||||
SenderType::Sender(send_os_instructions),
|
|
||||||
);
|
|
||||||
|
|
||||||
let (send_server_instructions, receive_server_instructions): ChannelWithContext<
|
let (send_server_instructions, receive_server_instructions): ChannelWithContext<
|
||||||
ServerInstruction,
|
ServerInstruction,
|
||||||
> = channel();
|
> = channel();
|
||||||
let mut send_server_instructions = SenderWithContext::new(
|
let send_server_instructions = SenderWithContext::new(
|
||||||
ErrorContext::new(),
|
ErrorContext::new(),
|
||||||
SenderType::Sender(send_server_instructions),
|
SenderType::Sender(send_server_instructions),
|
||||||
);
|
);
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
let mut pty_bus = PtyBus::new(
|
|
||||||
receive_pty_instructions,
|
|
||||||
os_input.clone(),
|
|
||||||
send_server_instructions.clone(),
|
|
||||||
opts.debug,
|
|
||||||
);
|
|
||||||
|
|
||||||
let pty_thread = thread::Builder::new()
|
|
||||||
.name("pty".to_string())
|
|
||||||
.spawn(move || loop {
|
|
||||||
let (event, mut err_ctx) = pty_bus
|
|
||||||
.receive_pty_instructions
|
|
||||||
.recv()
|
|
||||||
.expect("failed to receive event on channel");
|
|
||||||
err_ctx.add_call(ContextType::Pty(PtyContext::from(&event)));
|
|
||||||
match event {
|
|
||||||
PtyInstruction::SpawnTerminal(file_to_open) => {
|
|
||||||
let pid = pty_bus.spawn_terminal(file_to_open);
|
|
||||||
pty_bus
|
|
||||||
.send_server_instructions
|
|
||||||
.send(ServerInstruction::new_pane(PaneId::Terminal(pid)))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
PtyInstruction::SpawnTerminalVertically(file_to_open) => {
|
|
||||||
let pid = pty_bus.spawn_terminal(file_to_open);
|
|
||||||
pty_bus
|
|
||||||
.send_server_instructions
|
|
||||||
.send(ServerInstruction::vertical_split(PaneId::Terminal(pid)))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
PtyInstruction::SpawnTerminalHorizontally(file_to_open) => {
|
|
||||||
let pid = pty_bus.spawn_terminal(file_to_open);
|
|
||||||
pty_bus
|
|
||||||
.send_server_instructions
|
|
||||||
.send(ServerInstruction::horizontal_split(PaneId::Terminal(pid)))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
PtyInstruction::NewTab => {
|
|
||||||
if let Some(layout) = maybe_layout.clone() {
|
|
||||||
pty_bus.spawn_terminals_for_layout(layout);
|
|
||||||
} else {
|
|
||||||
let pid = pty_bus.spawn_terminal(None);
|
|
||||||
pty_bus
|
|
||||||
.send_server_instructions
|
|
||||||
.send(ServerInstruction::screen_new_tab(pid))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PtyInstruction::ClosePane(id) => {
|
|
||||||
pty_bus.close_pane(id);
|
|
||||||
pty_bus
|
|
||||||
.send_server_instructions
|
|
||||||
.send(ServerInstruction::DoneClosingPane)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
PtyInstruction::CloseTab(ids) => {
|
|
||||||
pty_bus.close_tab(ids);
|
|
||||||
pty_bus
|
|
||||||
.send_server_instructions
|
|
||||||
.send(ServerInstruction::DoneClosingPane)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
PtyInstruction::Exit => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let os_thread = thread::Builder::new()
|
|
||||||
.name("os".to_string())
|
|
||||||
.spawn({
|
|
||||||
let mut os_input = os_input.clone();
|
|
||||||
move || loop {
|
|
||||||
let (event, mut err_ctx) = receive_os_instructions
|
|
||||||
.recv()
|
|
||||||
.expect("failed to receive an event on the channel");
|
|
||||||
err_ctx.add_call(ContextType::Os(OsContext::from(&event)));
|
|
||||||
match event {
|
|
||||||
ServerOsApiInstruction::SetTerminalSizeUsingFd(fd, cols, rows) => {
|
|
||||||
os_input.set_terminal_size_using_fd(fd, cols, rows);
|
|
||||||
}
|
|
||||||
ServerOsApiInstruction::WriteToTtyStdin(fd, mut buf) => {
|
|
||||||
let slice = buf.as_mut_slice();
|
|
||||||
os_input.write_to_tty_stdin(fd, slice).unwrap();
|
|
||||||
}
|
|
||||||
ServerOsApiInstruction::TcDrain(fd) => {
|
|
||||||
os_input.tcdrain(fd).unwrap();
|
|
||||||
}
|
|
||||||
ServerOsApiInstruction::Exit => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let router_thread = thread::Builder::new()
|
let router_thread = thread::Builder::new()
|
||||||
.name("server_router".to_string())
|
.name("server_router".to_string())
|
||||||
.spawn({
|
.spawn({
|
||||||
let os_input = os_input.clone();
|
let os_input = os_input.clone();
|
||||||
let mut send_os_instructions = send_os_instructions.clone();
|
let mut send_server_instructions = send_server_instructions.clone();
|
||||||
let mut send_pty_instructions = send_pty_instructions.clone();
|
|
||||||
move || loop {
|
move || loop {
|
||||||
let (instruction, err_ctx) = os_input.server_recv();
|
let (instruction, err_ctx) = os_input.server_recv();
|
||||||
send_server_instructions.update(err_ctx);
|
send_server_instructions.update(err_ctx);
|
||||||
send_pty_instructions.update(err_ctx);
|
|
||||||
send_os_instructions.update(err_ctx);
|
|
||||||
match instruction {
|
match instruction {
|
||||||
ServerInstruction::Exit => break,
|
ServerInstruction::Exit => break,
|
||||||
ServerInstruction::ToPty(instruction) => {
|
|
||||||
send_pty_instructions.send(instruction).unwrap();
|
|
||||||
}
|
|
||||||
ServerInstruction::OsApi(instruction) => {
|
|
||||||
send_os_instructions.send(instruction).unwrap();
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
send_server_instructions.send(instruction).unwrap();
|
send_server_instructions.send(instruction).unwrap();
|
||||||
}
|
}
|
||||||
|
|
@ -328,60 +233,517 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, opts: CliArgs) -> thread
|
||||||
thread::Builder::new()
|
thread::Builder::new()
|
||||||
.name("ipc_server".to_string())
|
.name("ipc_server".to_string())
|
||||||
.spawn({
|
.spawn({
|
||||||
|
let mut clients: HashMap<String, ClientMetaData> = HashMap::new();
|
||||||
|
// We handle only single client for now
|
||||||
|
let mut client: Option<String> = None;
|
||||||
move || loop {
|
move || loop {
|
||||||
let (instruction, mut err_ctx) = receive_server_instructions.recv().unwrap();
|
let (instruction, mut err_ctx) = receive_server_instructions.recv().unwrap();
|
||||||
err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction)));
|
err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction)));
|
||||||
send_pty_instructions.update(err_ctx);
|
|
||||||
send_os_instructions.update(err_ctx);
|
|
||||||
os_input.update_senders(err_ctx);
|
os_input.update_senders(err_ctx);
|
||||||
|
if let Some(ref c) = client {
|
||||||
|
clients.get_mut(c).unwrap().update(err_ctx);
|
||||||
|
}
|
||||||
match instruction {
|
match instruction {
|
||||||
ServerInstruction::OpenFile(file_name) => {
|
ServerInstruction::OpenFile(file_name) => {
|
||||||
let path = PathBuf::from(file_name);
|
let path = PathBuf::from(file_name);
|
||||||
send_pty_instructions
|
clients[client.as_ref().unwrap()]
|
||||||
|
.send_pty_instructions
|
||||||
.send(PtyInstruction::SpawnTerminal(Some(path)))
|
.send(PtyInstruction::SpawnTerminal(Some(path)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
ServerInstruction::SplitHorizontally => {
|
ServerInstruction::SplitHorizontally => {
|
||||||
send_pty_instructions
|
clients[client.as_ref().unwrap()]
|
||||||
|
.send_pty_instructions
|
||||||
.send(PtyInstruction::SpawnTerminalHorizontally(None))
|
.send(PtyInstruction::SpawnTerminalHorizontally(None))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
ServerInstruction::SplitVertically => {
|
ServerInstruction::SplitVertically => {
|
||||||
send_pty_instructions
|
clients[client.as_ref().unwrap()]
|
||||||
|
.send_pty_instructions
|
||||||
.send(PtyInstruction::SpawnTerminalVertically(None))
|
.send(PtyInstruction::SpawnTerminalVertically(None))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
ServerInstruction::MoveFocus => {
|
ServerInstruction::MoveFocus => {
|
||||||
os_input.send_to_client(ClientInstruction::ToScreen(
|
clients[client.as_ref().unwrap()]
|
||||||
ScreenInstruction::MoveFocus,
|
.send_screen_instructions
|
||||||
));
|
.send(ScreenInstruction::MoveFocus)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
ServerInstruction::NewClient(buffer_path) => {
|
ServerInstruction::NewClient(buffer_path, full_screen_ws) => {
|
||||||
send_pty_instructions.send(PtyInstruction::NewTab).unwrap();
|
client = Some(buffer_path.clone());
|
||||||
|
let client_data = init_client(
|
||||||
|
os_input.clone(),
|
||||||
|
opts.clone(),
|
||||||
|
send_server_instructions.clone(),
|
||||||
|
full_screen_ws,
|
||||||
|
);
|
||||||
|
clients.insert(buffer_path.clone(), client_data);
|
||||||
|
clients[client.as_ref().unwrap()]
|
||||||
|
.send_pty_instructions
|
||||||
|
.send(PtyInstruction::NewTab)
|
||||||
|
.unwrap();
|
||||||
os_input.add_client_sender(buffer_path);
|
os_input.add_client_sender(buffer_path);
|
||||||
}
|
}
|
||||||
ServerInstruction::ToScreen(instr) => {
|
ServerInstruction::ToScreen(instruction) => {
|
||||||
os_input.send_to_client(ClientInstruction::ToScreen(instr));
|
clients[client.as_ref().unwrap()]
|
||||||
|
.send_screen_instructions
|
||||||
|
.send(instruction)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
ServerInstruction::ToPty(instruction) => {
|
||||||
|
clients[client.as_ref().unwrap()]
|
||||||
|
.send_pty_instructions
|
||||||
|
.send(instruction)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
ServerInstruction::DoneClosingPane => {
|
ServerInstruction::DoneClosingPane => {
|
||||||
os_input.send_to_client(ClientInstruction::DoneClosingPane);
|
os_input.send_to_client(ClientInstruction::DoneClosingPane);
|
||||||
}
|
}
|
||||||
ServerInstruction::ClosePluginPane(pid) => {
|
ServerInstruction::DoneOpeningNewPane => {
|
||||||
os_input.send_to_client(ClientInstruction::ClosePluginPane(pid));
|
os_input.send_to_client(ClientInstruction::DoneOpeningNewPane);
|
||||||
|
}
|
||||||
|
ServerInstruction::DoneUpdatingTabs => {
|
||||||
|
os_input.send_to_client(ClientInstruction::DoneUpdatingTabs);
|
||||||
|
}
|
||||||
|
ServerInstruction::ClientShouldExit => {
|
||||||
|
os_input.send_to_client(ClientInstruction::Exit);
|
||||||
|
}
|
||||||
|
ServerInstruction::PluginUpdate(pid, event) => {
|
||||||
|
clients[client.as_ref().unwrap()]
|
||||||
|
.send_plugin_instructions
|
||||||
|
.send(PluginInstruction::Update(pid, event))
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
ServerInstruction::ClientExit => {
|
ServerInstruction::ClientExit => {
|
||||||
let _ = send_pty_instructions.send(PtyInstruction::Exit);
|
clients.remove(client.as_ref().unwrap()).unwrap();
|
||||||
let _ = send_os_instructions.send(ServerOsApiInstruction::Exit);
|
|
||||||
os_input.server_exit();
|
os_input.server_exit();
|
||||||
let _ = pty_thread.join();
|
|
||||||
let _ = os_thread.join();
|
|
||||||
let _ = router_thread.join();
|
let _ = router_thread.join();
|
||||||
let _ = os_input.send_to_client(ClientInstruction::Exit);
|
let _ = os_input.send_to_client(ClientInstruction::Exit);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
ServerInstruction::Render(output) => {
|
||||||
|
os_input.send_to_client(ClientInstruction::Render(output))
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn init_client(
|
||||||
|
os_input: Box<dyn ServerOsApi>,
|
||||||
|
opts: CliArgs,
|
||||||
|
send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||||
|
full_screen_ws: PositionAndSize,
|
||||||
|
) -> ClientMetaData {
|
||||||
|
let err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
|
||||||
|
let (send_screen_instructions, receive_screen_instructions): ChannelWithContext<
|
||||||
|
ScreenInstruction,
|
||||||
|
> = channel();
|
||||||
|
let send_screen_instructions =
|
||||||
|
SenderWithContext::new(err_ctx, SenderType::Sender(send_screen_instructions));
|
||||||
|
|
||||||
|
let (send_plugin_instructions, receive_plugin_instructions): ChannelWithContext<
|
||||||
|
PluginInstruction,
|
||||||
|
> = channel();
|
||||||
|
let send_plugin_instructions =
|
||||||
|
SenderWithContext::new(err_ctx, SenderType::Sender(send_plugin_instructions));
|
||||||
|
let (send_pty_instructions, receive_pty_instructions): ChannelWithContext<PtyInstruction> =
|
||||||
|
channel();
|
||||||
|
let send_pty_instructions =
|
||||||
|
SenderWithContext::new(err_ctx, SenderType::Sender(send_pty_instructions));
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
let mut pty_bus = PtyBus::new(
|
||||||
|
receive_pty_instructions,
|
||||||
|
os_input.clone(),
|
||||||
|
send_screen_instructions.clone(),
|
||||||
|
send_plugin_instructions.clone(),
|
||||||
|
opts.debug,
|
||||||
|
);
|
||||||
|
|
||||||
|
let pty_thread = thread::Builder::new()
|
||||||
|
.name("pty".to_string())
|
||||||
|
.spawn({
|
||||||
|
let send_server_instructions = send_server_instructions.clone();
|
||||||
|
move || loop {
|
||||||
|
let (event, mut err_ctx) = pty_bus
|
||||||
|
.receive_pty_instructions
|
||||||
|
.recv()
|
||||||
|
.expect("failed to receive event on channel");
|
||||||
|
err_ctx.add_call(ContextType::Pty(PtyContext::from(&event)));
|
||||||
|
match event {
|
||||||
|
PtyInstruction::SpawnTerminal(file_to_open) => {
|
||||||
|
let pid = pty_bus.spawn_terminal(file_to_open);
|
||||||
|
pty_bus
|
||||||
|
.send_screen_instructions
|
||||||
|
.send(ScreenInstruction::NewPane(PaneId::Terminal(pid)))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
PtyInstruction::SpawnTerminalVertically(file_to_open) => {
|
||||||
|
let pid = pty_bus.spawn_terminal(file_to_open);
|
||||||
|
pty_bus
|
||||||
|
.send_screen_instructions
|
||||||
|
.send(ScreenInstruction::VerticalSplit(PaneId::Terminal(pid)))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
PtyInstruction::SpawnTerminalHorizontally(file_to_open) => {
|
||||||
|
let pid = pty_bus.spawn_terminal(file_to_open);
|
||||||
|
pty_bus
|
||||||
|
.send_screen_instructions
|
||||||
|
.send(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid)))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
PtyInstruction::NewTab => {
|
||||||
|
if let Some(layout) = maybe_layout.clone() {
|
||||||
|
pty_bus.spawn_terminals_for_layout(layout);
|
||||||
|
} else {
|
||||||
|
let pid = pty_bus.spawn_terminal(None);
|
||||||
|
pty_bus
|
||||||
|
.send_screen_instructions
|
||||||
|
.send(ScreenInstruction::NewTab(pid))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PtyInstruction::ClosePane(id) => {
|
||||||
|
pty_bus.close_pane(id);
|
||||||
|
send_server_instructions
|
||||||
|
.send(ServerInstruction::DoneClosingPane)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
PtyInstruction::CloseTab(ids) => {
|
||||||
|
pty_bus.close_tab(ids);
|
||||||
|
send_server_instructions
|
||||||
|
.send(ServerInstruction::DoneClosingPane)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
PtyInstruction::Exit => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let screen_thread = thread::Builder::new()
|
||||||
|
.name("screen".to_string())
|
||||||
|
.spawn({
|
||||||
|
let os_input = os_input.clone();
|
||||||
|
let send_plugin_instructions = send_plugin_instructions.clone();
|
||||||
|
let send_pty_instructions = send_pty_instructions.clone();
|
||||||
|
let send_server_instructions = send_server_instructions.clone();
|
||||||
|
let max_panes = opts.max_panes;
|
||||||
|
|
||||||
|
move || {
|
||||||
|
let mut screen = Screen::new(
|
||||||
|
receive_screen_instructions,
|
||||||
|
send_plugin_instructions,
|
||||||
|
send_pty_instructions,
|
||||||
|
send_server_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_server_instructions.update(err_ctx);
|
||||||
|
screen.send_pty_instructions.update(err_ctx);
|
||||||
|
screen.send_plugin_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);
|
||||||
|
screen
|
||||||
|
.send_server_instructions
|
||||||
|
.send(ServerInstruction::DoneOpeningNewPane)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
ScreenInstruction::HorizontalSplit(pid) => {
|
||||||
|
screen.get_active_tab_mut().unwrap().horizontal_split(pid);
|
||||||
|
screen
|
||||||
|
.send_server_instructions
|
||||||
|
.send(ServerInstruction::DoneOpeningNewPane)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
ScreenInstruction::VerticalSplit(pid) => {
|
||||||
|
screen.get_active_tab_mut().unwrap().vertical_split(pid);
|
||||||
|
screen
|
||||||
|
.send_server_instructions
|
||||||
|
.send(ServerInstruction::DoneOpeningNewPane)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
screen
|
||||||
|
.send_server_instructions
|
||||||
|
.send(ServerInstruction::DoneUpdatingTabs)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
ScreenInstruction::SwitchTabNext => {
|
||||||
|
screen.switch_tab_next();
|
||||||
|
screen
|
||||||
|
.send_server_instructions
|
||||||
|
.send(ServerInstruction::DoneUpdatingTabs)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
ScreenInstruction::SwitchTabPrev => {
|
||||||
|
screen.switch_tab_prev();
|
||||||
|
screen
|
||||||
|
.send_server_instructions
|
||||||
|
.send(ServerInstruction::DoneUpdatingTabs)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
ScreenInstruction::CloseTab => {
|
||||||
|
screen.close_tab();
|
||||||
|
screen
|
||||||
|
.send_server_instructions
|
||||||
|
.send(ServerInstruction::DoneUpdatingTabs)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
ScreenInstruction::ApplyLayout(layout, new_pane_pids) => {
|
||||||
|
screen.apply_layout(Layout::new(layout), new_pane_pids);
|
||||||
|
screen
|
||||||
|
.send_server_instructions
|
||||||
|
.send(ServerInstruction::DoneUpdatingTabs)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
ScreenInstruction::GoToTab(tab_index) => {
|
||||||
|
screen.go_to_tab(tab_index as usize);
|
||||||
|
screen
|
||||||
|
.send_server_instructions
|
||||||
|
.send(ServerInstruction::DoneUpdatingTabs)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
ScreenInstruction::UpdateTabName(c) => {
|
||||||
|
screen.update_active_tab_name(c);
|
||||||
|
screen
|
||||||
|
.send_server_instructions
|
||||||
|
.send(ServerInstruction::DoneUpdatingTabs)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
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_pty_instructions = send_pty_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_pty_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_pty_instructions: send_pty_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();
|
||||||
|
ClientMetaData {
|
||||||
|
send_plugin_instructions,
|
||||||
|
send_screen_instructions,
|
||||||
|
send_pty_instructions,
|
||||||
|
screen_thread: Some(screen_thread),
|
||||||
|
pty_thread: Some(pty_thread),
|
||||||
|
wasm_thread: Some(wasm_thread),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -190,8 +190,11 @@ impl ClientOsApi for FakeInputOutput {
|
||||||
self.server_sender.update(new_ctx);
|
self.server_sender.update(new_ctx);
|
||||||
self.client_sender.update(new_ctx);
|
self.client_sender.update(new_ctx);
|
||||||
}
|
}
|
||||||
fn connect_to_server(&mut self) {
|
fn connect_to_server(&mut self, full_screen_ws: PositionAndSize) {
|
||||||
ClientOsApi::send_to_server(self, ServerInstruction::NewClient("zellij".into()));
|
ClientOsApi::send_to_server(
|
||||||
|
self,
|
||||||
|
ServerInstruction::NewClient("zellij".into(), full_screen_ws),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
fn client_recv(&self) -> (ClientInstruction, ErrorContext) {
|
fn client_recv(&self) -> (ClientInstruction, ErrorContext) {
|
||||||
self.client_receiver.lock().unwrap().recv().unwrap()
|
self.client_receiver.lock().unwrap().recv().unwrap()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue