Merge pull request #157 from mosaic-org/restructure

Restructure
This commit is contained in:
Denis Maximov 2021-01-30 13:45:51 +02:00 committed by GitHub
commit 63a2dc3399
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 846 additions and 684 deletions

115
Cargo.lock generated
View file

@ -53,7 +53,7 @@ version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00d68a33ebc8b57800847d00787307f84a562224a14db069b0acefe4c2abbf5d"
dependencies = [
"async-task",
"async-task 3.0.0",
"crossbeam-utils 0.7.2",
"futures-channel",
"futures-core",
@ -77,6 +77,12 @@ version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3"
[[package]]
name = "async-task"
version = "4.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0"
[[package]]
name = "atomic-waker"
version = "1.0.0"
@ -138,12 +144,26 @@ checksum = "d2468ff7bf85066b4a3678fede6fe66db31846d753ff0adfbfab2c6a6e81612b"
dependencies = [
"async-channel",
"atomic-waker",
"futures-lite",
"futures-lite 0.1.10",
"once_cell",
"parking",
"parking 1.0.6",
"waker-fn",
]
[[package]]
name = "blocking"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9"
dependencies = [
"async-channel",
"async-task 4.0.3",
"atomic-waker",
"fastrand",
"futures-lite 1.11.3",
"once_cell",
]
[[package]]
name = "bumpalo"
version = "3.4.0"
@ -491,9 +511,12 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "fastrand"
version = "1.3.3"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36a9cb09840f81cd211e435d00a4e487edd263dc3c8ff815c32dd76ad668ebed"
checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3"
dependencies = [
"instant",
]
[[package]]
name = "fnv"
@ -559,11 +582,26 @@ dependencies = [
"futures-core",
"futures-io",
"memchr",
"parking",
"parking 1.0.6",
"pin-project-lite 0.1.7",
"waker-fn",
]
[[package]]
name = "futures-lite"
version = "1.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4481d0cd0de1d204a4fa55e7d45f07b1d958abcb06714b3446438e2eff695fb"
dependencies = [
"fastrand",
"futures-core",
"futures-io",
"memchr",
"parking 2.0.0",
"pin-project-lite 0.2.0",
"waker-fn",
]
[[package]]
name = "futures-macro"
version = "0.3.5"
@ -749,6 +787,38 @@ dependencies = [
"serde_yaml",
]
[[package]]
name = "instant"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "interprocess"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "672deb438b19b22433dcc1b2c75f34c9964e31f325155da06a2dc605706fd264"
dependencies = [
"blocking 1.0.2",
"cfg-if 1.0.0",
"futures",
"intmap",
"lazy_static",
"libc",
"spinning",
"thiserror",
"winapi",
]
[[package]]
name = "intmap"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e50930385956f6c4a0b99f3dd654adcc40788456c36e17c5b20e1d1ceb523ec6"
[[package]]
name = "inventory"
version = "0.1.9"
@ -829,6 +899,15 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
[[package]]
name = "lock_api"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.11"
@ -906,6 +985,7 @@ dependencies = [
"directories-next",
"futures",
"insta",
"interprocess",
"libc",
"nix",
"serde",
@ -973,9 +1053,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.4.0"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d"
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
[[package]]
name = "parking"
@ -983,6 +1063,12 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cb300f271742d4a2a66c01b6b2fa0c83dfebd2e0bf11addb879a3547b4ed87c"
[[package]]
name = "parking"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
[[package]]
name = "pin-project"
version = "0.4.23"
@ -1374,8 +1460,8 @@ version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "620cbb3c6e34da57d3a248cda0cd01cd5848164dc062e764e65d06fe3ea7aed5"
dependencies = [
"async-task",
"blocking",
"async-task 3.0.0",
"blocking 0.4.7",
"concurrent-queue",
"fastrand",
"futures-io",
@ -1401,6 +1487,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "spinning"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d4f0e86297cad2658d92a707320d87bf4e6ae1050287f51d19b67ef3f153a7b"
dependencies = [
"lock_api",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"

View file

@ -26,6 +26,7 @@ unicode-width = "0.1.8"
vte = "0.8.0"
wasmer = "1.0.0"
wasmer-wasi = "1.0.0"
interprocess = "1.0.1"
[dependencies.async-std]
version = "1.3.0"

6
src/client/mod.rs Normal file
View file

@ -0,0 +1,6 @@
pub mod boundaries;
pub mod layout;
pub mod panes;
pub mod tab;
pub fn start_client() {}

View file

@ -1,6 +1,6 @@
#![allow(clippy::clippy::if_same_then_else)]
use crate::{pty_bus::VteEvent, tab::Pane, wasm_vm::PluginInstruction, SenderWithContext};
use crate::{common::SenderWithContext, pty_bus::VteEvent, tab::Pane, wasm_vm::PluginInstruction};
use std::{sync::mpsc::channel, unimplemented};

View file

@ -1,9 +1,9 @@
use crate::common::{AppInstruction, SenderWithContext};
use crate::panes::{PaneId, PositionAndSize, TerminalPane};
use crate::pty_bus::{PtyInstruction, VteEvent};
use crate::{boundaries::Boundaries, panes::PluginPane};
use crate::{layout::Layout, wasm_vm::PluginInstruction};
use crate::{os_input_output::OsApi, utils::shared::pad_to_size};
use crate::{AppInstruction, SenderWithContext};
use std::os::unix::io::RawFd;
use std::{
cmp::Reverse,

View file

@ -1,13 +1,13 @@
use super::{AppInstruction, OPENCALLS};
use crate::pty_bus::PtyInstruction;
use crate::screen::ScreenInstruction;
use crate::{AppInstruction, OPENCALLS};
use std::fmt::{Display, Error, Formatter};
const MAX_THREAD_CALL_STACK: usize = 6;
#[cfg(not(test))]
use crate::SenderWithContext;
use super::SenderWithContext;
#[cfg(not(test))]
use std::panic::PanicInfo;
#[cfg(not(test))]

View file

@ -1,9 +1,9 @@
use super::{os_input_output::OsApi, update_state, AppState};
use super::{AppInstruction, SenderWithContext, OPENCALLS};
use crate::pty_bus::PtyInstruction;
use crate::screen::ScreenInstruction;
use crate::CommandIsExecuting;
use crate::{errors::ContextType, wasm_vm::PluginInstruction};
use crate::{os_input_output::OsApi, update_state, AppState};
use crate::{AppInstruction, SenderWithContext, OPENCALLS};
struct InputHandler {
mode: InputMode,

46
src/common/ipc.rs Normal file
View file

@ -0,0 +1,46 @@
// IPC stuff for starting to split things into a client and server model
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
type SessionID = u64;
#[derive(PartialEq, Eq, Serialize, Deserialize, Hash)]
pub struct Session {
// Unique ID for this session
id: SessionID,
// Identifier for the underlying IPC primitive (socket, pipe)
conn_name: String,
// User configured alias for the session
alias: String,
}
// How do we want to connect to a session?
#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ClientType {
Reader,
Writer,
}
// Types of messages sent from the client to the server
#[derive(Serialize, Deserialize)]
pub enum ClientToServerMsg {
// List which sessions are available
ListSessions,
// Create a new session
CreateSession,
// Attach to a running session
AttachToSession(SessionID, ClientType),
// Force detach
DetachSession(SessionID),
// Disconnect from the session we're connected to
DisconnectFromSession,
}
// Types of messages sent from the server to the client
// @@@ Implement Serialize and Deserialize for this...
pub enum ServerToClientMsg {
// Info about a particular session
SessionInfo(Session),
// A list of sessions
SessionList(HashSet<Session>),
}

662
src/common/mod.rs Normal file
View file

@ -0,0 +1,662 @@
pub mod command_is_executing;
pub mod errors;
pub mod input;
pub mod ipc;
pub mod os_input_output;
pub mod pty_bus;
pub mod screen;
pub mod utils;
pub mod wasm_vm;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::mpsc::{channel, sync_channel, Receiver, SendError, Sender, SyncSender};
use std::thread;
use std::{cell::RefCell, sync::mpsc::TrySendError};
use std::{collections::HashMap, fs};
use crate::panes::PaneId;
use directories_next::ProjectDirs;
use input::InputMode;
use serde::{Deserialize, Serialize};
use termion::input::TermRead;
use wasm_vm::PluginEnv;
use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value};
use wasmer_wasi::{Pipe, WasiState};
use crate::cli::CliArgs;
use crate::layout::Layout;
use command_is_executing::CommandIsExecuting;
use errors::{AppContext, ContextType, ErrorContext, PluginContext, PtyContext, ScreenContext};
use input::input_loop;
use os_input_output::OsApi;
use pty_bus::{PtyBus, PtyInstruction};
use screen::{Screen, ScreenInstruction};
use utils::consts::{MOSAIC_IPC_PIPE, MOSAIC_ROOT_PLUGIN_DIR};
use wasm_vm::{mosaic_imports, wasi_stdout, wasi_write_string, PluginInstruction};
#[derive(Serialize, Deserialize, Debug)]
pub enum ApiCommand {
OpenFile(PathBuf),
SplitHorizontally,
SplitVertically,
MoveFocus,
}
// FIXME: It would be good to add some more things to this over time
#[derive(Debug, Clone)]
pub struct AppState {
pub input_mode: InputMode,
}
impl Default for AppState {
fn default() -> Self {
Self {
input_mode: InputMode::Normal,
}
}
}
// FIXME: Make this a method on the big `Communication` struct, so that app_tx can be extracted
// from self instead of being explicitly passed here
pub fn update_state(
app_tx: &SenderWithContext<AppInstruction>,
update_fn: impl FnOnce(AppState) -> AppState,
) {
let (state_tx, state_rx) = channel();
drop(app_tx.send(AppInstruction::GetState(state_tx)));
let state = state_rx.recv().unwrap();
drop(app_tx.send(AppInstruction::SetState(update_fn(state))))
}
pub type ChannelWithContext<T> = (Sender<(T, ErrorContext)>, Receiver<(T, ErrorContext)>);
pub type SyncChannelWithContext<T> = (SyncSender<(T, ErrorContext)>, Receiver<(T, ErrorContext)>);
#[derive(Clone)]
enum SenderType<T: Clone> {
Sender(Sender<(T, ErrorContext)>),
SyncSender(SyncSender<(T, ErrorContext)>),
}
#[derive(Clone)]
pub struct SenderWithContext<T: Clone> {
err_ctx: ErrorContext,
sender: SenderType<T>,
}
impl<T: Clone> SenderWithContext<T> {
fn new(err_ctx: ErrorContext, sender: SenderType<T>) -> Self {
Self { err_ctx, sender }
}
pub fn send(&self, event: T) -> Result<(), SendError<(T, ErrorContext)>> {
match self.sender {
SenderType::Sender(ref s) => s.send((event, self.err_ctx)),
SenderType::SyncSender(ref s) => s.send((event, self.err_ctx)),
}
}
pub fn try_send(&self, event: T) -> Result<(), TrySendError<(T, ErrorContext)>> {
if let SenderType::SyncSender(ref s) = self.sender {
s.try_send((event, self.err_ctx))
} else {
panic!("try_send can only be called on SyncSenders!")
}
}
pub fn update(&mut self, new_ctx: ErrorContext) {
self.err_ctx = new_ctx;
}
}
unsafe impl<T: Clone> Send for SenderWithContext<T> {}
unsafe impl<T: Clone> Sync for SenderWithContext<T> {}
thread_local!(static OPENCALLS: RefCell<ErrorContext> = RefCell::default());
#[derive(Clone)]
pub enum AppInstruction {
GetState(Sender<AppState>),
SetState(AppState),
Exit,
Error(String),
}
pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
let take_snapshot = "\u{1b}[?1049h";
os_input.unset_raw_mode(0);
let _ = os_input
.get_stdout_writer()
.write(take_snapshot.as_bytes())
.unwrap();
let mut app_state = AppState::default();
let command_is_executing = CommandIsExecuting::new();
let full_screen_ws = os_input.get_terminal_size_using_fd(0);
os_input.set_raw_mode(0);
let (send_screen_instructions, receive_screen_instructions): ChannelWithContext<
ScreenInstruction,
> = channel();
let err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
let mut send_screen_instructions =
SenderWithContext::new(err_ctx, SenderType::Sender(send_screen_instructions));
let (send_pty_instructions, receive_pty_instructions): ChannelWithContext<PtyInstruction> =
channel();
let mut send_pty_instructions =
SenderWithContext::new(err_ctx, SenderType::Sender(send_pty_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_app_instructions, receive_app_instructions): SyncChannelWithContext<AppInstruction> =
sync_channel(0);
let send_app_instructions =
SenderWithContext::new(err_ctx, SenderType::SyncSender(send_app_instructions));
let mut pty_bus = PtyBus::new(
receive_pty_instructions,
send_screen_instructions.clone(),
send_plugin_instructions.clone(),
os_input.clone(),
opts.debug,
);
// Don't use default layouts in tests, but do everywhere else
#[cfg(not(test))]
let default_layout = Some(PathBuf::from("default"));
#[cfg(test)]
let default_layout = None;
let maybe_layout = opts.layout.or(default_layout).map(Layout::new);
#[cfg(not(test))]
std::panic::set_hook({
use errors::handle_panic;
let send_app_instructions = send_app_instructions.clone();
Box::new(move |info| {
handle_panic(info, &send_app_instructions);
})
});
let pty_thread = thread::Builder::new()
.name("pty".to_string())
.spawn({
let mut command_is_executing = command_is_executing.clone();
send_pty_instructions.send(PtyInstruction::NewTab).unwrap();
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)));
pty_bus.send_screen_instructions.update(err_ctx);
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);
command_is_executing.done_closing_pane();
}
PtyInstruction::CloseTab(ids) => {
pty_bus.close_tab(ids);
command_is_executing.done_closing_pane();
}
PtyInstruction::Quit => {
break;
}
}
}
})
.unwrap();
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_pty_instructions = send_pty_instructions.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_pty_instructions,
send_plugin_instructions,
send_app_instructions,
&full_screen_ws,
os_input,
max_panes,
);
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.send_pty_instructions.update(err_ctx);
match event {
ScreenInstruction::Pty(pid, vte_event) => {
screen
.get_active_tab_mut()
.unwrap()
.handle_pty_event(pid, vte_event);
}
ScreenInstruction::Render => {
screen.render();
}
ScreenInstruction::NewPane(pid) => {
screen.get_active_tab_mut().unwrap().new_pane(pid);
command_is_executing.done_opening_new_pane();
}
ScreenInstruction::HorizontalSplit(pid) => {
screen.get_active_tab_mut().unwrap().horizontal_split(pid);
command_is_executing.done_opening_new_pane();
}
ScreenInstruction::VerticalSplit(pid) => {
screen.get_active_tab_mut().unwrap().vertical_split(pid);
command_is_executing.done_opening_new_pane();
}
ScreenInstruction::WriteCharacter(bytes) => {
screen
.get_active_tab_mut()
.unwrap()
.write_to_active_terminal(bytes);
}
ScreenInstruction::ResizeLeft => {
screen.get_active_tab_mut().unwrap().resize_left();
}
ScreenInstruction::ResizeRight => {
screen.get_active_tab_mut().unwrap().resize_right();
}
ScreenInstruction::ResizeDown => {
screen.get_active_tab_mut().unwrap().resize_down();
}
ScreenInstruction::ResizeUp => {
screen.get_active_tab_mut().unwrap().resize_up();
}
ScreenInstruction::MoveFocus => {
screen.get_active_tab_mut().unwrap().move_focus();
}
ScreenInstruction::MoveFocusLeft => {
screen.get_active_tab_mut().unwrap().move_focus_left();
}
ScreenInstruction::MoveFocusDown => {
screen.get_active_tab_mut().unwrap().move_focus_down();
}
ScreenInstruction::MoveFocusRight => {
screen.get_active_tab_mut().unwrap().move_focus_right();
}
ScreenInstruction::MoveFocusUp => {
screen.get_active_tab_mut().unwrap().move_focus_up();
}
ScreenInstruction::ScrollUp => {
screen
.get_active_tab_mut()
.unwrap()
.scroll_active_terminal_up();
}
ScreenInstruction::ScrollDown => {
screen
.get_active_tab_mut()
.unwrap()
.scroll_active_terminal_down();
}
ScreenInstruction::ClearScroll => {
screen
.get_active_tab_mut()
.unwrap()
.clear_active_terminal_scroll();
}
ScreenInstruction::CloseFocusedPane => {
screen.get_active_tab_mut().unwrap().close_focused_pane();
screen.render();
}
ScreenInstruction::SetSelectable(id, selectable) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_selectable(id, selectable);
// FIXME: Is this needed?
screen.render();
}
ScreenInstruction::SetMaxHeight(id, max_height) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_max_height(id, max_height);
}
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_opening_new_pane();
}
ScreenInstruction::SwitchTabNext => screen.switch_tab_next(),
ScreenInstruction::SwitchTabPrev => screen.switch_tab_prev(),
ScreenInstruction::CloseTab => screen.close_tab(),
ScreenInstruction::ApplyLayout((layout, new_pane_pids)) => {
screen.apply_layout(layout, new_pane_pids);
command_is_executing.done_opening_new_pane();
}
ScreenInstruction::Quit => {
break;
}
}
}
}
})
.unwrap();
let wasm_thread = thread::Builder::new()
.name("wasm".to_string())
.spawn({
let mut send_pty_instructions = send_pty_instructions.clone();
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_pty_instructions.update(err_ctx);
send_app_instructions.update(err_ctx);
match event {
PluginInstruction::Load(pid_tx, path) => {
let project_dirs =
ProjectDirs::from("org", "Mosaic Contributors", "Mosaic").unwrap();
let plugin_dir = project_dirs.data_dir().join("plugins/");
let root_plugin_dir = Path::new(MOSAIC_ROOT_PLUGIN_DIR);
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")))
.or_else(|_| {
fs::read(&root_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("mosaic")
.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_pty_instructions: send_pty_instructions.clone(),
send_screen_instructions: send_screen_instructions.clone(),
send_app_instructions: send_app_instructions.clone(),
wasi_env,
};
let mosaic = mosaic_imports(&store, &plugin_env);
let instance = Instance::new(&module, &mosaic.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::Draw(buf_tx, pid, rows, cols) => {
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
let draw = instance.exports.get_function("draw").unwrap();
draw.call(&[Value::I32(rows as i32), Value::I32(cols as i32)])
.unwrap();
buf_tx.send(wasi_stdout(&plugin_env.wasi_env)).unwrap();
}
// FIXME: Deduplicate this with the callback below!
PluginInstruction::Input(pid, input_bytes) => {
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
let handle_key = instance.exports.get_function("handle_key").unwrap();
for key in input_bytes.keys() {
if let Ok(key) = key {
wasi_write_string(
&plugin_env.wasi_env,
&serde_json::to_string(&key).unwrap(),
);
handle_key.call(&[]).unwrap();
}
}
drop(send_screen_instructions.send(ScreenInstruction::Render));
}
PluginInstruction::GlobalInput(input_bytes) => {
// FIXME: Set up an event subscription system, and timed callbacks
for (instance, plugin_env) in plugin_map.values() {
let handler =
instance.exports.get_function("handle_global_key").unwrap();
for key in input_bytes.keys() {
if let Ok(key) = key {
wasi_write_string(
&plugin_env.wasi_env,
&serde_json::to_string(&key).unwrap(),
);
handler.call(&[]).unwrap();
}
}
}
drop(send_screen_instructions.send(ScreenInstruction::Render));
}
PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)),
PluginInstruction::Quit => break,
}
}
})
.unwrap();
// TODO: currently we don't wait for this to quit
// because otherwise the app will hang. Need to fix this so it both
// listens to the ipc-bus and is able to quit cleanly
#[cfg(not(test))]
let _ipc_thread = thread::Builder::new()
.name("ipc_server".to_string())
.spawn({
use std::io::Read;
let mut send_pty_instructions = send_pty_instructions.clone();
let mut send_screen_instructions = send_screen_instructions.clone();
move || {
std::fs::remove_file(MOSAIC_IPC_PIPE).ok();
let listener = std::os::unix::net::UnixListener::bind(MOSAIC_IPC_PIPE)
.expect("could not listen on ipc socket");
let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
err_ctx.add_call(ContextType::IPCServer);
send_pty_instructions.update(err_ctx);
send_screen_instructions.update(err_ctx);
for stream in listener.incoming() {
match stream {
Ok(mut stream) => {
let mut buffer = [0; 65535]; // TODO: more accurate
let _ = stream
.read(&mut buffer)
.expect("failed to parse ipc message");
let decoded: ApiCommand = bincode::deserialize(&buffer)
.expect("failed to deserialize ipc message");
match &decoded {
ApiCommand::OpenFile(file_name) => {
let path = PathBuf::from(file_name);
send_pty_instructions
.send(PtyInstruction::SpawnTerminal(Some(path)))
.unwrap();
}
ApiCommand::SplitHorizontally => {
send_pty_instructions
.send(PtyInstruction::SpawnTerminalHorizontally(None))
.unwrap();
}
ApiCommand::SplitVertically => {
send_pty_instructions
.send(PtyInstruction::SpawnTerminalVertically(None))
.unwrap();
}
ApiCommand::MoveFocus => {
send_screen_instructions
.send(ScreenInstruction::MoveFocus)
.unwrap();
}
}
}
Err(err) => {
panic!("err {:?}", err);
}
}
}
}
})
.unwrap();
let _stdin_thread = thread::Builder::new()
.name("stdin_handler".to_string())
.spawn({
let send_screen_instructions = send_screen_instructions.clone();
let send_pty_instructions = send_pty_instructions.clone();
let send_plugin_instructions = send_plugin_instructions.clone();
let os_input = os_input.clone();
move || {
input_loop(
os_input,
command_is_executing,
send_screen_instructions,
send_pty_instructions,
send_plugin_instructions,
send_app_instructions,
)
}
});
#[warn(clippy::never_loop)]
loop {
let (app_instruction, mut err_ctx) = receive_app_instructions
.recv()
.expect("failed to receive app instruction on channel");
err_ctx.add_call(ContextType::App(AppContext::from(&app_instruction)));
send_screen_instructions.update(err_ctx);
send_pty_instructions.update(err_ctx);
match app_instruction {
AppInstruction::GetState(state_tx) => drop(state_tx.send(app_state.clone())),
AppInstruction::SetState(state) => app_state = state,
AppInstruction::Exit => {
let _ = send_screen_instructions.send(ScreenInstruction::Quit);
let _ = send_pty_instructions.send(PtyInstruction::Quit);
let _ = send_plugin_instructions.send(PluginInstruction::Quit);
break;
}
AppInstruction::Error(backtrace) => {
let _ = send_screen_instructions.send(ScreenInstruction::Quit);
let _ = screen_thread.join();
let _ = send_pty_instructions.send(PtyInstruction::Quit);
let _ = pty_thread.join();
let _ = send_plugin_instructions.send(PluginInstruction::Quit);
let _ = wasm_thread.join();
os_input.unset_raw_mode(0);
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
let error = format!("{}\n{}", goto_start_of_last_line, backtrace);
let _ = os_input
.get_stdout_writer()
.write(error.as_bytes())
.unwrap();
std::process::exit(1);
}
}
}
let _ = send_screen_instructions.send(ScreenInstruction::Quit);
screen_thread.join().unwrap();
let _ = send_pty_instructions.send(PtyInstruction::Quit);
pty_thread.join().unwrap();
let _ = send_plugin_instructions.send(PluginInstruction::Quit);
wasm_thread.join().unwrap();
// cleanup();
let reset_style = "\u{1b}[m";
let show_cursor = "\u{1b}[?25h";
let restore_snapshot = "\u{1b}[?1049l";
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
let goodbye_message = format!(
"{}\n{}{}{}Bye from Mosaic!",
goto_start_of_last_line, restore_snapshot, reset_style, show_cursor
);
os_input.unset_raw_mode(0);
let _ = os_input
.get_stdout_writer()
.write(goodbye_message.as_bytes())
.unwrap();
os_input.get_stdout_writer().flush().unwrap();
}

