feat(infra): initial plugin system

- Added the ability to load and keep track of several plugins at once
- Removed `wasm-wip` feature flag, enabling plugins in all builds
- `split_space` now returns a tuple with the `part_position_and_size` (as before) plus a cloned version of the part, so plugins and terminals can be distinguished by its users
- Added a `Draw` instruction for rendering plugins when asked to by Mosaic
- Added a new `PluginPane`, implementing `Pane` for plugins!
- Replaced RawFd as a pane-identifier with the `PaneId` enum in most places
- `change_size` -> `change_pos_and_size`, with the functionality updated to fit the new name
- `buffer_as_vte_output` -> `render`
- `pid()` on the `Pane` trait now returns a `PaneId`
- Changed lots of functions in `tab.rs` to be more pane oriented, renaming some to remove the `terminal` and switching to `PaneId`s
- splitting functions in `tab.rs` now correctly update the positions of returned fragments
- Used `PaneId`'s as a replacement for `PaneKind` in the tab `BTreeMap`
- Removed `get_rows` and `get_columns` from the `Pane` trait
- Layouts can now create plugin panes!
- Changed lots of the `active_terminal` stuff in `tab.rs` to work with all panes
- Changed the `Tab` `render` function, so that common requirements are moved there and out of the `Pane::render()` methods
- Added a `shared.rs` to `utils` with a couple of useful functions
- We can now accept input and pass it to plugins via serialised JSON
- Trigger a screen render after handling input meant for a plugin
- Allow plugins to be properly closed
- Allow plugin panes to be split (opening a new terminal)
- Allow plugin API functions to send messages on the PTY bus
- Added the API functionality needed for a plugin to open a file in a new pane
- No more compiler warnings!
- Fixed 53 clippy lints!
This commit is contained in:
Brooks Rady 2021-01-08 14:14:39 +00:00 committed by GitHub
commit 7efc435bda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1194 additions and 1044 deletions

101
Cargo.lock generated
View File

