mirror of
https://github.com/zellij-org/zellij.git
synced 2024-12-25 10:15:09 +03:00
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:
commit
7efc435bda
101
Cargo.lock
generated
101
Cargo.lock
generated
@ -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",
|
||||
|
18
Cargo.toml
18
Cargo.toml
@ -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
|
||||
|
4
build.rs
4
build.rs
@ -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!(
|
||||
|
@ -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
|
||||
|
@ -1,3 +1,4 @@
|
||||
#![allow(clippy::mutex_atomic)]
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
236
src/main.rs
236
src/main.rs
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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
167
src/panes/plugin_pane.rs
Normal 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!()
|
||||
}
|
||||
}
|
@ -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;
|
@ -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")?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
@ -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(¶ms, &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(¶ms[..], bell_terminated);
|
||||
}
|
||||
VteEvent::CsiDispatch(params, intermediates, ignore, c) => {
|
||||
self.csi_dispatch(¶ms, &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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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
1226
src/tab.rs
File diff suppressed because it is too large
Load Diff
@ -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()
|
||||
|
@ -14,4 +14,4 @@ parts:
|
||||
Percent: 80
|
||||
- direction: Vertical
|
||||
split_size:
|
||||
Percent: 20
|
||||
Percent: 20
|
@ -1,4 +1,4 @@
|
||||
use crate::terminal_pane::PositionAndSize;
|
||||
use crate::panes::PositionAndSize;
|
||||
use ::insta::assert_snapshot;
|
||||
|
||||
use crate::tests::fakes::FakeInputOutput;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::terminal_pane::PositionAndSize;
|
||||
use crate::panes::PositionAndSize;
|
||||
use ::insta::assert_snapshot;
|
||||
|
||||
use crate::tests::fakes::FakeInputOutput;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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};
|
||||
|
@ -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};
|
||||
|
@ -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};
|
||||
|
@ -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};
|
||||
|
@ -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};
|
||||
|
@ -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};
|
||||
|
@ -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};
|
||||
|
@ -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};
|
||||
|
@ -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,
|
||||
|
@ -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};
|
||||
|
@ -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() {
|
||||
|
@ -1,2 +1,3 @@
|
||||
pub mod consts;
|
||||
pub mod logging;
|
||||
pub mod shared;
|
||||
|
19
src/utils/shared.rs
Normal file
19
src/utils/shared.rs
Normal 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")
|
||||
}
|
@ -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
BIN
strider.wasm
Executable file → Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user