View file

@ -9,6 +9,7 @@ use ::std::time::{Duration, Instant};
use ::vte;
use std::path::PathBuf;
use super::{ScreenInstruction, SenderWithContext, OPENCALLS};
use crate::os_input_output::OsApi;
use crate::utils::logging::debug_to_file;
use crate::{
@ -16,7 +17,6 @@ use crate::{
panes::PaneId,
};
use crate::{layout::Layout, wasm_vm::PluginInstruction};
use crate::{ScreenInstruction, SenderWithContext, OPENCALLS};
pub struct ReadFromPid {
pid: RawFd,

View file

@ -2,13 +2,13 @@ use std::collections::BTreeMap;
use std::os::unix::io::RawFd;
use std::sync::mpsc::Receiver;
use super::{AppInstruction, SenderWithContext};
use crate::os_input_output::OsApi;
use crate::panes::PositionAndSize;
use crate::pty_bus::{PtyInstruction, VteEvent};
use crate::tab::Tab;
use crate::{errors::ErrorContext, wasm_vm::PluginInstruction};
use crate::{layout::Layout, panes::PaneId};
use crate::{AppInstruction, SenderWithContext};
/*
* Screen

View file

@ -5,9 +5,9 @@ use std::{
use wasmer::{imports, Function, ImportObject, Store, WasmerEnv};
use wasmer_wasi::WasiEnv;
use crate::{
input::get_help, panes::PaneId, pty_bus::PtyInstruction, screen::ScreenInstruction,
AppInstruction, SenderWithContext,
use super::{
input::get_help, pty_bus::PtyInstruction, screen::ScreenInstruction, AppInstruction, PaneId,
SenderWithContext,
};
#[derive(Clone, Debug)]

View file

@ -1,108 +1,30 @@
#[cfg(test)]
mod tests;
mod boundaries;
mod cli;
mod command_is_executing;
mod errors;
mod input;
mod layout;
mod os_input_output;
mod panes;
mod pty_bus;
mod screen;
mod tab;
mod utils;
mod common;
// TODO mod server;
mod client;
mod wasm_vm;
use client::{boundaries, layout, panes, tab};
use common::{
command_is_executing, errors, ipc, os_input_output, pty_bus, screen, start, utils, wasm_vm,
ApiCommand,
};
use std::io::Write;
use std::os::unix::net::UnixStream;
use std::path::{Path, PathBuf};
use std::sync::mpsc::{channel, sync_channel, Receiver, SendError, Sender, SyncSender};
use std::thread;
use std::{cell::RefCell, sync::mpsc::TrySendError};
use std::{collections::HashMap, fs};
use directories_next::ProjectDirs;
use input::InputMode;
use panes::PaneId;
use serde::{Deserialize, Serialize};
use structopt::StructOpt;
use termion::input::TermRead;
use wasm_vm::PluginEnv;
use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value};
use wasmer_wasi::{Pipe, WasiState};
use crate::cli::CliArgs;
use crate::command_is_executing::CommandIsExecuting;
use crate::errors::{
AppContext, ContextType, ErrorContext, PluginContext, PtyContext, ScreenContext,
};
use crate::input::input_loop;
use crate::layout::Layout;
use crate::os_input_output::{get_os_input, OsApi};
use crate::pty_bus::{PtyBus, PtyInstruction, VteEvent};
use crate::screen::{Screen, ScreenInstruction};
use crate::utils::consts::MOSAIC_ROOT_PLUGIN_DIR;
use crate::os_input_output::get_os_input;
use crate::pty_bus::VteEvent;
use crate::utils::{
consts::{MOSAIC_IPC_PIPE, MOSAIC_TMP_DIR, MOSAIC_TMP_LOG_DIR},
logging::*,
};
use crate::wasm_vm::{mosaic_imports, wasi_stdout, wasi_write_string, PluginInstruction};
thread_local!(static OPENCALLS: RefCell<ErrorContext> = RefCell::default());
#[derive(Serialize, Deserialize, Debug)]
enum ApiCommand {
OpenFile(PathBuf),
SplitHorizontally,
SplitVertically,
MoveFocus,
}
pub type ChannelWithContext<T> = (Sender<(T, ErrorContext)>, Receiver<(T, ErrorContext)>);
pub type SyncChannelWithContext<T> = (SyncSender<(T, ErrorContext)>, Receiver<(T, ErrorContext)>);
#[derive(Clone)]
enum SenderType<T: Clone> {
Sender(Sender<(T, ErrorContext)>),
SyncSender(SyncSender<(T, ErrorContext)>),
}
#[derive(Clone)]
pub struct SenderWithContext<T: Clone> {
err_ctx: ErrorContext,
sender: SenderType<T>,
}
impl<T: Clone> SenderWithContext<T> {
fn new(err_ctx: ErrorContext, sender: SenderType<T>) -> Self {
Self { err_ctx, sender }
}
pub fn send(&self, event: T) -> Result<(), SendError<(T, ErrorContext)>> {
match self.sender {
SenderType::Sender(ref s) => s.send((event, self.err_ctx)),
SenderType::SyncSender(ref s) => s.send((event, self.err_ctx)),
}
}
pub fn try_send(&self, event: T) -> Result<(), TrySendError<(T, ErrorContext)>> {
if let SenderType::SyncSender(ref s) = self.sender {
s.try_send((event, self.err_ctx))
} else {
panic!("try_send can only be called on SyncSenders!")
}
}
pub fn update(&mut self, new_ctx: ErrorContext) {
self.err_ctx = new_ctx;
}
}
unsafe impl<T: Clone> Send for SenderWithContext<T> {}
unsafe impl<T: Clone> Sync for SenderWithContext<T> {}
pub fn main() {
let opts = CliArgs::from_args();
@ -135,578 +57,3 @@ pub fn main() {
start(Box::new(os_input), opts);
}
}
// FIXME: It would be good to add some more things to this over time
#[derive(Debug, Clone)]
pub struct AppState {
pub input_mode: InputMode,
}
impl Default for AppState {
fn default() -> Self {
Self {
input_mode: InputMode::Normal,
}
}
}
// FIXME: Make this a method on the big `Communication` struct, so that app_tx can be extracted
// from self instead of being explicitly passed here
pub fn update_state(
app_tx: &SenderWithContext<AppInstruction>,
update_fn: impl FnOnce(AppState) -> AppState,
) {
let (state_tx, state_rx) = channel();
drop(app_tx.send(AppInstruction::GetState(state_tx)));
let state = state_rx.recv().unwrap();
drop(app_tx.send(AppInstruction::SetState(update_fn(state))))
}
#[derive(Clone)]
pub enum AppInstruction {
GetState(Sender<AppState>),
SetState(AppState),
Exit,
Error(String),
}
pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
let take_snapshot = "\u{1b}[?1049h";
os_input.unset_raw_mode(0);
let _ = os_input
.get_stdout_writer()
.write(take_snapshot.as_bytes())
.unwrap();
let mut app_state = AppState::default();
let command_is_executing = CommandIsExecuting::new();
let full_screen_ws = os_input.get_terminal_size_using_fd(0);
os_input.set_raw_mode(0);
let (send_screen_instructions, receive_screen_instructions): ChannelWithContext<
ScreenInstruction,
> = channel();
let err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
let mut send_screen_instructions =
SenderWithContext::new(err_ctx, SenderType::Sender(send_screen_instructions));
let (send_pty_instructions, receive_pty_instructions): ChannelWithContext<PtyInstruction> =
channel();
let mut send_pty_instructions =
SenderWithContext::new(err_ctx, SenderType::Sender(send_pty_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_app_instructions, receive_app_instructions): SyncChannelWithContext<AppInstruction> =
sync_channel(0);
let send_app_instructions =
SenderWithContext::new(err_ctx, SenderType::SyncSender(send_app_instructions));
let mut pty_bus = PtyBus::new(
receive_pty_instructions,
send_screen_instructions.clone(),
send_plugin_instructions.clone(),
os_input.clone(),
opts.debug,
);
// Don't use default layouts in tests, but do everywhere else
#[cfg(not(test))]
let default_layout = Some(PathBuf::from("default"));
#[cfg(test)]
let default_layout = None;
let maybe_layout = opts.layout.or(default_layout).map(Layout::new);
#[cfg(not(test))]
std::panic::set_hook({
use crate::errors::handle_panic;
let send_app_instructions = send_app_instructions.clone();
Box::new(move |info| {
handle_panic(info, &send_app_instructions);
})
});
let pty_thread = thread::Builder::new()
.name("pty".to_string())
.spawn({
let mut command_is_executing = command_is_executing.clone();
send_pty_instructions.send(PtyInstruction::NewTab).unwrap();
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)));
pty_bus.send_screen_instructions.update(err_ctx);
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);
command_is_executing.done_closing_pane();
}
PtyInstruction::CloseTab(ids) => {
pty_bus.close_tab(ids);
command_is_executing.done_closing_pane();
}
PtyInstruction::Quit => {
break;
}
}
}
})
.unwrap();
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_pty_instructions = send_pty_instructions.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_pty_instructions,
send_plugin_instructions,
send_app_instructions,
&full_screen_ws,
os_input,
max_panes,
);
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.send_pty_instructions.update(err_ctx);
match event {
ScreenInstruction::Pty(pid, vte_event) => {
screen
.get_active_tab_mut()
.unwrap()
.handle_pty_event(pid, vte_event);
}
ScreenInstruction::Render => {
screen.render();
}
ScreenInstruction::NewPane(pid) => {
screen.get_active_tab_mut().unwrap().new_pane(pid);
command_is_executing.done_opening_new_pane();
}
ScreenInstruction::HorizontalSplit(pid) => {
screen.get_active_tab_mut().unwrap().horizontal_split(pid);
command_is_executing.done_opening_new_pane();
}
ScreenInstruction::VerticalSplit(pid) => {
screen.get_active_tab_mut().unwrap().vertical_split(pid);
command_is_executing.done_opening_new_pane();
}
ScreenInstruction::WriteCharacter(bytes) => {
screen
.get_active_tab_mut()
.unwrap()
.write_to_active_terminal(bytes);
}
ScreenInstruction::ResizeLeft => {
screen.get_active_tab_mut().unwrap().resize_left();
}
ScreenInstruction::ResizeRight => {
screen.get_active_tab_mut().unwrap().resize_right();
}
ScreenInstruction::ResizeDown => {
screen.get_active_tab_mut().unwrap().resize_down();
}
ScreenInstruction::ResizeUp => {
screen.get_active_tab_mut().unwrap().resize_up();
}
ScreenInstruction::MoveFocus => {
screen.get_active_tab_mut().unwrap().move_focus();
}
ScreenInstruction::MoveFocusLeft => {
screen.get_active_tab_mut().unwrap().move_focus_left();
}
ScreenInstruction::MoveFocusDown => {
screen.get_active_tab_mut().unwrap().move_focus_down();
}
ScreenInstruction::MoveFocusRight => {
screen.get_active_tab_mut().unwrap().move_focus_right();
}
ScreenInstruction::MoveFocusUp => {
screen.get_active_tab_mut().unwrap().move_focus_up();
}
ScreenInstruction::ScrollUp => {
screen
.get_active_tab_mut()
.unwrap()
.scroll_active_terminal_up();
}
ScreenInstruction::ScrollDown => {
screen
.get_active_tab_mut()
.unwrap()
.scroll_active_terminal_down();
}
ScreenInstruction::ClearScroll => {
screen
.get_active_tab_mut()
.unwrap()
.clear_active_terminal_scroll();
}
ScreenInstruction::CloseFocusedPane => {
screen.get_active_tab_mut().unwrap().close_focused_pane();
screen.render();
}
ScreenInstruction::SetSelectable(id, selectable) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_selectable(id, selectable);
// FIXME: Is this needed?
screen.render();
}
ScreenInstruction::SetMaxHeight(id, max_height) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_max_height(id, max_height);
}
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_opening_new_pane();
}
ScreenInstruction::SwitchTabNext => screen.switch_tab_next(),
ScreenInstruction::SwitchTabPrev => screen.switch_tab_prev(),
ScreenInstruction::CloseTab => screen.close_tab(),
ScreenInstruction::ApplyLayout((layout, new_pane_pids)) => {
screen.apply_layout(layout, new_pane_pids);
command_is_executing.done_opening_new_pane();
}
ScreenInstruction::Quit => {
break;
}
}
}
}
})
.unwrap();
let wasm_thread = thread::Builder::new()
.name("wasm".to_string())
.spawn({
let mut send_pty_instructions = send_pty_instructions.clone();
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_pty_instructions.update(err_ctx);
send_app_instructions.update(err_ctx);
match event {
PluginInstruction::Load(pid_tx, path) => {
let project_dirs =
ProjectDirs::from("org", "Mosaic Contributors", "Mosaic").unwrap();
let plugin_dir = project_dirs.data_dir().join("plugins/");
let root_plugin_dir = Path::new(MOSAIC_ROOT_PLUGIN_DIR);
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")))
.or_else(|_| {
fs::read(&root_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("mosaic")
.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_pty_instructions: send_pty_instructions.clone(),
send_screen_instructions: send_screen_instructions.clone(),
send_app_instructions: send_app_instructions.clone(),
wasi_env,
};
let mosaic = mosaic_imports(&store, &plugin_env);
let instance = Instance::new(&module, &mosaic.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::Draw(buf_tx, pid, rows, cols) => {
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
let draw = instance.exports.get_function("draw").unwrap();
draw.call(&[Value::I32(rows as i32), Value::I32(cols as i32)])
.unwrap();
buf_tx.send(wasi_stdout(&plugin_env.wasi_env)).unwrap();
}
// FIXME: Deduplicate this with the callback below!
PluginInstruction::Input(pid, input_bytes) => {
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
let handle_key = instance.exports.get_function("handle_key").unwrap();
for key in input_bytes.keys() {
if let Ok(key) = key {
wasi_write_string(
&plugin_env.wasi_env,
&serde_json::to_string(&key).unwrap(),
);
handle_key.call(&[]).unwrap();
}
}
drop(send_screen_instructions.send(ScreenInstruction::Render));
}
PluginInstruction::GlobalInput(input_bytes) => {
// FIXME: Set up an event subscription system, and timed callbacks
for (instance, plugin_env) in plugin_map.values() {
let handler =
instance.exports.get_function("handle_global_key").unwrap();
for key in input_bytes.keys() {
if let Ok(key) = key {
wasi_write_string(
&plugin_env.wasi_env,
&serde_json::to_string(&key).unwrap(),
);
handler.call(&[]).unwrap();
}
}
}
drop(send_screen_instructions.send(ScreenInstruction::Render));
}
PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)),
PluginInstruction::Quit => break,
}
}
})
.unwrap();
// TODO: currently we don't wait for this to quit
// because otherwise the app will hang. Need to fix this so it both
// listens to the ipc-bus and is able to quit cleanly
#[cfg(not(test))]
let _ipc_thread = thread::Builder::new()
.name("ipc_server".to_string())
.spawn({
use std::io::Read;
let mut send_pty_instructions = send_pty_instructions.clone();
let mut send_screen_instructions = send_screen_instructions.clone();
move || {
std::fs::remove_file(MOSAIC_IPC_PIPE).ok();
let listener = std::os::unix::net::UnixListener::bind(MOSAIC_IPC_PIPE)
.expect("could not listen on ipc socket");
let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
err_ctx.add_call(ContextType::IPCServer);
send_pty_instructions.update(err_ctx);
send_screen_instructions.update(err_ctx);
for stream in listener.incoming() {
match stream {
Ok(mut stream) => {
let mut buffer = [0; 65535]; // TODO: more accurate
let _ = stream
.read(&mut buffer)
.expect("failed to parse ipc message");
let decoded: ApiCommand = bincode::deserialize(&buffer)
.expect("failed to deserialize ipc message");
match &decoded {
ApiCommand::OpenFile(file_name) => {
let path = PathBuf::from(file_name);
send_pty_instructions
.send(PtyInstruction::SpawnTerminal(Some(path)))
.unwrap();
}
ApiCommand::SplitHorizontally => {
send_pty_instructions
.send(PtyInstruction::SpawnTerminalHorizontally(None))
.unwrap();
}
ApiCommand::SplitVertically => {
send_pty_instructions
.send(PtyInstruction::SpawnTerminalVertically(None))
.unwrap();
}
ApiCommand::MoveFocus => {
send_screen_instructions
.send(ScreenInstruction::MoveFocus)
.unwrap();
}
}
}
Err(err) => {
panic!("err {:?}", err);
}
}
}
}
})
.unwrap();
let _stdin_thread = thread::Builder::new()
.name("stdin_handler".to_string())
.spawn({
let send_screen_instructions = send_screen_instructions.clone();
let send_pty_instructions = send_pty_instructions.clone();
let send_plugin_instructions = send_plugin_instructions.clone();
let os_input = os_input.clone();
move || {
input_loop(
os_input,
command_is_executing,
send_screen_instructions,
send_pty_instructions,
send_plugin_instructions,
send_app_instructions,
)
}
});
#[warn(clippy::never_loop)]
loop {
let (app_instruction, mut err_ctx) = receive_app_instructions
.recv()
.expect("failed to receive app instruction on channel");
err_ctx.add_call(ContextType::App(AppContext::from(&app_instruction)));
send_screen_instructions.update(err_ctx);
send_pty_instructions.update(err_ctx);
match app_instruction {
AppInstruction::GetState(state_tx) => drop(state_tx.send(app_state.clone())),
AppInstruction::SetState(state) => app_state = state,
AppInstruction::Exit => {
let _ = send_screen_instructions.send(ScreenInstruction::Quit);
let _ = send_pty_instructions.send(PtyInstruction::Quit);
let _ = send_plugin_instructions.send(PluginInstruction::Quit);
break;
}
AppInstruction::Error(backtrace) => {
let _ = send_screen_instructions.send(ScreenInstruction::Quit);
let _ = screen_thread.join();
let _ = send_pty_instructions.send(PtyInstruction::Quit);
let _ = pty_thread.join();
let _ = send_plugin_instructions.send(PluginInstruction::Quit);
let _ = wasm_thread.join();
os_input.unset_raw_mode(0);
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
let error = format!("{}\n{}", goto_start_of_last_line, backtrace);
let _ = os_input
.get_stdout_writer()
.write(error.as_bytes())
.unwrap();
std::process::exit(1);
}
}
}
let _ = send_screen_instructions.send(ScreenInstruction::Quit);
screen_thread.join().unwrap();
let _ = send_pty_instructions.send(PtyInstruction::Quit);
pty_thread.join().unwrap();
let _ = send_plugin_instructions.send(PluginInstruction::Quit);
wasm_thread.join().unwrap();
// cleanup();
let reset_style = "\u{1b}[m";
let show_cursor = "\u{1b}[?25h";
let restore_snapshot = "\u{1b}[?1049l";
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
let goodbye_message = format!(
"{}\n{}{}{}Bye from Mosaic!",
goto_start_of_last_line, restore_snapshot, reset_style, show_cursor
);
os_input.unset_raw_mode(0);
let _ = os_input
.get_stdout_writer()
.write(goodbye_message.as_bytes())
.unwrap();
os_input.get_stdout_writer().flush().unwrap();
}

5
src/server/mod.rs Normal file
View file

@ -0,0 +1,5 @@
use super::super::common::{screen};
pub fn start_server() {
// TODO
}