@ -890,11 +890,13 @@ dependencies = [
"serde_json",
"serde_yaml",
"signal-hook",
"strip-ansi-escapes",
"structopt",
"termion",
"termios",
"unicode-truncate",
"unicode-width",
"vte",
"vte 0.8.0",
"wasmer",
"wasmer-wasi",
]
@ -931,6 +933,12 @@ dependencies = [
"libc",
]
[[package]]
name = "numtoa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]]
name = "object"
version = "0.22.0"
@ -1134,6 +1142,15 @@ version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_termios"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
dependencies = [
"redox_syscall",
]
[[package]]
name = "regalloc"
version = "0.0.31"
@ -1349,6 +1366,15 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "strip-ansi-escapes"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d63676e2abafa709460982ddc02a3bb586b6d15a49b75c212e06edd3933acee"
dependencies = [
"vte 0.3.3",
]
[[package]]
name = "strsim"
version = "0.8.0"
@ -1426,6 +1452,18 @@ dependencies = [
"winapi",
]
[[package]]
name = "termion"
version = "1.6.0"
source = "git+https://gitlab.com/TheLostLambda/termion.git#70159e07c59c02dc681db3b38dea16c295610ffa"
dependencies = [
"libc",
"numtoa",
"redox_syscall",
"redox_termios",
"serde",
]
[[package]]
name = "termios"
version = "0.3.2"
@ -1559,6 +1597,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "utf8parse"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d"
[[package]]
name = "utf8parse"
version = "0.2.0"
@ -1583,6 +1627,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "vte"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f42f536e22f7fcbb407639765c8fd78707a33109301f834a594758bedd6e8cf"
dependencies = [
"utf8parse 0.1.1",
]
[[package]]
name = "vte"
version = "0.8.0"
@ -1590,7 +1643,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96cc8a191608603611e78c6ec11dafef37e3cca0775aeef1931824753e81711d"
dependencies = [
"arrayvec",
"utf8parse",
"utf8parse 0.2.0",
"vte_generate_state_changes",
]
@ -1690,9 +1743,9 @@ checksum = "93b162580e34310e5931c4b792560108b10fd14d64915d7fff8ff00180e70092"
[[package]]
name = "wasmer"
version = "1.0.0-rc1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe7fb8734c3e522aea0bed12315115e4c5d684c3d312db5f3ef6a8a312b1b47"
checksum = "94b1ece7c894857344ae93506686ae36ccd867b4ed55819c06d2316d009098d4"
dependencies = [
"cfg-if 0.1.10",
"indexmap",
@ -1713,9 +1766,9 @@ dependencies = [
[[package]]
name = "wasmer-compiler"
version = "1.0.0-rc1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97789fdc5968ea3d29528648dc2422e0c795ca195b88a59c30a56f0e52805690"
checksum = "fc85134b257e5fba5870693441e300b601d08f18833ac4fa6934f0b72afc56d2"
dependencies = [
"enumset",
"raw-cpuid",
@ -1731,9 +1784,9 @@ dependencies = [
[[package]]
name = "wasmer-compiler-cranelift"
version = "1.0.0-rc1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e80c86796019ef6d4519e1a66f2b99ab73b937a4e43e723772956b3e8c8df23"
checksum = "60d68fb05dbe908724901b680070560944d99d04c52c763e98124aa988ac6dd0"
dependencies = [
"cranelift-codegen",
"cranelift-frontend",
@ -1750,9 +1803,9 @@ dependencies = [
[[package]]
name = "wasmer-derive"
version = "1.0.0-rc1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c74a84dc4ba0d60e9419f335734fa807097caf4938b2b44bc0703688a42b467"
checksum = "ca24205ffdf2d3b1a9c01219f4f3f0a1382658680abe73bc5b146f941adeeb8e"
dependencies = [
"proc-macro-error",
"proc-macro2",
@ -1762,9 +1815,9 @@ dependencies = [
[[package]]
name = "wasmer-engine"
version = "1.0.0-rc1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e787fb8e42b5ad32c1c8dcf105e42d2919dfb3ea4b8e286de3e43f306ae1457b"
checksum = "d91ed16436a9813d92f434e1d40fdf91b45ca30f351a799f793015359acca86b"
dependencies = [
"backtrace",
"bincode",
@ -1783,9 +1836,9 @@ dependencies = [
[[package]]
name = "wasmer-engine-jit"
version = "1.0.0-rc1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "552f4252f8d7984279c55df0970ca1d42b1e4c63d918e7af1cd004e427e5008c"
checksum = "df1e3ca5e34eacd4ab6d9d32edd41b51d2e39cf3d75453611c9c57cee3a64691"
dependencies = [
"bincode",
"cfg-if 0.1.10",
@ -1801,9 +1854,9 @@ dependencies = [
[[package]]
name = "wasmer-engine-native"
version = "1.0.0-rc1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5264031a9b398a071fa128fe89fb55bc75f9c0ac5eaa7f1f9ef9efcee08afa1c"
checksum = "6a21d6c5ae0c384ba2f01f598c95b01d4da2eaec3376fb96de2ded38c54143a0"
dependencies = [
"bincode",
"cfg-if 0.1.10",
@ -1822,9 +1875,9 @@ dependencies = [
[[package]]
name = "wasmer-object"
version = "1.0.0-rc1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e22ccf03052d73b3588bd30de94db9ee949957a543d0c317122f2b87b7d1f309"
checksum = "06e007e73ec7775aecc61045092dabfcff1e9f228129cd129e76a3e6aae26454"
dependencies = [
"object",
"thiserror",
@ -1834,9 +1887,9 @@ dependencies = [
[[package]]
name = "wasmer-types"
version = "1.0.0-rc1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3ea5b135db86baf39ce45f6cf98cc97d6e4234d3f75ac56a026f94bd8b68b1"
checksum = "2dbba7a95edb61b40daa43079979fc3212234e1645a15b8c527c36decad59fc6"
dependencies = [
"cranelift-entity",
"serde",
@ -1845,9 +1898,9 @@ dependencies = [
[[package]]
name = "wasmer-vm"
version = "1.0.0-rc1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d766b8db150b7e524c83b244e14a1180bf919b4f8bea6f063bae9a8e8d4156"
checksum = "9cd9acd4d53c004a11fcaff17f2a2528ae8f1748c6d5c4aea7d8bed2d9236f0f"
dependencies = [
"backtrace",
"cc",
@ -1865,9 +1918,9 @@ dependencies = [
[[package]]
name = "wasmer-wasi"
version = "1.0.0-rc1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b9e383c0a20fb697080b8e87613a0bb2e901a9f06ca710030b4a521ebcc398"
checksum = "5de224b58d5813a37dce64c483347909c478c5c2dcb15a93d67cfe6a863fd92c"
dependencies = [
"bincode",
"byteorder",

View File

@ -16,29 +16,25 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.8"
signal-hook = "0.1.10"
strip-ansi-escapes = "0.1.0"
structopt = "0.3"
termion = { git = "https://gitlab.com/TheLostLambda/termion.git", features = ["serde"] }
termios = "0.3"
unicode-truncate = "0.1.1"
unicode-width = "0.1.8"
vte = "0.8.0"
wasmer = "1.0.0"
wasmer-wasi = "1.0.0"
[dependencies.async-std]
version = "1.3.0"
features = ["unstable"]
[dependencies.wasmer]
version = "1.0.0-rc"
optional = true
[dependencies.wasmer-wasi]
version = "1.0.0-rc"
optional = true
[features]
wasm-wip = ["wasmer", "wasmer-wasi"]
[dev-dependencies]
insta = "0.16.1"
[build-dependencies]
structopt = "0.3"
[profile.release]
lto = true

View File

@ -8,9 +8,7 @@ const BIN_NAME: &str = "mosaic";
fn main() {
let mut clap_app = CliArgs::clap();
println!("cargo:rerun-if-changed=src/app.rs");
let mut out_dir = std::env::var_os("CARGO_MANIFEST_DIR")
.unwrap()
.to_os_string();
let mut out_dir = std::env::var_os("CARGO_MANIFEST_DIR").unwrap();
out_dir.push("/assets/completions");
println!(

View File

@ -372,7 +372,7 @@ impl Boundaries {
boundary_characters: HashMap::new(),
}
}
pub fn add_rect(&mut self, rect: &Box<dyn Pane>) {
pub fn add_rect(&mut self, rect: &dyn Pane) {
if self.rect_right_boundary_is_before_screen_edge(rect) {
// let boundary_x_coords = self.rect_right_boundary_x_coords(rect);
let boundary_x_coords = rect.right_boundary_x_coords();
@ -429,20 +429,20 @@ impl Boundaries {
}
vte_output
}
fn rect_right_boundary_is_before_screen_edge(&self, rect: &Box<dyn Pane>) -> bool {
fn rect_right_boundary_is_before_screen_edge(&self, rect: &dyn Pane) -> bool {
rect.x() + rect.columns() < self.columns
}
fn rect_bottom_boundary_is_before_screen_edge(&self, rect: &Box<dyn Pane>) -> bool {
fn rect_bottom_boundary_is_before_screen_edge(&self, rect: &dyn Pane) -> bool {
rect.y() + rect.rows() < self.rows
}
fn rect_right_boundary_row_start(&self, rect: &Box<dyn Pane>) -> usize {
fn rect_right_boundary_row_start(&self, rect: &dyn Pane) -> usize {
if rect.y() == 0 {
0
} else {
rect.y() - 1
}
}
fn rect_right_boundary_row_end(&self, rect: &Box<dyn Pane>) -> usize {
fn rect_right_boundary_row_end(&self, rect: &dyn Pane) -> usize {
let rect_bottom_row = rect.y() + rect.rows();
// we do this because unless we're on the screen edge, we'd like to go one extra row to
// connect to whatever boundary is beneath us
@ -452,14 +452,14 @@ impl Boundaries {
rect_bottom_row + 1
}
}
fn rect_bottom_boundary_col_start(&self, rect: &Box<dyn Pane>) -> usize {
fn rect_bottom_boundary_col_start(&self, rect: &dyn Pane) -> usize {
if rect.x() == 0 {
0
} else {
rect.x() - 1
}
}
fn rect_bottom_boundary_col_end(&self, rect: &Box<dyn Pane>) -> usize {
fn rect_bottom_boundary_col_end(&self, rect: &dyn Pane) -> usize {
let rect_right_col = rect.x() + rect.columns();
// we do this because unless we're on the screen edge, we'd like to go one extra column to
// connect to whatever boundary is right of us

View File

@ -1,3 +1,4 @@
#![allow(clippy::mutex_atomic)]
use std::sync::{Arc, Condvar, Mutex};
#[derive(Clone)]

View File

@ -1,17 +1,22 @@
use crate::pty_bus::PtyInstruction;
use crate::screen::ScreenInstruction;
use crate::{AppInstruction, SenderWithContext, OPENCALLS};
use backtrace::Backtrace;
use crate::{AppInstruction, OPENCALLS};
use std::fmt::{Display, Error, Formatter};
use std::panic::PanicInfo;
use std::{process, thread};
const MAX_THREAD_CALL_STACK: usize = 6;
#[cfg(not(test))]
use crate::SenderWithContext;
#[cfg(not(test))]
use std::panic::PanicInfo;
#[cfg(not(test))]
pub fn handle_panic(
info: &PanicInfo<'_>,
send_app_instructions: &SenderWithContext<AppInstruction>,
) {
use backtrace::Backtrace;
use std::{process, thread};
let backtrace = Backtrace::new();
let thread = thread::current();
let thread = thread.name().unwrap_or("unnamed");
@ -84,6 +89,12 @@ impl ErrorContext {
}
}
impl Default for ErrorContext {
fn default() -> Self {
Self::new()
}
}
impl Display for ErrorContext {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
writeln!(f, "Originating Thread(s):")?;
@ -101,7 +112,7 @@ impl Display for ErrorContext {
pub enum ContextType {
Screen(ScreenContext),
Pty(PtyContext),
#[cfg(feature = "wasm-wip")]
Plugin(PluginContext),
App(AppContext),
IPCServer,
@ -117,7 +128,7 @@ impl Display for ContextType {
match *self {
ContextType::Screen(c) => write!(f, "{}screen_thread: {}{:?}", purple, green, c),
ContextType::Pty(c) => write!(f, "{}pty_thread: {}{:?}", purple, green, c),
#[cfg(feature = "wasm-wip")]
ContextType::Plugin(c) => write!(f, "{}plugin_thread: {}{:?}", purple, green, c),
ContextType::App(c) => write!(f, "{}main_thread: {}{:?}", purple, green, c),
ContextType::IPCServer => write!(f, "{}ipc_server: {}AcceptInput", purple, green),
@ -225,21 +236,24 @@ impl From<&PtyInstruction> for PtyContext {
}
// FIXME: This whole pattern *needs* a macro eventually, it's soul-crushing to write
#[cfg(feature = "wasm-wip")]
use crate::wasm_vm::PluginInstruction;
#[cfg(feature = "wasm-wip")]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PluginContext {
Load,
Draw,
Input,
Unload,
Quit,
}
#[cfg(feature = "wasm-wip")]
impl From<&PluginInstruction> for PluginContext {
fn from(plugin_instruction: &PluginInstruction) -> Self {
match *plugin_instruction {
PluginInstruction::Load(_) => PluginContext::Load,
PluginInstruction::Load(..) => PluginContext::Load,
PluginInstruction::Draw(..) => PluginContext::Draw,
PluginInstruction::Input(..) => PluginContext::Input,
PluginInstruction::Unload(_) => PluginContext::Unload,
PluginInstruction::Quit => PluginContext::Quit,
}

View File

@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use std::{fs::File, io::prelude::*, path::PathBuf};
use crate::terminal_pane::PositionAndSize;
use crate::panes::PositionAndSize;
fn split_space_to_parts_vertically(
space_to_split: &PositionAndSize,
@ -61,8 +61,11 @@ fn split_space_to_parts_horizontally(
split_parts
}
fn split_space(space_to_split: &PositionAndSize, layout: &Layout) -> Vec<PositionAndSize> {
let mut pane_positions: Vec<PositionAndSize> = vec![];
fn split_space(
space_to_split: &PositionAndSize,
layout: &Layout,
) -> Vec<(Layout, PositionAndSize)> {
let mut pane_positions = Vec::new();
let percentages: Vec<u8> = layout
.parts
.iter()
@ -88,7 +91,7 @@ fn split_space(space_to_split: &PositionAndSize, layout: &Layout) -> Vec<Positio
let mut part_positions = split_space(&part_position_and_size, part);
pane_positions.append(&mut part_positions);
} else {
pane_positions.push(*part_position_and_size);
pane_positions.push((part.clone(), *part_position_and_size));
}
}
pane_positions
@ -165,27 +168,22 @@ impl Layout {
panic!("The total percent for each part should equal 100.");
}
}
pub fn total_panes(&self) -> usize {
pub fn total_terminal_panes(&self) -> usize {
let mut total_panes = 0;
total_panes += self.parts.len();
for part in self.parts.iter() {
total_panes += part.total_panes();
if part.plugin.is_none() {
total_panes += part.total_terminal_panes();
}
}
total_panes
}
// FIXME: I probably shouldn't exist, much less with PathBuf (use &Path)
#[cfg(feature = "wasm-wip")]
pub fn list_plugins(&self) -> Vec<&PathBuf> {
dbg!(&self);
let mut plugins: Vec<_> = self.parts.iter().flat_map(Layout::list_plugins).collect();
if let Some(path) = &self.plugin {
plugins.push(path);
}
plugins
}
pub fn position_panes_in_space(&self, space: &PositionAndSize) -> Vec<PositionAndSize> {
pub fn position_panes_in_space(
&self,
space: &PositionAndSize,
) -> Vec<(Layout, PositionAndSize)> {
split_space(space, &self)
}
}

View File

@ -8,26 +8,35 @@ mod errors;
mod input;
mod layout;
mod os_input_output;
mod panes;
mod pty_bus;
mod screen;
mod tab;
mod terminal_pane;
mod utils;
#[cfg(feature = "wasm-wip")]
mod wasm_vm;
use std::cell::RefCell;
use std::collections::HashMap;
use std::io::Write;
use std::os::unix::net::UnixStream;
use std::path::PathBuf;
use std::sync::mpsc::{channel, sync_channel, Receiver, SendError, Sender, SyncSender};
use std::thread;
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, PtyContext, ScreenContext};
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};
@ -37,9 +46,9 @@ use crate::utils::{
consts::{MOSAIC_IPC_PIPE, MOSAIC_TMP_DIR, MOSAIC_TMP_LOG_DIR},
logging::*,
};
use std::cell::RefCell;
use crate::wasm_vm::{mosaic_imports, wasi_stdout, wasi_write_string, PluginInstruction};
thread_local!(static OPENCALLS: RefCell<ErrorContext> = RefCell::new(ErrorContext::new()));
thread_local!(static OPENCALLS: RefCell<ErrorContext> = RefCell::default());
#[derive(Serialize, Deserialize, Debug)]
enum ApiCommand {
@ -49,6 +58,9 @@ enum ApiCommand {
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)>),
@ -125,42 +137,34 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
let command_is_executing = CommandIsExecuting::new();
let full_screen_ws = os_input.get_terminal_size_using_fd(0);
os_input.into_raw_mode(0);
let (send_screen_instructions, receive_screen_instructions): (
Sender<(ScreenInstruction, ErrorContext)>,
Receiver<(ScreenInstruction, ErrorContext)>,
) = channel();
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): (
Sender<(PtyInstruction, ErrorContext)>,
Receiver<(PtyInstruction, ErrorContext)>,
) = channel();
let (send_pty_instructions, receive_pty_instructions): ChannelWithContext<PtyInstruction> =
channel();
let mut send_pty_instructions =
SenderWithContext::new(err_ctx, SenderType::Sender(send_pty_instructions));
#[cfg(feature = "wasm-wip")]
use crate::wasm_vm::PluginInstruction;
#[cfg(feature = "wasm-wip")]
let (send_plugin_instructions, receive_plugin_instructions): (
Sender<(PluginInstruction, ErrorContext)>,
Receiver<(PluginInstruction, ErrorContext)>,
) = channel();
#[cfg(feature = "wasm-wip")]
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): (
SyncSender<(AppInstruction, ErrorContext)>,
Receiver<(AppInstruction, ErrorContext)>,
) = sync_channel(0);
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,
);
@ -180,17 +184,8 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
.name("pty".to_string())
.spawn({
let mut command_is_executing = command_is_executing.clone();
#[cfg(feature = "wasm-wip")]
let send_plugin_instructions = send_plugin_instructions.clone();
move || {
if let Some(layout) = maybe_layout {
#[cfg(feature = "wasm-wip")]
for plugin_path in layout.list_plugins() {
dbg!(send_plugin_instructions
.send(PluginInstruction::Load(plugin_path.clone())))
.unwrap();
}
pty_bus.spawn_terminals_for_layout(layout);
} else {
let pid = pty_bus.spawn_terminal(None);
@ -212,21 +207,21 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
let pid = pty_bus.spawn_terminal(file_to_open);
pty_bus
.send_screen_instructions
.send(ScreenInstruction::NewPane(pid))
.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(pid))
.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(pid))
.send(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid)))
.unwrap();
}
PtyInstruction::NewTab => {
@ -261,6 +256,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
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;
@ -268,6 +264,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
let mut screen = Screen::new(
receive_screen_instructions,
send_pty_instructions,
send_plugin_instructions,
send_app_instructions,
&full_screen_ws,
os_input,
@ -366,7 +363,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
screen
.get_active_tab_mut()
.unwrap()
.toggle_active_terminal_fullscreen();
.toggle_active_pane_fullscreen();
}
ScreenInstruction::NewTab(pane_id) => {
screen.new_tab(pane_id);
@ -388,97 +385,104 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
.unwrap(),
);
// Here be dragons! This is very much a work in progress, and isn't quite functional
// yet. It's being left out of the tests because is slows them down massively (by
// recompiling a WASM module for every single test). Stay tuned for more updates!
#[cfg(feature = "wasm-wip")]
active_threads.push(
thread::Builder::new()
.name("wasm".to_string())
.spawn(move || {
use crate::errors::PluginContext;
use crate::wasm_vm::{mosaic_imports, wasi_stdout};
use std::io;
use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value};
use wasmer_wasi::{Pipe, WasiState};
.spawn({
let mut send_pty_instructions = send_pty_instructions.clone();
let mut send_screen_instructions = send_screen_instructions.clone();
let store = Store::default();
move || {
let store = Store::default();
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)));
// FIXME: Clueless on how many of these lines I need...
// screen.send_app_instructions.update(err_ctx);
// screen.send_pty_instructions.update(err_ctx);
match event {
PluginInstruction::Load(path) => {
// FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that
let module = Module::from_file(&store, path).unwrap();
let mut plugin_id = 0;
let mut plugin_map = HashMap::new();
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();
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) => {
// FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that
let module = Module::from_file(&store, &path).unwrap();
let wasi = wasi_env.import_object(&module).unwrap();
let mosaic = mosaic_imports(&store, &wasi_env);
let instance = Instance::new(&module, &mosaic.chain_back(wasi)).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 start = instance.exports.get_function("_start").unwrap();
let handle_key = instance.exports.get_function("handle_key").unwrap();
let draw = instance.exports.get_function("draw").unwrap();
let wasi = wasi_env.import_object(&module).unwrap();
// This eventually calls the `.init()` method
start.call(&[]).unwrap();
let plugin_env = PluginEnv {
send_pty_instructions: send_pty_instructions.clone(),
wasi_env,
};
#[warn(clippy::never_loop)]
loop {
let (cols, rows) = (80, 24); //terminal::size()?;
draw.call(&[Value::I32(rows), Value::I32(cols)]).unwrap();
let mosaic = mosaic_imports(&store, &plugin_env);
let instance =
Instance::new(&module, &mosaic.chain_back(wasi)).unwrap();
// Needed because raw mode doesn't implicitly return to the start of the line
write!(
io::stdout(),
"{}\n\r",
wasi_stdout(&wasi_env)
.lines()
.collect::<Vec<_>>()
.join("\n\r")
).unwrap();
let start = instance.exports.get_function("_start").unwrap();
/* match event::read().unwrap() {
Event::Key(KeyEvent {
code: KeyCode::Char('q'),
..
}) => break,
Event::Key(e) => {
wasi_write_string(&wasi_env, serde_json::to_string(&e).unwrap());
handle_key.call(&[])?;
}
_ => (),
} */
break;
// 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;
}
debug_log_to_file("WASM module loaded and exited cleanly :)".to_string()).unwrap();
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();
}
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();
}
}
send_screen_instructions
.send(ScreenInstruction::Render)
.unwrap();
}
PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)),
PluginInstruction::Quit => break,
}
PluginInstruction::Quit => break,
i => panic!("Yo, dawg, nice job calling the wasm thread!\n {:?} is defo not implemented yet...", i),
}
}
}
).unwrap(),
})
.unwrap(),
);
// TODO: currently we don't push this into active_threads
@ -572,14 +576,14 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
AppInstruction::Exit => {
let _ = send_screen_instructions.send(ScreenInstruction::Quit);
let _ = send_pty_instructions.send(PtyInstruction::Quit);
#[cfg(feature = "wasm-wip")]
let _ = send_plugin_instructions.send(PluginInstruction::Quit);
break;
}
AppInstruction::Error(backtrace) => {
let _ = send_screen_instructions.send(ScreenInstruction::Quit);
let _ = send_pty_instructions.send(PtyInstruction::Quit);
#[cfg(feature = "wasm-wip")]
let _ = send_plugin_instructions.send(PluginInstruction::Quit);
os_input.unset_raw_mode(0);
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);

View File

@ -1,4 +1,4 @@
use crate::terminal_pane::PositionAndSize;
use crate::panes::PositionAndSize;
use nix::fcntl::{fcntl, FcntlArg, OFlag};
use nix::pty::{forkpty, Winsize};
use nix::sys::signal::{kill, Signal};
@ -75,6 +75,7 @@ fn handle_command_exit(mut child: Child) {
}
for signal in signals.pending() {
// FIXME: We need to handle more signals here!
match signal {
signal_hook::SIGINT => {
child.kill().unwrap();
@ -141,7 +142,7 @@ pub struct OsInputOutput {
pub trait OsApi: Send + Sync {
fn get_terminal_size_using_fd(&self, pid: RawFd) -> PositionAndSize;
fn set_terminal_size_using_fd(&mut self, pid: RawFd, cols: u16, rows: u16);
fn into_raw_mode(&mut self, pid: RawFd);
fn set_raw_mode(&mut self, pid: RawFd);
fn unset_raw_mode(&mut self, pid: RawFd);
fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> (RawFd, RawFd);
fn read_from_tty_stdout(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
@ -160,7 +161,7 @@ impl OsApi for OsInputOutput {
fn set_terminal_size_using_fd(&mut self, pid: RawFd, cols: u16, rows: u16) {
set_terminal_size_using_fd(pid, cols, rows);
}
fn into_raw_mode(&mut self, pid: RawFd) {
fn set_raw_mode(&mut self, pid: RawFd) {
into_raw_mode(pid);
}
fn unset_raw_mode(&mut self, pid: RawFd) {

View File

@ -1,7 +1,9 @@
mod plugin_pane;
mod scroll;
mod terminal_character;
mod terminal_pane;
pub use plugin_pane::*;
pub use scroll::*;
pub use terminal_character::*;
pub use terminal_pane::*;

167
src/panes/plugin_pane.rs Normal file
View File

@ -0,0 +1,167 @@
#![allow(clippy::clippy::if_same_then_else)]
use crate::{pty_bus::VteEvent, tab::Pane, wasm_vm::PluginInstruction, SenderWithContext};
use std::{sync::mpsc::channel, unimplemented};
use crate::panes::{PaneId, PositionAndSize};
pub struct PluginPane {
pub pid: u32,
pub should_render: bool,
pub position_and_size: PositionAndSize,
pub position_and_size_override: Option<PositionAndSize>,
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
}
impl PluginPane {
pub fn new(
pid: u32,
position_and_size: PositionAndSize,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
) -> Self {
Self {
pid,
should_render: true,
position_and_size,
position_and_size_override: None,
send_plugin_instructions,
}
}
}
impl Pane for PluginPane {
// FIXME: These position and size things should all be moved to default trait implementations,
// with something like a get_pos_and_sz() method underpinning all of them. Alternatively and
// preferably, just use an enum and not a trait object
fn x(&self) -> usize {
self.position_and_size_override
.unwrap_or(self.position_and_size)
.x
}
fn y(&self) -> usize {
self.position_and_size_override
.unwrap_or(self.position_and_size)
.y
}
fn rows(&self) -> usize {
self.position_and_size_override
.unwrap_or(self.position_and_size)
.rows
}
fn columns(&self) -> usize {
self.position_and_size_override
.unwrap_or(self.position_and_size)
.columns
}
fn reset_size_and_position_override(&mut self) {
self.position_and_size_override = None;
self.should_render = true;
}
fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) {
self.position_and_size = *position_and_size;
self.should_render = true;
}
// FIXME: This is obviously a bit outdated and needs the x and y moved into `size`
fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize) {
let position_and_size_override = PositionAndSize {
x,
y,
rows: size.rows,
columns: size.columns,
};
self.position_and_size_override = Some(position_and_size_override);
self.should_render = true;
}
fn handle_event(&mut self, _event: VteEvent) {
unimplemented!()
}
fn cursor_coordinates(&self) -> Option<(usize, usize)> {
None
}
fn adjust_input_to_terminal(&self, _input_bytes: Vec<u8>) -> Vec<u8> {
unimplemented!() // FIXME: Shouldn't need this implmented?
}
fn position_and_size_override(&self) -> Option<PositionAndSize> {
self.position_and_size_override
}
fn should_render(&self) -> bool {
self.should_render
}
fn set_should_render(&mut self, should_render: bool) {
self.should_render = should_render;
}
fn render(&mut self) -> Option<String> {
// if self.should_render {
if true {
// while checking should_render rather than rendering each pane every time
// is more performant, it causes some problems when the pane to the left should be
// rendered and has wide characters (eg. Chinese characters or emoji)
// as a (hopefully) temporary hack, we render all panes until we find a better solution
let (buf_tx, buf_rx) = channel();
self.send_plugin_instructions
.send(PluginInstruction::Draw(
buf_tx,
self.pid,
self.rows(),
self.columns(),
))
.unwrap();
self.should_render = false;
Some(buf_rx.recv().unwrap())
} else {
None
}
}
fn pid(&self) -> PaneId {
PaneId::Plugin(self.pid)
}
fn reduce_height_down(&mut self, count: usize) {
self.position_and_size.y += count;
self.position_and_size.rows -= count;
self.should_render = true;
}
fn increase_height_down(&mut self, count: usize) {
self.position_and_size.rows += count;
self.should_render = true;
}
fn increase_height_up(&mut self, count: usize) {
self.position_and_size.y -= count;
self.position_and_size.rows += count;
self.should_render = true;
}
fn reduce_height_up(&mut self, count: usize) {
self.position_and_size.rows -= count;
self.should_render = true;
}
fn reduce_width_right(&mut self, count: usize) {
self.position_and_size.x += count;
self.position_and_size.columns -= count;
self.should_render = true;
}
fn reduce_width_left(&mut self, count: usize) {
self.position_and_size.columns -= count;
self.should_render = true;
}
fn increase_width_left(&mut self, count: usize) {
self.position_and_size.x -= count;
self.position_and_size.columns += count;
self.should_render = true;
}
fn increase_width_right(&mut self, count: usize) {
self.position_and_size.columns += count;
self.should_render = true;
}
fn scroll_up(&mut self, _count: usize) {
unimplemented!()
}
fn scroll_down(&mut self, _count: usize) {
unimplemented!()
}
fn clear_scroll(&mut self) {
unimplemented!()
}
}

View File

@ -1,7 +1,10 @@
use std::collections::VecDeque;
use std::fmt::{self, Debug, Formatter};
use std::{
cmp::max,
fmt::{self, Debug, Formatter},
};
use crate::terminal_pane::terminal_character::{
use crate::panes::terminal_character::{
CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
};
@ -341,10 +344,8 @@ impl Scroll {
if lines_to_skip > 0 {
lines_to_skip -= 1;
} else {
for _ in line.len()..self.total_columns {
// pad line if needed
line.push(EMPTY_TERMINAL_CHARACTER);
}
// pad line if needed
line.resize(self.total_columns, EMPTY_TERMINAL_CHARACTER);
lines.push_front(line);
}
if lines.len() == self.lines_in_view {
@ -477,9 +478,14 @@ impl Scroll {
count
};
for _ in current_fragment.characters.len()..current_cursor_column_position + move_count {
current_fragment.characters.push(EMPTY_TERMINAL_CHARACTER);
}
current_fragment.characters.resize(
max(
current_fragment.characters.len(),
current_cursor_column_position + move_count,
),
EMPTY_TERMINAL_CHARACTER,
);
self.cursor_position.move_forward(move_count);
}
pub fn move_cursor_back(&mut self, count: usize) {
@ -645,9 +651,10 @@ impl Scroll {
.get_mut(current_line_wrap_position)
.expect("cursor out of bounds");
for _ in current_fragment.characters.len()..col {
current_fragment.characters.push(EMPTY_TERMINAL_CHARACTER);
}
current_fragment.characters.resize(
max(current_fragment.characters.len(), col),
EMPTY_TERMINAL_CHARACTER,
);
self.cursor_position.move_to_column(col);
}
pub fn move_cursor_to_column(&mut self, col: usize) {
@ -680,9 +687,7 @@ impl Scroll {
self.scroll_region = None;
}
fn scroll_region_absolute_indices(&mut self) -> Option<(usize, usize)> {
if self.scroll_region.is_none() {
return None;
};
self.scroll_region?;
if self.canonical_lines.len() > self.lines_in_view {
let absolute_top = self.canonical_lines.len() - 1 - self.lines_in_view;
let absolute_bottom = self.canonical_lines.len() - 1;

View File

@ -390,7 +390,7 @@ impl CharacterStyles {
}
}
if let Some(next_params) = ansi_params.get(params_used..) {
if next_params.len() > 0 {
if !next_params.is_empty() {
self.add_style_from_ansi_params(next_params);
}
}
@ -536,14 +536,9 @@ impl Display for CharacterStyles {
write!(f, "\u{1b}[2m")?;
}
AnsiCode::Reset => {
if let Some(bold) = self.bold {
if let Some(AnsiCode::Reset) = self.bold {
// we only reset dim if both dim and bold should be reset
match bold {
AnsiCode::Reset => {
write!(f, "\u{1b}[22m")?;
}
_ => {}
}
write!(f, "\u{1b}[22m")?;
}
}
_ => {}

View File

@ -5,12 +5,17 @@ use ::nix::pty::Winsize;
use ::std::os::unix::io::RawFd;
use ::vte::Perform;
use crate::terminal_pane::terminal_character::{CharacterStyles, NamedColor, TerminalCharacter};
use crate::terminal_pane::Scroll;
use crate::panes::terminal_character::{CharacterStyles, TerminalCharacter};
use crate::panes::Scroll;
use crate::utils::logging::debug_log_to_file;
use crate::VteEvent;
#[derive(Clone, Copy, Debug)]
#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)]
pub enum PaneId {
Terminal(RawFd),
Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct?
}
#[derive(Clone, Copy, Debug, Default)]
pub struct PositionAndSize {
pub x: usize,
pub y: usize,
@ -18,13 +23,12 @@ pub struct PositionAndSize {
pub columns: usize,
}
impl PositionAndSize {
pub fn from(winsize: Winsize) -> PositionAndSize {
impl From<Winsize> for PositionAndSize {
fn from(winsize: Winsize) -> PositionAndSize {
PositionAndSize {
columns: winsize.ws_col as usize,
rows: winsize.ws_row as usize,
x: winsize.ws_xpixel as usize,
y: winsize.ws_ypixel as usize,
..Default::default()
}
}
}
@ -58,30 +62,12 @@ impl Pane for TerminalPane {
self.reflow_lines();
self.mark_for_rerender();
}
fn change_size_p(&mut self, position_and_size: &PositionAndSize) {
self.position_and_size = *position_and_size;
fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) {
self.position_and_size.columns = position_and_size.columns;
self.position_and_size.rows = position_and_size.rows;
self.reflow_lines();
self.mark_for_rerender();
}
fn get_rows(&self) -> usize {
match &self.position_and_size_override.as_ref() {
Some(position_and_size_override) => position_and_size_override.rows,
None => self.position_and_size.rows as usize,
}
}
fn get_columns(&self) -> usize {
match &self.position_and_size_override.as_ref() {
Some(position_and_size_override) => position_and_size_override.columns,
None => self.position_and_size.columns as usize,
}
}
fn change_size(&mut self, ws: &PositionAndSize) {
self.position_and_size.columns = ws.columns;
self.position_and_size.rows = ws.rows;
self.reflow_lines();
self.mark_for_rerender();
}
fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize) {
let position_and_size_override = PositionAndSize {
x,
@ -179,8 +165,7 @@ impl Pane for TerminalPane {
fn set_should_render(&mut self, should_render: bool) {
self.should_render = should_render;
}
fn buffer_as_vte_output(&mut self) -> Option<String> {
// TODO: rename to render
fn render(&mut self) -> Option<String> {
// if self.should_render {
if true {
// while checking should_render rather than rendering each pane every time
@ -219,8 +204,8 @@ impl Pane for TerminalPane {
None
}
}
fn pid(&self) -> RawFd {
self.pid
fn pid(&self) -> PaneId {
PaneId::Terminal(self.pid)
}
fn reduce_height_down(&mut self, count: usize) {
self.position_and_size.y += count;
@ -281,15 +266,10 @@ impl Pane for TerminalPane {
}
impl TerminalPane {
pub fn new(pid: RawFd, ws: PositionAndSize, x: usize, y: usize) -> TerminalPane {
let scroll = Scroll::new(ws.columns, ws.rows);
pub fn new(pid: RawFd, position_and_size: PositionAndSize) -> TerminalPane {
let scroll = Scroll::new(position_and_size.columns, position_and_size.rows);
let pending_styles = CharacterStyles::new();
let position_and_size = PositionAndSize {
x,
y,
rows: ws.rows,
columns: ws.columns,
};
TerminalPane {
pid,
scroll,
@ -303,43 +283,6 @@ impl TerminalPane {
pub fn mark_for_rerender(&mut self) {
self.should_render = true;
}
pub fn handle_event(&mut self, event: VteEvent) {
match event {
VteEvent::Print(c) => {
self.print(c);
self.mark_for_rerender();
}
VteEvent::Execute(byte) => {
self.execute(byte);
}
VteEvent::Hook(params, intermediates, ignore, c) => {
self.hook(&params, &intermediates, ignore, c);
}
VteEvent::Put(byte) => {
self.put(byte);
}
VteEvent::Unhook => {
self.unhook();
}
VteEvent::OscDispatch(params, bell_terminated) => {
let params: Vec<&[u8]> = params.iter().map(|p| &p[..]).collect();
self.osc_dispatch(&params[..], bell_terminated);
}
VteEvent::CsiDispatch(params, intermediates, ignore, c) => {
self.csi_dispatch(&params, &intermediates, ignore, c);
}
VteEvent::EscDispatch(intermediates, ignore, byte) => {
self.esc_dispatch(&intermediates, ignore, byte);
}
}
}
// TODO: merge these two methods
pub fn change_size(&mut self, ws: &PositionAndSize) {
self.position_and_size.columns = ws.columns;
self.position_and_size.rows = ws.rows;
self.reflow_lines();
self.mark_for_rerender();
}
pub fn get_x(&self) -> usize {
match self.position_and_size_override {
Some(position_and_size_override) => position_and_size_override.x,
@ -369,61 +312,15 @@ impl TerminalPane {
let columns = self.get_columns();
self.scroll.change_size(columns, rows);
}
pub fn buffer_as_vte_output(&mut self) -> Option<String> {
// TODO: rename to render
// if self.should_render {
if true {
// while checking should_render rather than rendering each pane every time
// is more performant, it causes some problems when the pane to the left should be
// rendered and has wide characters (eg. Chinese characters or emoji)
// as a (hopefully) temporary hack, we render all panes until we find a better solution
let mut vte_output = String::new();
let buffer_lines = &self.read_buffer_as_lines();
let display_cols = self.get_columns();
let mut character_styles = CharacterStyles::new();
for (row, line) in buffer_lines.iter().enumerate() {
let x = self.get_x();
let y = self.get_y();
vte_output = format!("{}\u{1b}[{};{}H\u{1b}[m", vte_output, y + row + 1, x + 1); // goto row/col and reset styles
for (col, t_character) in line.iter().enumerate() {
if col < display_cols {
// in some cases (eg. while resizing) some characters will spill over
// before they are corrected by the shell (for the prompt) or by reflowing
// lines
if let Some(new_styles) =
character_styles.update_and_return_diff(&t_character.styles)
{
// the terminal keeps the previous styles as long as we're in the same
// line, so we only want to update the new styles here (this also
// includes resetting previous styles as needed)
vte_output = format!("{}{}", vte_output, new_styles);
}
vte_output.push(t_character.character);
}
}
character_styles.clear();
}
self.mark_for_rerender();
Some(vte_output)
} else {
None
}
}
pub fn read_buffer_as_lines(&self) -> Vec<Vec<TerminalCharacter>> {
self.scroll.as_character_lines()
}
#[cfg(test)]
pub fn cursor_coordinates(&self) -> Option<(usize, usize)> {
// (x, y)
self.scroll.cursor_coordinates_on_screen()
}
pub fn scroll_up(&mut self, count: usize) {
self.scroll.move_viewport_up(count);
self.mark_for_rerender();
}
pub fn scroll_down(&mut self, count: usize) {
self.scroll.move_viewport_down(count);
self.mark_for_rerender();
}
pub fn rotate_scroll_region_up(&mut self, count: usize) {
self.scroll.rotate_scroll_region_up(count);
self.mark_for_rerender();
@ -432,22 +329,6 @@ impl TerminalPane {
self.scroll.rotate_scroll_region_down(count);
self.mark_for_rerender();
}
pub fn clear_scroll(&mut self) {
self.scroll.reset_viewport();
self.mark_for_rerender();
}
pub fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize) {
let position_and_size_override = PositionAndSize {
x,
y,
rows: size.rows,
columns: size.columns,
};
self.position_and_size_override = Some(position_and_size_override);
self.reflow_lines();
self.mark_for_rerender();
}
fn add_newline(&mut self) {
self.scroll.add_canonical_line();
// self.reset_all_ansi_codes(); // TODO: find out if we should be resetting here or not
@ -558,12 +439,10 @@ impl vte::Perform for TerminalPane {
} else {
(params[0] as usize - 1, params[0] as usize)
}
} else if params[0] == 0 {
(0, params[1] as usize - 1)
} else {
if params[0] == 0 {
(0, params[1] as usize - 1)
} else {
(params[0] as usize - 1, params[1] as usize - 1)
}
(params[0] as usize - 1, params[1] as usize - 1)
};
self.scroll.move_cursor_to(row, col);
} else if c == 'A' {
@ -723,11 +602,8 @@ impl vte::Perform for TerminalPane {
}
fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, byte: u8) {
match (byte, intermediates.get(0)) {
(b'M', None) => {
self.scroll.move_cursor_up_in_scroll_region(1);
}
_ => {}
if let (b'M', None) = (byte, intermediates.get(0)) {
self.scroll.move_cursor_up_in_scroll_region(1);
}
}
}

View File

@ -9,10 +9,13 @@ use ::std::time::{Duration, Instant};
use ::vte;
use std::path::PathBuf;
use crate::errors::{ContextType, ErrorContext};
use crate::layout::Layout;
use crate::os_input_output::OsApi;
use crate::utils::logging::debug_to_file;
use crate::{
errors::{ContextType, ErrorContext},
panes::PaneId,
};
use crate::{layout::Layout, wasm_vm::PluginInstruction};
use crate::{ScreenInstruction, SenderWithContext, OPENCALLS};
pub struct ReadFromPid {
@ -148,13 +151,14 @@ pub enum PtyInstruction {
SpawnTerminalVertically(Option<PathBuf>),
SpawnTerminalHorizontally(Option<PathBuf>),
NewTab,
ClosePane(RawFd),
CloseTab(Vec<RawFd>),
ClosePane(PaneId),
CloseTab(Vec<PaneId>),
Quit,
}
pub struct PtyBus {
pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
pub receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>,
pub id_to_child_pid: HashMap<RawFd, RawFd>,
os_input: Box<dyn OsApi>,
@ -231,7 +235,7 @@ fn stream_terminal_bytes(
// 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
send_screen_instructions
.send(ScreenInstruction::ClosePane(pid))
.send(ScreenInstruction::ClosePane(PaneId::Terminal(pid)))
.unwrap();
}
});
@ -241,11 +245,13 @@ impl PtyBus {
pub fn new(
receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>,
send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
os_input: Box<dyn OsApi>,
debug_to_file: bool,
) -> Self {
PtyBus {
send_screen_instructions,
send_plugin_instructions,
receive_pty_instructions,
os_input,
id_to_child_pid: HashMap::new(),
@ -265,7 +271,7 @@ impl PtyBus {
pid_primary
}
pub fn spawn_terminals_for_layout(&mut self, layout: Layout) {
let total_panes = layout.total_panes();
let total_panes = layout.total_terminal_panes();
let mut new_pane_pids = vec![];
for _ in 0..total_panes {
let (pid_primary, pid_secondary): (RawFd, RawFd) = self.os_input.spawn_terminal(None);
@ -287,11 +293,19 @@ impl PtyBus {
);
}
}
pub fn close_pane(&mut self, id: RawFd) {
let child_pid = self.id_to_child_pid.get(&id).unwrap();
self.os_input.kill(*child_pid).unwrap();
pub fn close_pane(&mut self, id: PaneId) {
match id {
PaneId::Terminal(id) => {
let child_pid = self.id_to_child_pid.get(&id).unwrap();
self.os_input.kill(*child_pid).unwrap();
}
PaneId::Plugin(pid) => self
.send_plugin_instructions
.send(PluginInstruction::Unload(pid))
.unwrap(),
}
}
pub fn close_tab(&mut self, ids: Vec<RawFd>) {
ids.iter().for_each(|id| self.close_pane(*id));
pub fn close_tab(&mut self, ids: Vec<PaneId>) {
ids.iter().for_each(|&id| self.close_pane(id));
}
}

View File

@ -2,12 +2,12 @@ use std::collections::BTreeMap;
use std::os::unix::io::RawFd;
use std::sync::mpsc::Receiver;
use crate::errors::ErrorContext;
use crate::layout::Layout;
use crate::os_input_output::OsApi;
use crate::panes::PositionAndSize;
use crate::pty_bus::{PtyInstruction, VteEvent};
use crate::tab::Tab;
use crate::terminal_pane::PositionAndSize;
use crate::{errors::ErrorContext, wasm_vm::PluginInstruction};
use crate::{layout::Layout, panes::PaneId};
use crate::{AppInstruction, SenderWithContext};
/*
@ -23,9 +23,9 @@ use crate::{AppInstruction, SenderWithContext};
pub enum ScreenInstruction {
Pty(RawFd, VteEvent),
Render,
NewPane(RawFd),
HorizontalSplit(RawFd),
VerticalSplit(RawFd),
NewPane(PaneId),
HorizontalSplit(PaneId),
VerticalSplit(PaneId),
WriteCharacter(Vec<u8>),
ResizeLeft,
ResizeRight,
@ -42,7 +42,7 @@ pub enum ScreenInstruction {
ClearScroll,
CloseFocusedPane,
ToggleActiveTerminalFullscreen,
ClosePane(RawFd),
ClosePane(PaneId),
ApplyLayout((Layout, Vec<RawFd>)),
NewTab(RawFd),
SwitchTabNext,
@ -55,6 +55,7 @@ pub struct Screen {
max_panes: Option<usize>,
tabs: BTreeMap<usize, Tab>,
pub send_pty_instructions: SenderWithContext<PtyInstruction>,
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
pub send_app_instructions: SenderWithContext<AppInstruction>,
full_screen_ws: PositionAndSize,
active_tab_index: Option<usize>,
@ -65,6 +66,7 @@ impl Screen {
pub fn new(
receive_screen_instructions: Receiver<(ScreenInstruction, ErrorContext)>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
full_screen_ws: &PositionAndSize,
os_api: Box<dyn OsApi>,
@ -74,6 +76,7 @@ impl Screen {
receiver: receive_screen_instructions,
max_panes,
send_pty_instructions,
send_plugin_instructions,
send_app_instructions,
full_screen_ws: *full_screen_ws,
active_tab_index: None,
@ -88,9 +91,10 @@ impl Screen {
&self.full_screen_ws,
self.os_api.clone(),
self.send_pty_instructions.clone(),
self.send_plugin_instructions.clone(),
self.send_app_instructions.clone(),
self.max_panes,
Some(pane_id),
Some(PaneId::Terminal(pane_id)),
);
self.active_tab_index = Some(tab_index);
self.tabs.insert(tab_index, tab);
@ -119,7 +123,7 @@ impl Screen {
let active_tab_id = self.get_active_tab().unwrap().index;
let tab_ids: Vec<usize> = self.tabs.keys().copied().collect();
let first_tab = tab_ids.get(0).unwrap();
let last_tab = tab_ids.get(tab_ids.len() - 1).unwrap();
let last_tab = tab_ids.last().unwrap();
let active_tab_id_position = tab_ids.iter().position(|id| id == &active_tab_id).unwrap();
if active_tab_id == *first_tab {
@ -135,18 +139,18 @@ impl Screen {
self.switch_tab_prev();
}
let mut active_tab = self.tabs.remove(&active_tab_index).unwrap();
let pane_ids = active_tab.get_terminal_pane_ids();
let pane_ids = active_tab.get_pane_ids();
self.send_pty_instructions
.send(PtyInstruction::CloseTab(pane_ids))
.unwrap();
if self.tabs.len() == 0 {
if self.tabs.is_empty() {
self.active_tab_index = None;
self.render();
}
}
pub fn render(&mut self) {
if let Some(active_tab) = self.get_active_tab_mut() {
if active_tab.get_active_terminal().is_some() {
if active_tab.get_active_pane().is_some() {
active_tab.render();
} else {
self.close_tab();
@ -181,6 +185,7 @@ impl Screen {
&self.full_screen_ws,
self.os_api.clone(),
self.send_pty_instructions.clone(),
self.send_plugin_instructions.clone(),
self.send_app_instructions.clone(),
self.max_panes,
None,

1226
src/tab.rs

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
use crate::terminal_pane::PositionAndSize;
use crate::panes::PositionAndSize;
use std::collections::{HashMap, VecDeque};
use std::io::Write;
use std::os::unix::io::RawFd;
@ -130,7 +130,7 @@ impl OsApi for FakeInputOutput {
.unwrap()
.push(IoEvent::SetTerminalSizeUsingFd(pid, cols, rows));
}
fn into_raw_mode(&mut self, pid: RawFd) {
fn set_raw_mode(&mut self, pid: RawFd) {
self.io_events
.lock()
.unwrap()

View File

@ -14,4 +14,4 @@ parts:
Percent: 80
- direction: Vertical
split_size:
Percent: 20
Percent: 20

View File

@ -1,4 +1,4 @@
use crate::terminal_pane::PositionAndSize;
use crate::panes::PositionAndSize;
use ::insta::assert_snapshot;
use crate::tests::fakes::FakeInputOutput;

View File

@ -1,4 +1,4 @@
use crate::terminal_pane::PositionAndSize;
use crate::panes::PositionAndSize;
use ::insta::assert_snapshot;
use crate::tests::fakes::FakeInputOutput;

View File

@ -1,7 +1,7 @@
use ::insta::assert_snapshot;
use ::std::collections::HashMap;
use crate::terminal_pane::PositionAndSize;
use crate::panes::PositionAndSize;
use crate::tests::fakes::FakeInputOutput;
use crate::tests::possible_tty_inputs::Bytes;
use crate::tests::utils::get_output_frame_snapshots;

View File

@ -1,7 +1,7 @@
use insta::assert_snapshot;
use std::path::PathBuf;
use crate::terminal_pane::PositionAndSize;
use crate::panes::PositionAndSize;
use crate::tests::fakes::FakeInputOutput;
use crate::tests::utils::commands::{COMMAND_TOGGLE, QUIT};
use crate::tests::utils::get_output_frame_snapshots;

View File

@ -1,6 +1,6 @@
use ::insta::assert_snapshot;
use crate::terminal_pane::PositionAndSize;
use crate::panes::PositionAndSize;
use crate::tests::fakes::FakeInputOutput;
use crate::tests::utils::get_output_frame_snapshots;
use crate::{start, CliArgs};

View File

@ -1,6 +1,6 @@
use ::insta::assert_snapshot;
use crate::terminal_pane::PositionAndSize;
use crate::panes::PositionAndSize;
use crate::tests::fakes::FakeInputOutput;
use crate::tests::utils::get_output_frame_snapshots;
use crate::{start, CliArgs};

View File

@ -1,6 +1,6 @@
use ::insta::assert_snapshot;
use crate::terminal_pane::PositionAndSize;
use crate::panes::PositionAndSize;
use crate::tests::fakes::FakeInputOutput;
use crate::tests::utils::get_output_frame_snapshots;
use crate::{start, CliArgs};

View File

@ -1,6 +1,6 @@
use ::insta::assert_snapshot;
use crate::terminal_pane::PositionAndSize;
use crate::panes::PositionAndSize;
use crate::tests::fakes::FakeInputOutput;
use crate::tests::utils::get_output_frame_snapshots;
use crate::{start, CliArgs};

View File

@ -1,6 +1,6 @@
use insta::assert_snapshot;
use crate::terminal_pane::PositionAndSize;
use crate::panes::PositionAndSize;
use crate::tests::fakes::FakeInputOutput;
use crate::tests::utils::get_output_frame_snapshots;
use crate::{start, CliArgs};

View File

@ -1,6 +1,6 @@
use ::insta::assert_snapshot;
use crate::terminal_pane::PositionAndSize;
use crate::panes::PositionAndSize;
use crate::tests::fakes::FakeInputOutput;
use crate::tests::utils::get_output_frame_snapshots;
use crate::{start, CliArgs};

View File

@ -1,6 +1,6 @@
use ::insta::assert_snapshot;
use crate::terminal_pane::PositionAndSize;
use crate::panes::PositionAndSize;
use crate::tests::fakes::FakeInputOutput;
use crate::tests::utils::get_output_frame_snapshots;
use crate::{start, CliArgs};

View File

@ -1,6 +1,6 @@
use ::insta::assert_snapshot;
use crate::terminal_pane::PositionAndSize;
use crate::panes::PositionAndSize;
use crate::tests::fakes::FakeInputOutput;
use crate::tests::utils::get_output_frame_snapshots;
use crate::{start, CliArgs};

View File

@ -2,8 +2,8 @@ use insta::assert_snapshot;
use crate::tests::fakes::FakeInputOutput;
use crate::tests::utils::get_output_frame_snapshots;
use crate::{panes::PositionAndSize, tests::utils::commands::CLOSE_FOCUSED_PANE};
use crate::{start, CliArgs};
use crate::{terminal_pane::PositionAndSize, tests::utils::commands::CLOSE_FOCUSED_PANE};
use crate::tests::utils::commands::{
CLOSE_TAB, COMMAND_TOGGLE, NEW_TAB, QUIT, SPLIT_HORIZONTALLY, SWITCH_NEXT_TAB, SWITCH_PREV_TAB,

View File

@ -1,6 +1,6 @@
use insta::assert_snapshot;
use crate::terminal_pane::PositionAndSize;
use crate::panes::PositionAndSize;
use crate::tests::fakes::FakeInputOutput;
use crate::tests::utils::get_output_frame_snapshots;
use crate::{start, CliArgs};

View File

@ -1,5 +1,5 @@
use crate::terminal_pane::PositionAndSize;
use crate::terminal_pane::TerminalPane;
use crate::panes::PositionAndSize;
use crate::panes::TerminalPane;
pub fn get_output_frame_snapshots(
output_frames: &[Vec<u8>],
@ -7,9 +7,7 @@ pub fn get_output_frame_snapshots(
) -> Vec<String> {
let mut vte_parser = vte::Parser::new();
let main_pid = 0;
let x = 0;
let y = 0;
let mut terminal_output = TerminalPane::new(main_pid, *win_size, x, y);
let mut terminal_output = TerminalPane::new(main_pid, *win_size);
let mut snapshots = vec![];
for frame in output_frames.iter() {

View File

@ -1,2 +1,3 @@
pub mod consts;
pub mod logging;
pub mod shared;

19
src/utils/shared.rs Normal file
View File

@ -0,0 +1,19 @@
use std::{iter, str::from_utf8};
use strip_ansi_escapes::strip;
pub fn ansi_len(s: &str) -> usize {
from_utf8(&strip(s.as_bytes()).unwrap())
.unwrap()
.chars()
.count()
}
pub fn pad_to_size(s: &str, rows: usize, columns: usize) -> String {
s.lines()
.map(|l| [l, &str::repeat(" ", columns - ansi_len(l))].concat())
.chain(iter::repeat(str::repeat(" ", columns)))
.take(rows)
.collect::<Vec<_>>()
.join("\n\r")
}

View File

@ -1,39 +1,44 @@
use std::{
path::PathBuf,
process::{Command, Stdio},
};
use wasmer::{imports, Function, ImportObject, Store};
use std::{path::PathBuf, sync::mpsc::Sender};
use wasmer::{imports, Function, ImportObject, Store, WasmerEnv};
use wasmer_wasi::WasiEnv;
use crate::{pty_bus::PtyInstruction, SenderWithContext};
#[derive(Clone, Debug)]
pub enum PluginInstruction {
Load(PathBuf),
Load(Sender<u32>, PathBuf),
Draw(Sender<String>, u32, usize, usize), // String buffer, plugin id, rows, cols
Input(u32, Vec<u8>), // plugin id, input bytes
Unload(u32),
Quit,
}
// Plugin API -----------------------------------------------------------------
#[derive(WasmerEnv, Clone)]
pub struct PluginEnv {
pub send_pty_instructions: SenderWithContext<PtyInstruction>, // FIXME: This should be a big bundle of all of the channels
pub wasi_env: WasiEnv,
}
pub fn mosaic_imports(store: &Store, wasi_env: &WasiEnv) -> ImportObject {
// Plugin API ---------------------------------------------------------------------------------------------------------
pub fn mosaic_imports(store: &Store, plugin_env: &PluginEnv) -> ImportObject {
imports! {
"mosaic" => {
"host_open_file" => Function::new_native_with_env(store, wasi_env.clone(), host_open_file)
"host_open_file" => Function::new_native_with_env(store, plugin_env.clone(), host_open_file)
}
}
}
fn host_open_file(wasi_env: &WasiEnv) {
Command::new("xdg-open")
.arg(format!(
"./{}",
wasi_stdout(wasi_env).lines().next().unwrap()
))
.stderr(Stdio::null())
.spawn()
// FIXME: Bundle up all of the channels! Pair that with WasiEnv?
fn host_open_file(plugin_env: &PluginEnv) {
let path = PathBuf::from(wasi_stdout(&plugin_env.wasi_env).lines().next().unwrap());
plugin_env
.send_pty_instructions
.send(PtyInstruction::SpawnTerminal(Some(path)))
.unwrap();
}
// Helper Functions -----------------------------------------------------------
// Helper Functions ---------------------------------------------------------------------------------------------------
// FIXME: Unwrap city
pub fn wasi_stdout(wasi_env: &WasiEnv) -> String {
@ -44,7 +49,7 @@ pub fn wasi_stdout(wasi_env: &WasiEnv) -> String {
buf
}
pub fn _wasi_write_string(wasi_env: &WasiEnv, buf: &str) {
pub fn wasi_write_string(wasi_env: &WasiEnv, buf: &str) {
let mut state = wasi_env.state();
let wasi_file = state.fs.stdin_mut().unwrap().as_mut().unwrap();
writeln!(wasi_file, "{}\r", buf).unwrap();

BIN
strider.wasm Executable file → Normal file

Binary file not shown.