feat(plugins): update and render plugins asynchronously (#2410)
* working-ish minus a few race conditions * relax atomicity * various refactoringz * remove commented out code * clarify some stuffs * refactor(plugins): move PluginMap and friends to a different file * refactor(plugins): move zellij_exports and friends to a different file * style(fmt): rustfmt * fix(e2e): adjust tests for flakiness async loading
This commit is contained in:
parent
a29c653385
commit
1289643f89
39 changed files with 936 additions and 662 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
|
@ -30,9 +30,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.18"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
@ -2242,9 +2242,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.5.6"
|
version = "1.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
|
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
|
@ -2253,9 +2253,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.26"
|
version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64"
|
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "region"
|
name = "region"
|
||||||
|
|
@ -3957,6 +3957,7 @@ dependencies = [
|
||||||
"miette 3.3.0",
|
"miette 3.3.0",
|
||||||
"names",
|
"names",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
|
"regex",
|
||||||
"ssh2",
|
"ssh2",
|
||||||
"suggest",
|
"suggest",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ suggest = "0.4"
|
||||||
insta = { version = "1.6.0", features = ["backtrace"] }
|
insta = { version = "1.6.0", features = ["backtrace"] }
|
||||||
ssh2 = "0.9.1"
|
ssh2 = "0.9.1"
|
||||||
rand = "0.8.0"
|
rand = "0.8.0"
|
||||||
|
regex = "1.8.1"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use ::insta::assert_snapshot;
|
||||||
use zellij_utils::{pane_size::Size, position::Position};
|
use zellij_utils::{pane_size::Size, position::Position};
|
||||||
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
@ -74,6 +75,26 @@ pub fn sgr_mouse_report(position: Position, button: u8) -> Vec<u8> {
|
||||||
.to_vec()
|
.to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// what we do here is adjust snapshots for various race conditions that should hopefully be
|
||||||
|
// temporary until we can fix them - when adding stuff here, please add a detailed comment
|
||||||
|
// explaining the race condition and what needs to be done to solve it
|
||||||
|
fn account_for_races_in_snapshot(snapshot: String) -> String {
|
||||||
|
// these replacements need to be done because plugins set themselves as "unselectable" at runtime
|
||||||
|
// when they are loaded - since they are loaded asynchronously, sometimes the "BASE" indication
|
||||||
|
// (which should only happen if there's more than one selectable pane) is rendered and
|
||||||
|
// sometimes it isn't - this removes it entirely
|
||||||
|
//
|
||||||
|
// to fix this, we should set plugins as unselectable in the layout (before they are loaded),
|
||||||
|
// once that happens, we should be able to remove this hack (and adjust the snapshots for the
|
||||||
|
// trailing spaces that we had to get rid of here)
|
||||||
|
let base_replace = Regex::new(r" BASE \s*\n").unwrap();
|
||||||
|
let eol_arrow_replace = Regex::new(r"\s*\n").unwrap();
|
||||||
|
let snapshot = base_replace.replace_all(&snapshot, "\n").to_string();
|
||||||
|
let snapshot = eol_arrow_replace.replace_all(&snapshot, "\n").to_string();
|
||||||
|
|
||||||
|
snapshot
|
||||||
|
}
|
||||||
|
|
||||||
// All the E2E tests are marked as "ignored" so that they can be run separately from the normal
|
// All the E2E tests are marked as "ignored" so that they can be run separately from the normal
|
||||||
// tests
|
// tests
|
||||||
|
|
||||||
|
|
@ -105,6 +126,8 @@ pub fn starts_with_one_terminal() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -152,6 +175,7 @@ pub fn split_terminals_vertically() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,6 +219,7 @@ pub fn cannot_split_terminals_vertically_when_active_terminal_is_too_small() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -272,6 +297,7 @@ pub fn scrolling_inside_a_pane() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -332,6 +358,7 @@ pub fn toggle_pane_fullscreen() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -396,6 +423,7 @@ pub fn open_new_tab() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -536,6 +564,7 @@ pub fn close_pane() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -680,6 +709,7 @@ pub fn typing_exit_closes_pane() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -742,6 +772,7 @@ pub fn resize_pane() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -801,6 +832,7 @@ pub fn lock_mode() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -864,6 +896,7 @@ pub fn resize_terminal_window() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -949,6 +982,7 @@ pub fn detach_and_attach_session() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -984,6 +1018,7 @@ pub fn status_bar_loads_custom_keybindings() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1043,6 +1078,7 @@ fn focus_pane_with_mouse() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1118,6 +1154,7 @@ pub fn scrolling_inside_a_pane_with_mouse() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1164,6 +1201,7 @@ pub fn start_without_pane_frames() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1308,6 +1346,8 @@ pub fn mirrored_sessions() {
|
||||||
break (first_runner_snapshot, second_runner_snapshot);
|
break (first_runner_snapshot, second_runner_snapshot);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let first_runner_snapshot = account_for_races_in_snapshot(first_runner_snapshot);
|
||||||
|
let second_runner_snapshot = account_for_races_in_snapshot(second_runner_snapshot);
|
||||||
assert_snapshot!(first_runner_snapshot);
|
assert_snapshot!(first_runner_snapshot);
|
||||||
assert_snapshot!(second_runner_snapshot);
|
assert_snapshot!(second_runner_snapshot);
|
||||||
}
|
}
|
||||||
|
|
@ -1396,6 +1436,8 @@ pub fn multiple_users_in_same_pane_and_tab() {
|
||||||
break (first_runner_snapshot, second_runner_snapshot);
|
break (first_runner_snapshot, second_runner_snapshot);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let first_runner_snapshot = account_for_races_in_snapshot(first_runner_snapshot);
|
||||||
|
let second_runner_snapshot = account_for_races_in_snapshot(second_runner_snapshot);
|
||||||
assert_snapshot!(first_runner_snapshot);
|
assert_snapshot!(first_runner_snapshot);
|
||||||
assert_snapshot!(second_runner_snapshot);
|
assert_snapshot!(second_runner_snapshot);
|
||||||
}
|
}
|
||||||
|
|
@ -1486,6 +1528,8 @@ pub fn multiple_users_in_different_panes_and_same_tab() {
|
||||||
break (first_runner_snapshot, second_runner_snapshot);
|
break (first_runner_snapshot, second_runner_snapshot);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let first_runner_snapshot = account_for_races_in_snapshot(first_runner_snapshot);
|
||||||
|
let second_runner_snapshot = account_for_races_in_snapshot(second_runner_snapshot);
|
||||||
assert_snapshot!(first_runner_snapshot);
|
assert_snapshot!(first_runner_snapshot);
|
||||||
assert_snapshot!(second_runner_snapshot);
|
assert_snapshot!(second_runner_snapshot);
|
||||||
}
|
}
|
||||||
|
|
@ -1581,6 +1625,8 @@ pub fn multiple_users_in_different_tabs() {
|
||||||
break (first_runner_snapshot, second_runner_snapshot);
|
break (first_runner_snapshot, second_runner_snapshot);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let first_runner_snapshot = account_for_races_in_snapshot(first_runner_snapshot);
|
||||||
|
let second_runner_snapshot = account_for_races_in_snapshot(second_runner_snapshot);
|
||||||
assert_snapshot!(first_runner_snapshot);
|
assert_snapshot!(first_runner_snapshot);
|
||||||
assert_snapshot!(second_runner_snapshot);
|
assert_snapshot!(second_runner_snapshot);
|
||||||
}
|
}
|
||||||
|
|
@ -1637,6 +1683,7 @@ pub fn bracketed_paste() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1684,6 +1731,7 @@ pub fn toggle_floating_panes() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1731,6 +1779,7 @@ pub fn tmux_mode() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1829,6 +1878,7 @@ pub fn undo_rename_tab() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1878,6 +1928,7 @@ pub fn undo_rename_pane() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1987,5 +2038,6 @@ pub fn send_command_through_the_cli() {
|
||||||
break last_snapshot;
|
break last_snapshot;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 1640
|
assertion_line: 1676
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 538
|
assertion_line: 557
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 952
|
assertion_line: 975
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
@ -25,5 +25,5 @@ expression: last_snapshot
|
||||||
│ ││ │
|
│ ││ │
|
||||||
│ ││ │
|
│ ││ │
|
||||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT BASE
|
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 1046
|
assertion_line: 1071
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
@ -25,5 +25,5 @@ expression: last_snapshot
|
||||||
│ ││ │
|
│ ││ │
|
||||||
│ ││ │
|
│ ││ │
|
||||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT BASE
|
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 804
|
assertion_line: 819
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 1312
|
assertion_line: 1341
|
||||||
expression: second_runner_snapshot
|
expression: second_runner_snapshot
|
||||||
---
|
---
|
||||||
Zellij (mirrored_sessions) Tab #1 Tab #2
|
Zellij (mirrored_sessions) Tab #1 Tab #2
|
||||||
|
|
@ -25,5 +25,5 @@ expression: second_runner_snapshot
|
||||||
│ ││ │
|
│ ││ │
|
||||||
│ ││ │
|
│ ││ │
|
||||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT BASE
|
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||||
<←→> Move focus / <n> New / <x> Close / <r> Rename / <s> Sync / <TAB> Toggle / <ENTER> Select pane
|
<←→> Move focus / <n> New / <x> Close / <r> Rename / <s> Sync / <TAB> Toggle / <ENTER> Select pane
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 1311
|
assertion_line: 1340
|
||||||
expression: first_runner_snapshot
|
expression: first_runner_snapshot
|
||||||
---
|
---
|
||||||
Zellij (mirrored_sessions) Tab #1 Tab #2
|
Zellij (mirrored_sessions) Tab #1 Tab #2
|
||||||
|
|
@ -25,5 +25,5 @@ expression: first_runner_snapshot
|
||||||
│ ││ │
|
│ ││ │
|
||||||
│ ││ │
|
│ ││ │
|
||||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT BASE
|
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 1490
|
assertion_line: 1523
|
||||||
expression: second_runner_snapshot
|
expression: second_runner_snapshot
|
||||||
---
|
---
|
||||||
Zellij (multiple_users_in_same_pane_and_tab) Tab #1 [ ]
|
Zellij (multiple_users_in_same_pane_and_tab) Tab #1 [ ]
|
||||||
|
|
@ -25,5 +25,5 @@ expression: second_runner_snapshot
|
||||||
│ ││ │
|
│ ││ │
|
||||||
│ ││ │
|
│ ││ │
|
||||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT BASE
|
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 1489
|
assertion_line: 1522
|
||||||
expression: first_runner_snapshot
|
expression: first_runner_snapshot
|
||||||
---
|
---
|
||||||
Zellij (multiple_users_in_same_pane_and_tab) Tab #1 [ ]
|
Zellij (multiple_users_in_same_pane_and_tab) Tab #1 [ ]
|
||||||
|
|
@ -25,5 +25,5 @@ expression: first_runner_snapshot
|
||||||
│ ││ │
|
│ ││ │
|
||||||
│ ││ │
|
│ ││ │
|
||||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT BASE
|
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 1616
|
assertion_line: 1620
|
||||||
expression: second_runner_snapshot
|
expression: second_runner_snapshot
|
||||||
---
|
---
|
||||||
Zellij (multiple_users_in_different_tabs) Tab #1 [ ] Tab #2
|
Zellij (multiple_users_in_different_tabs) Tab #1 [ ] Tab #2
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 1615
|
assertion_line: 1619
|
||||||
expression: first_runner_snapshot
|
expression: first_runner_snapshot
|
||||||
---
|
---
|
||||||
Zellij (multiple_users_in_different_tabs) Tab #1 Tab #2 [ ]
|
Zellij (multiple_users_in_different_tabs) Tab #1 Tab #2 [ ]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 398
|
assertion_line: 416
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1 Tab #2
|
Zellij (e2e-test) Tab #1 Tab #2
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 745
|
assertion_line: 765
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
@ -25,5 +25,5 @@ expression: last_snapshot
|
||||||
│ ││ │
|
│ ││ │
|
||||||
│ ││ │
|
│ ││ │
|
||||||
└────────────────────────────────────────────────────┘└────────────────────────────────────────────────────────────────┘
|
└────────────────────────────────────────────────────┘└────────────────────────────────────────────────────────────────┘
|
||||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT BASE
|
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 867
|
assertion_line: 889
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
@ -25,5 +25,5 @@ expression: last_snapshot
|
||||||
│ ││ │
|
│ ││ │
|
||||||
│ ││ │
|
│ ││ │
|
||||||
└────────────────────────────────────────────────┘└────────────────────────────────────────────────┘
|
└────────────────────────────────────────────────┘└────────────────────────────────────────────────┘
|
||||||
Ctrl + g p t n h s o q Alt + <[]> BASE
|
Ctrl + g p t n h s o q Alt + <[]>
|
||||||
QuickNav: Alt + <n> / Alt + <←↓↑→> or Alt + <hjkl> / Alt + <+|->
|
QuickNav: Alt + <n> / Alt + <←↓↑→> or Alt + <hjkl> / Alt + <+|->
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 275
|
assertion_line: 290
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
@ -25,5 +25,5 @@ expression: last_snapshot
|
||||||
│ ││line20 │
|
│ ││line20 │
|
||||||
│ ││li█e21 │
|
│ ││li█e21 │
|
||||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT BASE
|
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||||
<↓↑> Scroll / <PgDn|PgUp> Scroll / <d|u> Scroll / <e> Edit / <s> Search / <ENTER> Select
|
<↓↑> Scroll / <PgDn|PgUp> Scroll / <d|u> Scroll / <e> Edit / <s> Search / <ENTER> Select
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 1121
|
assertion_line: 1147
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
@ -25,5 +25,5 @@ expression: last_snapshot
|
||||||
│ ││line18 │
|
│ ││line18 │
|
||||||
│ ││li█e19 │
|
│ ││li█e19 │
|
||||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT BASE
|
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 1990
|
assertion_line: 2031
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 155
|
assertion_line: 168
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
@ -25,5 +25,5 @@ expression: last_snapshot
|
||||||
│ ││ │
|
│ ││ │
|
||||||
│ ││ │
|
│ ││ │
|
||||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT BASE
|
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 1167
|
assertion_line: 1194
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
@ -25,5 +25,5 @@ $ │$ █
|
||||||
│
|
│
|
||||||
│
|
│
|
||||||
│
|
│
|
||||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT BASE
|
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 108
|
assertion_line: 120
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 987
|
assertion_line: 1011
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 1734
|
assertion_line: 1772
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
@ -25,5 +25,5 @@ expression: last_snapshot
|
||||||
│ ││ │
|
│ ││ │
|
||||||
│ ││ │
|
│ ││ │
|
||||||
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
|
||||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT BASE
|
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||||
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
Tip: Alt + <n> => new pane. Alt + <←↓↑→> or Alt + <hjkl> => navigate. Alt + <+|-> => resize pane.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 1687
|
assertion_line: 1724
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 335
|
assertion_line: 351
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
@ -25,5 +25,5 @@ expression: last_snapshot
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT BASE
|
Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SEARCH <o> SESSION <q> QUIT
|
||||||
(FULLSCREEN): + 1 hidden panes
|
(FULLSCREEN): + 1 hidden panes
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 682
|
assertion_line: 702
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 1881
|
assertion_line: 1921
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/tests/e2e/cases.rs
|
source: src/tests/e2e/cases.rs
|
||||||
assertion_line: 1832
|
assertion_line: 1871
|
||||||
expression: last_snapshot
|
expression: last_snapshot
|
||||||
---
|
---
|
||||||
Zellij (e2e-test) Tab #1
|
Zellij (e2e-test) Tab #1
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
mod plugin_loader;
|
mod plugin_loader;
|
||||||
|
mod plugin_map;
|
||||||
mod wasm_bridge;
|
mod wasm_bridge;
|
||||||
|
mod zellij_exports;
|
||||||
use log::info;
|
use log::info;
|
||||||
use std::{collections::HashMap, fs, path::PathBuf};
|
use std::{collections::HashMap, fs, path::PathBuf};
|
||||||
use wasmer::Store;
|
use wasmer::Store;
|
||||||
|
|
@ -37,7 +39,6 @@ pub enum PluginInstruction {
|
||||||
Option<String>, // pane title
|
Option<String>, // pane title
|
||||||
RunPlugin,
|
RunPlugin,
|
||||||
usize, // tab index
|
usize, // tab index
|
||||||
ClientId,
|
|
||||||
Size,
|
Size,
|
||||||
),
|
),
|
||||||
Resize(u32, usize, usize), // plugin_id, columns, rows
|
Resize(u32, usize, usize), // plugin_id, columns, rows
|
||||||
|
|
@ -91,7 +92,7 @@ pub(crate) fn plugin_thread_main(
|
||||||
err_ctx.add_call(ContextType::Plugin((&event).into()));
|
err_ctx.add_call(ContextType::Plugin((&event).into()));
|
||||||
match event {
|
match event {
|
||||||
PluginInstruction::Load(should_float, pane_title, run, tab_index, client_id, size) => {
|
PluginInstruction::Load(should_float, pane_title, run, tab_index, client_id, size) => {
|
||||||
match wasm_bridge.load_plugin(&run, tab_index, size, client_id) {
|
match wasm_bridge.load_plugin(&run, tab_index, size, Some(client_id)) {
|
||||||
Ok(plugin_id) => {
|
Ok(plugin_id) => {
|
||||||
drop(bus.senders.send_to_screen(ScreenInstruction::AddPlugin(
|
drop(bus.senders.send_to_screen(ScreenInstruction::AddPlugin(
|
||||||
should_float,
|
should_float,
|
||||||
|
|
@ -112,14 +113,8 @@ pub(crate) fn plugin_thread_main(
|
||||||
PluginInstruction::Unload(pid) => {
|
PluginInstruction::Unload(pid) => {
|
||||||
wasm_bridge.unload_plugin(pid)?;
|
wasm_bridge.unload_plugin(pid)?;
|
||||||
},
|
},
|
||||||
PluginInstruction::Reload(
|
PluginInstruction::Reload(should_float, pane_title, run, tab_index, size) => {
|
||||||
should_float,
|
match wasm_bridge.reload_plugin(&run) {
|
||||||
pane_title,
|
|
||||||
run,
|
|
||||||
tab_index,
|
|
||||||
client_id,
|
|
||||||
size,
|
|
||||||
) => match wasm_bridge.reload_plugin(&run) {
|
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let _ = bus
|
let _ = bus
|
||||||
.senders
|
.senders
|
||||||
|
|
@ -128,7 +123,9 @@ pub(crate) fn plugin_thread_main(
|
||||||
Err(err) => match err.downcast_ref::<ZellijError>() {
|
Err(err) => match err.downcast_ref::<ZellijError>() {
|
||||||
Some(ZellijError::PluginDoesNotExist) => {
|
Some(ZellijError::PluginDoesNotExist) => {
|
||||||
log::warn!("Plugin {} not found, starting it instead", run.location);
|
log::warn!("Plugin {} not found, starting it instead", run.location);
|
||||||
match wasm_bridge.load_plugin(&run, tab_index, size, client_id) {
|
// we intentionally do not provide the client_id here because it belongs to
|
||||||
|
// the cli who spawned the command and is not an existing client_id
|
||||||
|
match wasm_bridge.load_plugin(&run, tab_index, size, None) {
|
||||||
Ok(plugin_id) => {
|
Ok(plugin_id) => {
|
||||||
drop(bus.senders.send_to_screen(ScreenInstruction::AddPlugin(
|
drop(bus.senders.send_to_screen(ScreenInstruction::AddPlugin(
|
||||||
should_float,
|
should_float,
|
||||||
|
|
@ -147,6 +144,7 @@ pub(crate) fn plugin_thread_main(
|
||||||
return Err(err);
|
return Err(err);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
PluginInstruction::Resize(pid, new_columns, new_rows) => {
|
PluginInstruction::Resize(pid, new_columns, new_rows) => {
|
||||||
wasm_bridge.resize_plugin(pid, new_columns, new_rows)?;
|
wasm_bridge.resize_plugin(pid, new_columns, new_rows)?;
|
||||||
|
|
@ -184,7 +182,7 @@ pub(crate) fn plugin_thread_main(
|
||||||
for run_instruction in extracted_run_instructions {
|
for run_instruction in extracted_run_instructions {
|
||||||
if let Some(Run::Plugin(run)) = run_instruction {
|
if let Some(Run::Plugin(run)) = run_instruction {
|
||||||
let plugin_id =
|
let plugin_id =
|
||||||
wasm_bridge.load_plugin(&run, tab_index, size, client_id)?;
|
wasm_bridge.load_plugin(&run, tab_index, size, Some(client_id))?;
|
||||||
plugin_ids.entry(run.location).or_default().push(plugin_id);
|
plugin_ids.entry(run.location).or_default().push(plugin_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::plugins::wasm_bridge::{wasi_read_string, zellij_exports, PluginEnv, PluginMap};
|
use crate::plugins::plugin_map::{PluginEnv, PluginMap, RunningPlugin, Subscriptions};
|
||||||
|
use crate::plugins::zellij_exports::{wasi_read_string, zellij_exports};
|
||||||
use highway::{HighwayHash, PortableHash};
|
use highway::{HighwayHash, PortableHash};
|
||||||
use log::info;
|
use log::info;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
|
@ -146,21 +147,8 @@ fn assert_plugin_version(instance: &Instance, plugin_env: &PluginEnv) -> Result<
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_plugin_instance(instance: &mut Instance) -> Result<()> {
|
|
||||||
let err_context = || format!("failed to load plugin from instance {instance:#?}");
|
|
||||||
|
|
||||||
let load_function = instance
|
|
||||||
.exports
|
|
||||||
.get_function("_start")
|
|
||||||
.with_context(err_context)?;
|
|
||||||
// This eventually calls the `.load()` method
|
|
||||||
load_function.call(&[]).with_context(err_context)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PluginLoader<'a> {
|
pub struct PluginLoader<'a> {
|
||||||
plugin_cache: Arc<Mutex<HashMap<PathBuf, Module>>>,
|
plugin_cache: Arc<Mutex<HashMap<PathBuf, Module>>>,
|
||||||
plugin_map: Arc<Mutex<PluginMap>>,
|
|
||||||
plugin_path: PathBuf,
|
plugin_path: PathBuf,
|
||||||
loading_indication: &'a mut LoadingIndication,
|
loading_indication: &'a mut LoadingIndication,
|
||||||
senders: ThreadSenders,
|
senders: ThreadSenders,
|
||||||
|
|
@ -206,15 +194,20 @@ impl<'a> PluginLoader<'a> {
|
||||||
)?;
|
)?;
|
||||||
plugin_loader
|
plugin_loader
|
||||||
.load_module_from_memory()
|
.load_module_from_memory()
|
||||||
.and_then(|module| plugin_loader.create_plugin_instance_and_environment(module))
|
.and_then(|module| {
|
||||||
.and_then(|(instance, plugin_env)| {
|
plugin_loader.create_plugin_instance_environment_and_subscriptions(module)
|
||||||
plugin_loader.load_plugin_instance(&instance, &plugin_env)?;
|
})
|
||||||
plugin_loader.clone_instance_for_other_clients(
|
.and_then(|(instance, plugin_env, subscriptions)| {
|
||||||
|
plugin_loader.load_plugin_instance(
|
||||||
&instance,
|
&instance,
|
||||||
&plugin_env,
|
&plugin_env,
|
||||||
&connected_clients,
|
&plugin_map,
|
||||||
|
&subscriptions,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
.and_then(|_| {
|
||||||
|
plugin_loader.clone_instance_for_other_clients(&connected_clients, &plugin_map)
|
||||||
|
})
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
display_loading_stage!(end, loading_indication, senders, plugin_id);
|
display_loading_stage!(end, loading_indication, senders, plugin_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -237,7 +230,6 @@ impl<'a> PluginLoader<'a> {
|
||||||
let err_context = || format!("failed to start plugin {plugin:#?} for client {client_id}");
|
let err_context = || format!("failed to start plugin {plugin:#?} for client {client_id}");
|
||||||
let mut plugin_loader = PluginLoader::new(
|
let mut plugin_loader = PluginLoader::new(
|
||||||
&plugin_cache,
|
&plugin_cache,
|
||||||
&plugin_map,
|
|
||||||
loading_indication,
|
loading_indication,
|
||||||
&senders,
|
&senders,
|
||||||
plugin_id,
|
plugin_id,
|
||||||
|
|
@ -252,19 +244,69 @@ impl<'a> PluginLoader<'a> {
|
||||||
.load_module_from_memory()
|
.load_module_from_memory()
|
||||||
.or_else(|_e| plugin_loader.load_module_from_hd_cache())
|
.or_else(|_e| plugin_loader.load_module_from_hd_cache())
|
||||||
.or_else(|_e| plugin_loader.compile_module())
|
.or_else(|_e| plugin_loader.compile_module())
|
||||||
.and_then(|module| plugin_loader.create_plugin_instance_and_environment(module))
|
.and_then(|module| {
|
||||||
.and_then(|(instance, plugin_env)| {
|
plugin_loader.create_plugin_instance_environment_and_subscriptions(module)
|
||||||
plugin_loader.load_plugin_instance(&instance, &plugin_env)?;
|
})
|
||||||
plugin_loader.clone_instance_for_other_clients(
|
.and_then(|(instance, plugin_env, subscriptions)| {
|
||||||
|
plugin_loader.load_plugin_instance(
|
||||||
&instance,
|
&instance,
|
||||||
&plugin_env,
|
&plugin_env,
|
||||||
|
&plugin_map,
|
||||||
|
&subscriptions,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.and_then(|_| {
|
||||||
|
plugin_loader.clone_instance_for_other_clients(
|
||||||
&connected_clients.lock().unwrap(),
|
&connected_clients.lock().unwrap(),
|
||||||
|
&plugin_map,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
display_loading_stage!(end, loading_indication, senders, plugin_id);
|
display_loading_stage!(end, loading_indication, senders, plugin_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
pub fn add_client(
|
||||||
|
client_id: ClientId,
|
||||||
|
plugin_dir: PathBuf,
|
||||||
|
plugin_cache: Arc<Mutex<HashMap<PathBuf, Module>>>,
|
||||||
|
senders: ThreadSenders,
|
||||||
|
store: Store,
|
||||||
|
plugin_map: Arc<Mutex<PluginMap>>,
|
||||||
|
connected_clients: Arc<Mutex<Vec<ClientId>>>,
|
||||||
|
loading_indication: &mut LoadingIndication,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut new_plugins = HashSet::new();
|
||||||
|
for (&(plugin_id, _), _) in &*plugin_map.lock().unwrap() {
|
||||||
|
new_plugins.insert((plugin_id, client_id));
|
||||||
|
}
|
||||||
|
for (plugin_id, existing_client_id) in new_plugins {
|
||||||
|
let mut plugin_loader = PluginLoader::new_from_different_client_id(
|
||||||
|
&plugin_cache,
|
||||||
|
&plugin_map,
|
||||||
|
loading_indication,
|
||||||
|
&senders,
|
||||||
|
plugin_id,
|
||||||
|
existing_client_id,
|
||||||
|
&store,
|
||||||
|
&plugin_dir,
|
||||||
|
)?;
|
||||||
|
plugin_loader
|
||||||
|
.load_module_from_memory()
|
||||||
|
.and_then(|module| {
|
||||||
|
plugin_loader.create_plugin_instance_environment_and_subscriptions(module)
|
||||||
|
})
|
||||||
|
.and_then(|(instance, plugin_env, subscriptions)| {
|
||||||
|
plugin_loader.load_plugin_instance(
|
||||||
|
&instance,
|
||||||
|
&plugin_env,
|
||||||
|
&plugin_map,
|
||||||
|
&subscriptions,
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
connected_clients.lock().unwrap().push(client_id);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reload_plugin(
|
pub fn reload_plugin(
|
||||||
plugin_id: u32,
|
plugin_id: u32,
|
||||||
|
|
@ -297,22 +339,26 @@ impl<'a> PluginLoader<'a> {
|
||||||
)?;
|
)?;
|
||||||
plugin_loader
|
plugin_loader
|
||||||
.compile_module()
|
.compile_module()
|
||||||
.and_then(|module| plugin_loader.create_plugin_instance_and_environment(module))
|
.and_then(|module| {
|
||||||
.and_then(|(instance, plugin_env)| {
|
plugin_loader.create_plugin_instance_environment_and_subscriptions(module)
|
||||||
plugin_loader.load_plugin_instance(&instance, &plugin_env)?;
|
})
|
||||||
plugin_loader.clone_instance_for_other_clients(
|
.and_then(|(instance, plugin_env, subscriptions)| {
|
||||||
|
plugin_loader.load_plugin_instance(
|
||||||
&instance,
|
&instance,
|
||||||
&plugin_env,
|
&plugin_env,
|
||||||
&connected_clients,
|
&plugin_map,
|
||||||
|
&subscriptions,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
.and_then(|_| {
|
||||||
|
plugin_loader.clone_instance_for_other_clients(&connected_clients, &plugin_map)
|
||||||
|
})
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
display_loading_stage!(end, loading_indication, senders, plugin_id);
|
display_loading_stage!(end, loading_indication, senders, plugin_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn new(
|
pub fn new(
|
||||||
plugin_cache: &Arc<Mutex<HashMap<PathBuf, Module>>>,
|
plugin_cache: &Arc<Mutex<HashMap<PathBuf, Module>>>,
|
||||||
plugin_map: &Arc<Mutex<PluginMap>>,
|
|
||||||
loading_indication: &'a mut LoadingIndication,
|
loading_indication: &'a mut LoadingIndication,
|
||||||
senders: &ThreadSenders,
|
senders: &ThreadSenders,
|
||||||
plugin_id: u32,
|
plugin_id: u32,
|
||||||
|
|
@ -328,7 +374,6 @@ impl<'a> PluginLoader<'a> {
|
||||||
let plugin_path = plugin.path.clone();
|
let plugin_path = plugin.path.clone();
|
||||||
Ok(PluginLoader {
|
Ok(PluginLoader {
|
||||||
plugin_cache: plugin_cache.clone(),
|
plugin_cache: plugin_cache.clone(),
|
||||||
plugin_map: plugin_map.clone(),
|
|
||||||
plugin_path,
|
plugin_path,
|
||||||
loading_indication,
|
loading_indication,
|
||||||
senders: senders.clone(),
|
senders: senders.clone(),
|
||||||
|
|
@ -354,19 +399,63 @@ impl<'a> PluginLoader<'a> {
|
||||||
plugin_dir: &'a PathBuf,
|
plugin_dir: &'a PathBuf,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let err_context = || "Failed to find existing plugin";
|
let err_context = || "Failed to find existing plugin";
|
||||||
let (_old_instance, old_user_env, (rows, cols)) = {
|
let (running_plugin, _subscriptions) = {
|
||||||
let mut plugin_map = plugin_map.lock().unwrap();
|
let mut plugin_map = plugin_map.lock().unwrap();
|
||||||
plugin_map
|
plugin_map
|
||||||
.remove(&(plugin_id, client_id))
|
.remove(&(plugin_id, client_id))
|
||||||
.with_context(err_context)?
|
.with_context(err_context)?
|
||||||
};
|
};
|
||||||
let tab_index = old_user_env.tab_index;
|
let running_plugin = running_plugin.lock().unwrap();
|
||||||
let size = Size { rows, cols };
|
let tab_index = running_plugin.plugin_env.tab_index;
|
||||||
let plugin_config = old_user_env.plugin.clone();
|
let size = Size {
|
||||||
loading_indication.set_name(old_user_env.name());
|
rows: running_plugin.rows,
|
||||||
|
cols: running_plugin.columns,
|
||||||
|
};
|
||||||
|
let plugin_config = running_plugin.plugin_env.plugin.clone();
|
||||||
|
loading_indication.set_name(running_plugin.plugin_env.name());
|
||||||
|
PluginLoader::new(
|
||||||
|
plugin_cache,
|
||||||
|
loading_indication,
|
||||||
|
senders,
|
||||||
|
plugin_id,
|
||||||
|
client_id,
|
||||||
|
store,
|
||||||
|
plugin_config,
|
||||||
|
plugin_dir,
|
||||||
|
tab_index,
|
||||||
|
size,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pub fn new_from_different_client_id(
|
||||||
|
plugin_cache: &Arc<Mutex<HashMap<PathBuf, Module>>>,
|
||||||
|
plugin_map: &Arc<Mutex<PluginMap>>,
|
||||||
|
loading_indication: &'a mut LoadingIndication,
|
||||||
|
senders: &ThreadSenders,
|
||||||
|
plugin_id: u32,
|
||||||
|
client_id: ClientId,
|
||||||
|
store: &Store,
|
||||||
|
plugin_dir: &'a PathBuf,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let err_context = || "Failed to find existing plugin";
|
||||||
|
let (running_plugin, _subscriptions) = {
|
||||||
|
let plugin_map = plugin_map.lock().unwrap();
|
||||||
|
plugin_map
|
||||||
|
.iter()
|
||||||
|
.find(|((p_id, _c_id), _)| p_id == &plugin_id)
|
||||||
|
.with_context(err_context)?
|
||||||
|
.1
|
||||||
|
.clone()
|
||||||
|
};
|
||||||
|
let running_plugin = running_plugin.lock().unwrap();
|
||||||
|
let tab_index = running_plugin.plugin_env.tab_index;
|
||||||
|
let size = Size {
|
||||||
|
rows: running_plugin.rows,
|
||||||
|
cols: running_plugin.columns,
|
||||||
|
};
|
||||||
|
let plugin_config = running_plugin.plugin_env.plugin.clone();
|
||||||
|
loading_indication.set_name(running_plugin.plugin_env.name());
|
||||||
PluginLoader::new(
|
PluginLoader::new(
|
||||||
plugin_cache,
|
plugin_cache,
|
||||||
plugin_map,
|
|
||||||
loading_indication,
|
loading_indication,
|
||||||
senders,
|
senders,
|
||||||
plugin_id,
|
plugin_id,
|
||||||
|
|
@ -464,10 +553,10 @@ impl<'a> PluginLoader<'a> {
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
Ok(module)
|
Ok(module)
|
||||||
}
|
}
|
||||||
pub fn create_plugin_instance_and_environment(
|
pub fn create_plugin_instance_environment_and_subscriptions(
|
||||||
&mut self,
|
&mut self,
|
||||||
module: Module,
|
module: Module,
|
||||||
) -> Result<(Instance, PluginEnv)> {
|
) -> Result<(Instance, PluginEnv, Arc<Mutex<Subscriptions>>)> {
|
||||||
let err_context = || {
|
let err_context = || {
|
||||||
format!(
|
format!(
|
||||||
"Failed to create instance and plugin env for plugin {}",
|
"Failed to create instance and plugin env for plugin {}",
|
||||||
|
|
@ -499,12 +588,12 @@ impl<'a> PluginLoader<'a> {
|
||||||
plugin: mut_plugin,
|
plugin: mut_plugin,
|
||||||
senders: self.senders.clone(),
|
senders: self.senders.clone(),
|
||||||
wasi_env,
|
wasi_env,
|
||||||
subscriptions: Arc::new(Mutex::new(HashSet::new())),
|
|
||||||
plugin_own_data_dir: self.plugin_own_data_dir.clone(),
|
plugin_own_data_dir: self.plugin_own_data_dir.clone(),
|
||||||
tab_index: self.tab_index,
|
tab_index: self.tab_index,
|
||||||
};
|
};
|
||||||
|
|
||||||
let zellij = zellij_exports(&self.store, &plugin_env);
|
let subscriptions = Arc::new(Mutex::new(HashSet::new()));
|
||||||
|
let zellij = zellij_exports(&self.store, &plugin_env, &subscriptions);
|
||||||
let instance =
|
let instance =
|
||||||
Instance::new(&module, &zellij.chain_back(wasi)).with_context(err_context)?;
|
Instance::new(&module, &zellij.chain_back(wasi)).with_context(err_context)?;
|
||||||
assert_plugin_version(&instance, &plugin_env).with_context(err_context)?;
|
assert_plugin_version(&instance, &plugin_env).with_context(err_context)?;
|
||||||
|
|
@ -514,12 +603,14 @@ impl<'a> PluginLoader<'a> {
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.insert(cloned_plugin.path, module);
|
.insert(cloned_plugin.path, module);
|
||||||
Ok((instance, plugin_env))
|
Ok((instance, plugin_env, subscriptions))
|
||||||
}
|
}
|
||||||
pub fn load_plugin_instance(
|
pub fn load_plugin_instance(
|
||||||
&mut self,
|
&mut self,
|
||||||
instance: &Instance,
|
instance: &Instance,
|
||||||
plugin_env: &PluginEnv,
|
plugin_env: &PluginEnv,
|
||||||
|
plugin_map: &Arc<Mutex<PluginMap>>,
|
||||||
|
subscriptions: &Arc<Mutex<Subscriptions>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let err_context = || format!("failed to load plugin from instance {instance:#?}");
|
let err_context = || format!("failed to load plugin from instance {instance:#?}");
|
||||||
let main_user_instance = instance.clone();
|
let main_user_instance = instance.clone();
|
||||||
|
|
@ -548,13 +639,16 @@ impl<'a> PluginLoader<'a> {
|
||||||
self.senders,
|
self.senders,
|
||||||
self.plugin_id
|
self.plugin_id
|
||||||
);
|
);
|
||||||
let mut plugin_map = self.plugin_map.lock().unwrap();
|
plugin_map.lock().unwrap().insert(
|
||||||
plugin_map.insert(
|
|
||||||
(self.plugin_id, self.client_id),
|
(self.plugin_id, self.client_id),
|
||||||
(
|
(
|
||||||
|
Arc::new(Mutex::new(RunningPlugin::new(
|
||||||
main_user_instance,
|
main_user_instance,
|
||||||
main_user_env,
|
main_user_env,
|
||||||
(self.size.rows, self.size.cols),
|
self.size.rows,
|
||||||
|
self.size.cols,
|
||||||
|
))),
|
||||||
|
subscriptions.clone(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
display_loading_stage!(
|
display_loading_stage!(
|
||||||
|
|
@ -567,9 +661,8 @@ impl<'a> PluginLoader<'a> {
|
||||||
}
|
}
|
||||||
pub fn clone_instance_for_other_clients(
|
pub fn clone_instance_for_other_clients(
|
||||||
&mut self,
|
&mut self,
|
||||||
instance: &Instance,
|
|
||||||
plugin_env: &PluginEnv,
|
|
||||||
connected_clients: &[ClientId],
|
connected_clients: &[ClientId],
|
||||||
|
plugin_map: &Arc<Mutex<PluginMap>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if !connected_clients.is_empty() {
|
if !connected_clients.is_empty() {
|
||||||
display_loading_stage!(
|
display_loading_stage!(
|
||||||
|
|
@ -578,14 +671,32 @@ impl<'a> PluginLoader<'a> {
|
||||||
self.senders,
|
self.senders,
|
||||||
self.plugin_id
|
self.plugin_id
|
||||||
);
|
);
|
||||||
let mut plugin_map = self.plugin_map.lock().unwrap();
|
|
||||||
for client_id in connected_clients {
|
for client_id in connected_clients {
|
||||||
let (instance, new_plugin_env) =
|
let mut loading_indication = LoadingIndication::new("".into());
|
||||||
clone_plugin_for_client(&plugin_env, *client_id, &instance, &self.store)?;
|
let mut plugin_loader_for_client = PluginLoader::new_from_different_client_id(
|
||||||
plugin_map.insert(
|
&self.plugin_cache.clone(),
|
||||||
(self.plugin_id, *client_id),
|
&plugin_map,
|
||||||
(instance, new_plugin_env, (self.size.rows, self.size.cols)),
|
&mut loading_indication,
|
||||||
);
|
&self.senders.clone(),
|
||||||
|
self.plugin_id,
|
||||||
|
*client_id,
|
||||||
|
&self.store,
|
||||||
|
&self.plugin_dir,
|
||||||
|
)?;
|
||||||
|
plugin_loader_for_client
|
||||||
|
.load_module_from_memory()
|
||||||
|
.and_then(|module| {
|
||||||
|
plugin_loader_for_client
|
||||||
|
.create_plugin_instance_environment_and_subscriptions(module)
|
||||||
|
})
|
||||||
|
.and_then(|(instance, plugin_env, subscriptions)| {
|
||||||
|
plugin_loader_for_client.load_plugin_instance(
|
||||||
|
&instance,
|
||||||
|
&plugin_env,
|
||||||
|
plugin_map,
|
||||||
|
&subscriptions,
|
||||||
|
)
|
||||||
|
})?
|
||||||
}
|
}
|
||||||
display_loading_stage!(
|
display_loading_stage!(
|
||||||
indicate_cloning_plugin_for_other_clients_success,
|
indicate_cloning_plugin_for_other_clients_success,
|
||||||
|
|
@ -633,24 +744,3 @@ fn create_plugin_fs_entries(plugin_own_data_dir: &PathBuf) -> Result<()> {
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clone_plugin_for_client(
|
|
||||||
plugin_env: &PluginEnv,
|
|
||||||
client_id: ClientId,
|
|
||||||
instance: &Instance,
|
|
||||||
store: &Store,
|
|
||||||
) -> Result<(Instance, PluginEnv)> {
|
|
||||||
let err_context = || format!("Failed to clone plugin for client {client_id}");
|
|
||||||
let mut new_plugin_env = plugin_env.clone();
|
|
||||||
new_plugin_env.client_id = client_id;
|
|
||||||
let module = instance.module().clone();
|
|
||||||
let wasi = new_plugin_env
|
|
||||||
.wasi_env
|
|
||||||
.import_object(&module)
|
|
||||||
.with_context(err_context)?;
|
|
||||||
let zellij = zellij_exports(store, &new_plugin_env);
|
|
||||||
let mut instance =
|
|
||||||
Instance::new(&module, &zellij.chain_back(wasi)).with_context(err_context)?;
|
|
||||||
load_plugin_instance(&mut instance).with_context(err_context)?;
|
|
||||||
Ok((instance, new_plugin_env))
|
|
||||||
}
|
|
||||||
|
|
|
||||||
94
zellij-server/src/plugins/plugin_map.rs
Normal file
94
zellij-server/src/plugins/plugin_map.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
use crate::plugins::wasm_bridge::PluginId;
|
||||||
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
path::PathBuf,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
use wasmer::Instance;
|
||||||
|
use wasmer_wasi::WasiEnv;
|
||||||
|
|
||||||
|
use crate::{thread_bus::ThreadSenders, ClientId};
|
||||||
|
|
||||||
|
use zellij_utils::{data::EventType, input::plugins::PluginConfig};
|
||||||
|
|
||||||
|
// the idea here is to provide atomicity when adding/removing plugins from the map (eg. when a new
|
||||||
|
// client connects) but to also allow updates/renders not to block each other
|
||||||
|
// so when adding/removing from the map - everything is halted, that's life
|
||||||
|
// but when cloning the internal RunningPlugin and Subscriptions atomics, we can call methods on
|
||||||
|
// them without blocking other instances
|
||||||
|
pub type PluginMap =
|
||||||
|
HashMap<(PluginId, ClientId), (Arc<Mutex<RunningPlugin>>, Arc<Mutex<Subscriptions>>)>;
|
||||||
|
pub type Subscriptions = HashSet<EventType>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PluginEnv {
|
||||||
|
pub plugin_id: u32,
|
||||||
|
pub plugin: PluginConfig,
|
||||||
|
pub senders: ThreadSenders,
|
||||||
|
pub wasi_env: WasiEnv,
|
||||||
|
pub tab_index: usize,
|
||||||
|
pub client_id: ClientId,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub plugin_own_data_dir: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PluginEnv {
|
||||||
|
// Get the name (path) of the containing plugin
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"{} (ID {})",
|
||||||
|
self.plugin.path.display().to_string(),
|
||||||
|
self.plugin_id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
|
pub enum AtomicEvent {
|
||||||
|
Resize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RunningPlugin {
|
||||||
|
pub instance: Instance,
|
||||||
|
pub plugin_env: PluginEnv,
|
||||||
|
pub rows: usize,
|
||||||
|
pub columns: usize,
|
||||||
|
next_event_ids: HashMap<AtomicEvent, usize>,
|
||||||
|
last_applied_event_ids: HashMap<AtomicEvent, usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RunningPlugin {
|
||||||
|
pub fn new(instance: Instance, plugin_env: PluginEnv, rows: usize, columns: usize) -> Self {
|
||||||
|
RunningPlugin {
|
||||||
|
instance,
|
||||||
|
plugin_env,
|
||||||
|
rows,
|
||||||
|
columns,
|
||||||
|
next_event_ids: HashMap::new(),
|
||||||
|
last_applied_event_ids: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn next_event_id(&mut self, atomic_event: AtomicEvent) -> usize {
|
||||||
|
// TODO: probably not usize...
|
||||||
|
let current_event_id = *self.next_event_ids.get(&atomic_event).unwrap_or(&0);
|
||||||
|
if current_event_id < usize::MAX {
|
||||||
|
let next_event_id = current_event_id + 1;
|
||||||
|
self.next_event_ids.insert(atomic_event, next_event_id);
|
||||||
|
current_event_id
|
||||||
|
} else {
|
||||||
|
let current_event_id = 0;
|
||||||
|
let next_event_id = 1;
|
||||||
|
self.last_applied_event_ids.remove(&atomic_event);
|
||||||
|
self.next_event_ids.insert(atomic_event, next_event_id);
|
||||||
|
current_event_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn apply_event_id(&mut self, atomic_event: AtomicEvent, event_id: usize) -> bool {
|
||||||
|
if &event_id >= self.last_applied_event_ids.get(&atomic_event).unwrap_or(&0) {
|
||||||
|
self.last_applied_event_ids.insert(atomic_event, event_id);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,79 +1,36 @@
|
||||||
use super::PluginInstruction;
|
use super::PluginInstruction;
|
||||||
use crate::plugins::plugin_loader::{PluginLoader, VersionMismatchError};
|
use crate::plugins::plugin_loader::{PluginLoader, VersionMismatchError};
|
||||||
use log::{debug, info, warn};
|
use crate::plugins::plugin_map::{AtomicEvent, PluginEnv, PluginMap};
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use crate::plugins::zellij_exports::{wasi_read_string, wasi_write_object};
|
||||||
|
use log::info;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process,
|
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
thread,
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
};
|
||||||
use wasmer::{
|
use wasmer::{Instance, Module, Store, Value};
|
||||||
imports, ChainableNamedResolver, Function, ImportObject, Instance, Module, Store, Value,
|
|
||||||
WasmerEnv,
|
|
||||||
};
|
|
||||||
use wasmer_wasi::WasiEnv;
|
|
||||||
use zellij_utils::async_std::task::{self, JoinHandle};
|
use zellij_utils::async_std::task::{self, JoinHandle};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
background_jobs::BackgroundJob,
|
background_jobs::BackgroundJob, screen::ScreenInstruction, thread_bus::ThreadSenders,
|
||||||
panes::PaneId,
|
ui::loading_indication::LoadingIndication, ClientId,
|
||||||
pty::{ClientOrTabIndex, PtyInstruction},
|
|
||||||
screen::ScreenInstruction,
|
|
||||||
thread_bus::ThreadSenders,
|
|
||||||
ui::loading_indication::LoadingIndication,
|
|
||||||
ClientId,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use zellij_utils::{
|
use zellij_utils::{
|
||||||
consts::VERSION,
|
consts::VERSION,
|
||||||
data::{Event, EventType, PluginIds},
|
data::{Event, EventType},
|
||||||
errors::prelude::*,
|
errors::prelude::*,
|
||||||
errors::ZellijError,
|
errors::ZellijError,
|
||||||
input::{
|
input::{
|
||||||
command::TerminalAction,
|
|
||||||
layout::{RunPlugin, RunPluginLocation},
|
layout::{RunPlugin, RunPluginLocation},
|
||||||
plugins::{PluginConfig, PluginType, PluginsConfig},
|
plugins::PluginsConfig,
|
||||||
},
|
},
|
||||||
pane_size::Size,
|
pane_size::Size,
|
||||||
serde,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type PluginId = u32;
|
pub type PluginId = u32;
|
||||||
|
|
||||||
#[derive(WasmerEnv, Clone)]
|
|
||||||
pub struct PluginEnv {
|
|
||||||
pub plugin_id: u32,
|
|
||||||
pub plugin: PluginConfig,
|
|
||||||
pub senders: ThreadSenders,
|
|
||||||
pub wasi_env: WasiEnv,
|
|
||||||
pub subscriptions: Arc<Mutex<HashSet<EventType>>>,
|
|
||||||
pub tab_index: usize,
|
|
||||||
pub client_id: ClientId,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub plugin_own_data_dir: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PluginEnv {
|
|
||||||
// Get the name (path) of the containing plugin
|
|
||||||
pub fn name(&self) -> String {
|
|
||||||
format!(
|
|
||||||
"{} (ID {})",
|
|
||||||
self.plugin.path.display().to_string(),
|
|
||||||
self.plugin_id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type PluginMap = HashMap<(u32, ClientId), (Instance, PluginEnv, (usize, usize))>; // u32 =>
|
|
||||||
// plugin_id,
|
|
||||||
// (usize, usize)
|
|
||||||
// => (rows,
|
|
||||||
// columns)
|
|
||||||
|
|
||||||
pub struct WasmBridge {
|
pub struct WasmBridge {
|
||||||
connected_clients: Arc<Mutex<Vec<ClientId>>>,
|
connected_clients: Arc<Mutex<Vec<ClientId>>>,
|
||||||
|
|
@ -121,10 +78,24 @@ impl WasmBridge {
|
||||||
run: &RunPlugin,
|
run: &RunPlugin,
|
||||||
tab_index: usize,
|
tab_index: usize,
|
||||||
size: Size,
|
size: Size,
|
||||||
client_id: ClientId,
|
client_id: Option<ClientId>,
|
||||||
) -> Result<u32> {
|
) -> Result<u32> {
|
||||||
// returns the plugin id
|
// returns the plugin id
|
||||||
let err_context = move || format!("failed to load plugin for client {client_id}");
|
let err_context = move || format!("failed to load plugin");
|
||||||
|
|
||||||
|
let client_id = client_id
|
||||||
|
.or_else(|| {
|
||||||
|
self.connected_clients
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.copied()
|
||||||
|
})
|
||||||
|
.with_context(|| {
|
||||||
|
"Plugins must have a client id, none was provided and none are connected"
|
||||||
|
})?;
|
||||||
|
|
||||||
let plugin_id = self.next_plugin_id;
|
let plugin_id = self.next_plugin_id;
|
||||||
|
|
||||||
let plugin = self
|
let plugin = self
|
||||||
|
|
@ -277,48 +248,30 @@ impl WasmBridge {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn add_client(&mut self, client_id: ClientId) -> Result<()> {
|
pub fn add_client(&mut self, client_id: ClientId) -> Result<()> {
|
||||||
let err_context = || format!("failed to add plugins for client {client_id}");
|
let mut loading_indication = LoadingIndication::new("".into());
|
||||||
|
match PluginLoader::add_client(
|
||||||
self.connected_clients.lock().unwrap().push(client_id);
|
client_id,
|
||||||
|
self.plugin_dir.clone(),
|
||||||
let mut seen = HashSet::new();
|
self.plugin_cache.clone(),
|
||||||
let mut new_plugins = HashMap::new();
|
self.senders.clone(),
|
||||||
let mut plugin_map = self.plugin_map.lock().unwrap();
|
self.store.clone(),
|
||||||
for (&(plugin_id, _), (instance, plugin_env, (rows, columns))) in &*plugin_map {
|
self.plugin_map.clone(),
|
||||||
if seen.contains(&plugin_id) {
|
self.connected_clients.clone(),
|
||||||
continue;
|
&mut loading_indication,
|
||||||
}
|
) {
|
||||||
seen.insert(plugin_id);
|
Ok(_) => {
|
||||||
let mut new_plugin_env = plugin_env.clone();
|
let _ = self
|
||||||
|
.senders
|
||||||
new_plugin_env.client_id = client_id;
|
.send_to_screen(ScreenInstruction::RequestStateUpdateForPlugins);
|
||||||
new_plugins.insert(
|
|
||||||
plugin_id,
|
|
||||||
(instance.module().clone(), new_plugin_env, (*rows, *columns)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (plugin_id, (module, mut new_plugin_env, (rows, columns))) in new_plugins.drain() {
|
|
||||||
let wasi = new_plugin_env
|
|
||||||
.wasi_env
|
|
||||||
.import_object(&module)
|
|
||||||
.with_context(err_context)?;
|
|
||||||
let zellij = zellij_exports(&self.store, &new_plugin_env);
|
|
||||||
let mut instance =
|
|
||||||
Instance::new(&module, &zellij.chain_back(wasi)).with_context(err_context)?;
|
|
||||||
load_plugin_instance(&mut instance).with_context(err_context)?;
|
|
||||||
plugin_map.insert(
|
|
||||||
(plugin_id, client_id),
|
|
||||||
(instance, new_plugin_env, (rows, columns)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
},
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn resize_plugin(&mut self, pid: u32, new_columns: usize, new_rows: usize) -> Result<()> {
|
pub fn resize_plugin(&mut self, pid: u32, new_columns: usize, new_rows: usize) -> Result<()> {
|
||||||
let err_context = || format!("failed to resize plugin {pid}");
|
let err_context = move || format!("failed to resize plugin {pid}");
|
||||||
let mut plugin_bytes = vec![];
|
for ((plugin_id, client_id), (running_plugin, _subscriptions)) in
|
||||||
let mut plugin_map = self.plugin_map.lock().unwrap();
|
self.plugin_map.lock().unwrap().iter_mut()
|
||||||
for ((plugin_id, client_id), (instance, plugin_env, (current_rows, current_columns))) in
|
|
||||||
plugin_map.iter_mut()
|
|
||||||
{
|
{
|
||||||
if self
|
if self
|
||||||
.cached_resizes_for_pending_plugins
|
.cached_resizes_for_pending_plugins
|
||||||
|
|
@ -327,26 +280,53 @@ impl WasmBridge {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if *plugin_id == pid {
|
if *plugin_id == pid {
|
||||||
*current_rows = new_rows;
|
let event_id = running_plugin
|
||||||
*current_columns = new_columns;
|
.lock()
|
||||||
|
.unwrap()
|
||||||
// TODO: consolidate with above render function
|
.next_event_id(AtomicEvent::Resize);
|
||||||
let rendered_bytes = instance
|
task::spawn({
|
||||||
|
let senders = self.senders.clone();
|
||||||
|
let running_plugin = running_plugin.clone();
|
||||||
|
let plugin_id = *plugin_id;
|
||||||
|
let client_id = *client_id;
|
||||||
|
async move {
|
||||||
|
let mut running_plugin = running_plugin.lock().unwrap();
|
||||||
|
if running_plugin.apply_event_id(AtomicEvent::Resize, event_id) {
|
||||||
|
running_plugin.rows = new_rows;
|
||||||
|
running_plugin.columns = new_columns;
|
||||||
|
let rendered_bytes = running_plugin
|
||||||
|
.instance
|
||||||
.exports
|
.exports
|
||||||
.get_function("render")
|
.get_function("render")
|
||||||
.map_err(anyError::new)
|
.map_err(anyError::new)
|
||||||
.and_then(|render| {
|
.and_then(|render| {
|
||||||
render
|
render
|
||||||
.call(&[
|
.call(&[
|
||||||
Value::I32(*current_rows as i32),
|
Value::I32(running_plugin.rows as i32),
|
||||||
Value::I32(*current_columns as i32),
|
Value::I32(running_plugin.columns as i32),
|
||||||
])
|
])
|
||||||
.map_err(anyError::new)
|
.map_err(anyError::new)
|
||||||
})
|
})
|
||||||
.and_then(|_| wasi_read_string(&plugin_env.wasi_env))
|
.and_then(|_| wasi_read_string(&running_plugin.plugin_env.wasi_env))
|
||||||
.with_context(err_context)?;
|
.with_context(err_context);
|
||||||
|
match rendered_bytes {
|
||||||
plugin_bytes.push((*plugin_id, *client_id, rendered_bytes.as_bytes().to_vec()));
|
Ok(rendered_bytes) => {
|
||||||
|
let plugin_bytes = vec![(
|
||||||
|
plugin_id,
|
||||||
|
client_id,
|
||||||
|
rendered_bytes.as_bytes().to_vec(),
|
||||||
|
)];
|
||||||
|
senders
|
||||||
|
.send_to_screen(ScreenInstruction::PluginBytes(
|
||||||
|
plugin_bytes,
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
},
|
||||||
|
Err(e) => log::error!("{}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (plugin_id, mut current_size) in self.cached_resizes_for_pending_plugins.iter_mut() {
|
for (plugin_id, mut current_size) in self.cached_resizes_for_pending_plugins.iter_mut() {
|
||||||
|
|
@ -355,9 +335,6 @@ impl WasmBridge {
|
||||||
current_size.1 = new_columns;
|
current_size.1 = new_columns;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let _ = self
|
|
||||||
.senders
|
|
||||||
.send_to_screen(ScreenInstruction::PluginBytes(plugin_bytes));
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn update_plugins(
|
pub fn update_plugins(
|
||||||
|
|
@ -366,21 +343,17 @@ impl WasmBridge {
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let err_context = || "failed to update plugin state".to_string();
|
let err_context = || "failed to update plugin state".to_string();
|
||||||
|
|
||||||
let plugin_map = self.plugin_map.lock().unwrap();
|
|
||||||
let mut plugin_bytes = vec![];
|
|
||||||
for (pid, cid, event) in updates.drain(..) {
|
for (pid, cid, event) in updates.drain(..) {
|
||||||
for (&(plugin_id, client_id), (instance, plugin_env, (rows, columns))) in &*plugin_map {
|
for (&(plugin_id, client_id), (running_plugin, subscriptions)) in
|
||||||
|
&*self.plugin_map.lock().unwrap()
|
||||||
|
{
|
||||||
if self
|
if self
|
||||||
.cached_events_for_pending_plugins
|
.cached_events_for_pending_plugins
|
||||||
.contains_key(&plugin_id)
|
.contains_key(&plugin_id)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let subs = plugin_env
|
let subs = subscriptions.lock().unwrap().clone();
|
||||||
.subscriptions
|
|
||||||
.lock()
|
|
||||||
.to_anyhow()
|
|
||||||
.with_context(err_context)?;
|
|
||||||
// FIXME: This is very janky... Maybe I should write my own macro for Event -> EventType?
|
// FIXME: This is very janky... Maybe I should write my own macro for Event -> EventType?
|
||||||
let event_type =
|
let event_type =
|
||||||
EventType::from_str(&event.to_string()).with_context(err_context)?;
|
EventType::from_str(&event.to_string()).with_context(err_context)?;
|
||||||
|
|
@ -390,16 +363,34 @@ impl WasmBridge {
|
||||||
|| (cid.is_none() && pid == Some(plugin_id))
|
|| (cid.is_none() && pid == Some(plugin_id))
|
||||||
|| (cid == Some(client_id) && pid == Some(plugin_id)))
|
|| (cid == Some(client_id) && pid == Some(plugin_id)))
|
||||||
{
|
{
|
||||||
apply_event_to_plugin(
|
task::spawn({
|
||||||
|
let senders = self.senders.clone();
|
||||||
|
let running_plugin = running_plugin.clone();
|
||||||
|
let event = event.clone();
|
||||||
|
async move {
|
||||||
|
let running_plugin = running_plugin.lock().unwrap();
|
||||||
|
let mut plugin_bytes = vec![];
|
||||||
|
match apply_event_to_plugin(
|
||||||
plugin_id,
|
plugin_id,
|
||||||
client_id,
|
client_id,
|
||||||
&instance,
|
&running_plugin.instance,
|
||||||
&plugin_env,
|
&running_plugin.plugin_env,
|
||||||
&event,
|
&event,
|
||||||
*rows,
|
running_plugin.rows,
|
||||||
*columns,
|
running_plugin.columns,
|
||||||
&mut plugin_bytes,
|
&mut plugin_bytes,
|
||||||
)?;
|
) {
|
||||||
|
Ok(()) => {
|
||||||
|
let _ = senders.send_to_screen(ScreenInstruction::PluginBytes(
|
||||||
|
plugin_bytes,
|
||||||
|
));
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{}", e);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (plugin_id, cached_events) in self.cached_events_for_pending_plugins.iter_mut() {
|
for (plugin_id, cached_events) in self.cached_events_for_pending_plugins.iter_mut() {
|
||||||
|
|
@ -408,9 +399,6 @@ impl WasmBridge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let _ = self
|
|
||||||
.senders
|
|
||||||
.send_to_screen(ScreenInstruction::PluginBytes(plugin_bytes));
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn apply_cached_events(&mut self, plugin_ids: Vec<u32>) -> Result<()> {
|
pub fn apply_cached_events(&mut self, plugin_ids: Vec<u32>) -> Result<()> {
|
||||||
|
|
@ -450,7 +438,6 @@ impl WasmBridge {
|
||||||
fn apply_cached_events_and_resizes_for_plugin(&mut self, plugin_id: PluginId) -> Result<()> {
|
fn apply_cached_events_and_resizes_for_plugin(&mut self, plugin_id: PluginId) -> Result<()> {
|
||||||
let err_context = || format!("Failed to apply cached events to plugin");
|
let err_context = || format!("Failed to apply cached events to plugin");
|
||||||
if let Some(events) = self.cached_events_for_pending_plugins.remove(&plugin_id) {
|
if let Some(events) = self.cached_events_for_pending_plugins.remove(&plugin_id) {
|
||||||
let mut plugin_map = self.plugin_map.lock().unwrap();
|
|
||||||
let all_connected_clients: Vec<ClientId> = self
|
let all_connected_clients: Vec<ClientId> = self
|
||||||
.connected_clients
|
.connected_clients
|
||||||
.lock()
|
.lock()
|
||||||
|
|
@ -459,35 +446,48 @@ impl WasmBridge {
|
||||||
.copied()
|
.copied()
|
||||||
.collect();
|
.collect();
|
||||||
for client_id in &all_connected_clients {
|
for client_id in &all_connected_clients {
|
||||||
let mut plugin_bytes = vec![];
|
if let Some((running_plugin, subscriptions)) = self
|
||||||
if let Some((instance, plugin_env, (rows, columns))) =
|
.plugin_map
|
||||||
plugin_map.get_mut(&(plugin_id, *client_id))
|
|
||||||
{
|
|
||||||
let subs = plugin_env
|
|
||||||
.subscriptions
|
|
||||||
.lock()
|
.lock()
|
||||||
.to_anyhow()
|
.unwrap()
|
||||||
.with_context(err_context)?;
|
.get_mut(&(plugin_id, *client_id))
|
||||||
|
{
|
||||||
|
let subs = subscriptions.lock().unwrap().clone();
|
||||||
for event in events.clone() {
|
for event in events.clone() {
|
||||||
let event_type =
|
let event_type =
|
||||||
EventType::from_str(&event.to_string()).with_context(err_context)?;
|
EventType::from_str(&event.to_string()).with_context(err_context)?;
|
||||||
if !subs.contains(&event_type) {
|
if !subs.contains(&event_type) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
apply_event_to_plugin(
|
task::spawn({
|
||||||
|
let senders = self.senders.clone();
|
||||||
|
let running_plugin = running_plugin.clone();
|
||||||
|
let client_id = *client_id;
|
||||||
|
async move {
|
||||||
|
let running_plugin = running_plugin.lock().unwrap();
|
||||||
|
let mut plugin_bytes = vec![];
|
||||||
|
match apply_event_to_plugin(
|
||||||
plugin_id,
|
plugin_id,
|
||||||
*client_id,
|
client_id,
|
||||||
&instance,
|
&running_plugin.instance,
|
||||||
&plugin_env,
|
&running_plugin.plugin_env,
|
||||||
&event,
|
&event,
|
||||||
*rows,
|
running_plugin.rows,
|
||||||
*columns,
|
running_plugin.columns,
|
||||||
&mut plugin_bytes,
|
&mut plugin_bytes,
|
||||||
)?;
|
) {
|
||||||
|
Ok(()) => {
|
||||||
|
let _ = senders.send_to_screen(
|
||||||
|
ScreenInstruction::PluginBytes(plugin_bytes),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{}", e);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
let _ = self
|
|
||||||
.senders
|
|
||||||
.send_to_screen(ScreenInstruction::PluginBytes(plugin_bytes));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -512,11 +512,11 @@ impl WasmBridge {
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(
|
.filter(|(_, (running_plugin, _subscriptions))| {
|
||||||
|((_plugin_id, _client_id), (_instance, plugin_env, _size))| {
|
&running_plugin.lock().unwrap().plugin_env.plugin.location == plugin_location
|
||||||
&plugin_env.plugin.location == plugin_location
|
// TODO:
|
||||||
},
|
// better
|
||||||
)
|
})
|
||||||
.map(|((plugin_id, _client_id), _)| *plugin_id)
|
.map(|((plugin_id, _client_id), _)| *plugin_id)
|
||||||
.collect();
|
.collect();
|
||||||
if plugin_ids.is_empty() {
|
if plugin_ids.is_empty() {
|
||||||
|
|
@ -530,8 +530,11 @@ impl WasmBridge {
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.find(|((p_id, _client_id), (_instance, _plugin_env, _size))| *p_id == plugin_id)
|
.find(|((p_id, _client_id), _)| *p_id == plugin_id)
|
||||||
.map(|((_p_id, _client_id), (_instance, _plugin_env, size))| *size)
|
.map(|(_, (running_plugin, _subscriptions))| {
|
||||||
|
let running_plugin = running_plugin.lock().unwrap();
|
||||||
|
(running_plugin.rows, running_plugin.columns)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
fn start_plugin_loading_indication(
|
fn start_plugin_loading_indication(
|
||||||
&self,
|
&self,
|
||||||
|
|
@ -563,6 +566,7 @@ fn handle_plugin_loading_failure(
|
||||||
loading_indication: &mut LoadingIndication,
|
loading_indication: &mut LoadingIndication,
|
||||||
error: impl Display,
|
error: impl Display,
|
||||||
) {
|
) {
|
||||||
|
log::error!("{}", error);
|
||||||
let _ = senders.send_to_background_jobs(BackgroundJob::StopPluginLoadingAnimation(plugin_id));
|
let _ = senders.send_to_background_jobs(BackgroundJob::StopPluginLoadingAnimation(plugin_id));
|
||||||
loading_indication.indicate_loading_error(error.to_string());
|
loading_indication.indicate_loading_error(error.to_string());
|
||||||
let _ = senders.send_to_screen(ScreenInstruction::UpdatePluginLoadingStage(
|
let _ = senders.send_to_screen(ScreenInstruction::UpdatePluginLoadingStage(
|
||||||
|
|
@ -571,292 +575,6 @@ fn handle_plugin_loading_failure(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_plugin_instance(instance: &mut Instance) -> Result<()> {
|
|
||||||
let err_context = || format!("failed to load plugin from instance {instance:#?}");
|
|
||||||
|
|
||||||
let load_function = instance
|
|
||||||
.exports
|
|
||||||
.get_function("_start")
|
|
||||||
.with_context(err_context)?;
|
|
||||||
// This eventually calls the `.load()` method
|
|
||||||
load_function.call(&[]).with_context(err_context)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObject {
|
|
||||||
macro_rules! zellij_export {
|
|
||||||
($($host_function:ident),+ $(,)?) => {
|
|
||||||
imports! {
|
|
||||||
"zellij" => {
|
|
||||||
$(stringify!($host_function) =>
|
|
||||||
Function::new_native_with_env(store, plugin_env.clone(), $host_function),)+
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
zellij_export! {
|
|
||||||
host_subscribe,
|
|
||||||
host_unsubscribe,
|
|
||||||
host_set_selectable,
|
|
||||||
host_get_plugin_ids,
|
|
||||||
host_get_zellij_version,
|
|
||||||
host_open_file,
|
|
||||||
host_switch_tab_to,
|
|
||||||
host_set_timeout,
|
|
||||||
host_exec_cmd,
|
|
||||||
host_report_panic,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn host_subscribe(plugin_env: &PluginEnv) {
|
|
||||||
wasi_read_object::<HashSet<EventType>>(&plugin_env.wasi_env)
|
|
||||||
.and_then(|new| {
|
|
||||||
plugin_env.subscriptions.lock().to_anyhow()?.extend(new);
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.with_context(|| format!("failed to subscribe for plugin {}", plugin_env.name()))
|
|
||||||
.fatal();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn host_unsubscribe(plugin_env: &PluginEnv) {
|
|
||||||
wasi_read_object::<HashSet<EventType>>(&plugin_env.wasi_env)
|
|
||||||
.and_then(|old| {
|
|
||||||
plugin_env
|
|
||||||
.subscriptions
|
|
||||||
.lock()
|
|
||||||
.to_anyhow()?
|
|
||||||
.retain(|k| !old.contains(k));
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.with_context(|| format!("failed to unsubscribe for plugin {}", plugin_env.name()))
|
|
||||||
.fatal();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) {
|
|
||||||
match plugin_env.plugin.run {
|
|
||||||
PluginType::Pane(Some(tab_index)) => {
|
|
||||||
let selectable = selectable != 0;
|
|
||||||
plugin_env
|
|
||||||
.senders
|
|
||||||
.send_to_screen(ScreenInstruction::SetSelectable(
|
|
||||||
PaneId::Plugin(plugin_env.plugin_id),
|
|
||||||
selectable,
|
|
||||||
tab_index,
|
|
||||||
))
|
|
||||||
.with_context(|| {
|
|
||||||
format!(
|
|
||||||
"failed to set plugin {} selectable from plugin {}",
|
|
||||||
selectable,
|
|
||||||
plugin_env.name()
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.non_fatal();
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
debug!(
|
|
||||||
"{} - Calling method 'host_set_selectable' does nothing for headless plugins",
|
|
||||||
plugin_env.plugin.location
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn host_get_plugin_ids(plugin_env: &PluginEnv) {
|
|
||||||
let ids = PluginIds {
|
|
||||||
plugin_id: plugin_env.plugin_id,
|
|
||||||
zellij_pid: process::id(),
|
|
||||||
};
|
|
||||||
wasi_write_object(&plugin_env.wasi_env, &ids)
|
|
||||||
.with_context(|| {
|
|
||||||
format!(
|
|
||||||
"failed to query plugin IDs from host for plugin {}",
|
|
||||||
plugin_env.name()
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.non_fatal();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn host_get_zellij_version(plugin_env: &PluginEnv) {
|
|
||||||
wasi_write_object(&plugin_env.wasi_env, VERSION)
|
|
||||||
.with_context(|| {
|
|
||||||
format!(
|
|
||||||
"failed to request zellij version from host for plugin {}",
|
|
||||||
plugin_env.name()
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.non_fatal();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn host_open_file(plugin_env: &PluginEnv) {
|
|
||||||
wasi_read_object::<PathBuf>(&plugin_env.wasi_env)
|
|
||||||
.and_then(|path| {
|
|
||||||
plugin_env
|
|
||||||
.senders
|
|
||||||
.send_to_pty(PtyInstruction::SpawnTerminal(
|
|
||||||
Some(TerminalAction::OpenFile(path, None, None)),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
ClientOrTabIndex::TabIndex(plugin_env.tab_index),
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.with_context(|| {
|
|
||||||
format!(
|
|
||||||
"failed to open file on host from plugin {}",
|
|
||||||
plugin_env.name()
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.non_fatal();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn host_switch_tab_to(plugin_env: &PluginEnv, tab_idx: u32) {
|
|
||||||
plugin_env
|
|
||||||
.senders
|
|
||||||
.send_to_screen(ScreenInstruction::GoToTab(
|
|
||||||
tab_idx,
|
|
||||||
Some(plugin_env.client_id),
|
|
||||||
))
|
|
||||||
.with_context(|| {
|
|
||||||
format!(
|
|
||||||
"failed to switch host to tab {tab_idx} from plugin {}",
|
|
||||||
plugin_env.name()
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.non_fatal();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) {
|
|
||||||
// There is a fancy, high-performance way to do this with zero additional threads:
|
|
||||||
// If the plugin thread keeps a BinaryHeap of timer structs, it can manage multiple and easily `.peek()` at the
|
|
||||||
// next time to trigger in O(1) time. Once the wake-up time is known, the `wasm` thread can use `recv_timeout()`
|
|
||||||
// to wait for an event with the timeout set to be the time of the next wake up. If events come in in the meantime,
|
|
||||||
// they are handled, but if the timeout triggers, we replace the event from `recv()` with an
|
|
||||||
// `Update(pid, TimerEvent)` and pop the timer from the Heap (or reschedule it). No additional threads for as many
|
|
||||||
// timers as we'd like.
|
|
||||||
//
|
|
||||||
// But that's a lot of code, and this is a few lines:
|
|
||||||
let send_plugin_instructions = plugin_env.senders.to_plugin.clone();
|
|
||||||
let update_target = Some(plugin_env.plugin_id);
|
|
||||||
let client_id = plugin_env.client_id;
|
|
||||||
let plugin_name = plugin_env.name();
|
|
||||||
thread::spawn(move || {
|
|
||||||
let start_time = Instant::now();
|
|
||||||
thread::sleep(Duration::from_secs_f64(secs));
|
|
||||||
// FIXME: The way that elapsed time is being calculated here is not exact; it doesn't take into account the
|
|
||||||
// time it takes an event to actually reach the plugin after it's sent to the `wasm` thread.
|
|
||||||
let elapsed_time = Instant::now().duration_since(start_time).as_secs_f64();
|
|
||||||
|
|
||||||
send_plugin_instructions
|
|
||||||
.ok_or(anyhow!("found no sender to send plugin instruction to"))
|
|
||||||
.and_then(|sender| {
|
|
||||||
sender
|
|
||||||
.send(PluginInstruction::Update(vec![(
|
|
||||||
update_target,
|
|
||||||
Some(client_id),
|
|
||||||
Event::Timer(elapsed_time),
|
|
||||||
)]))
|
|
||||||
.to_anyhow()
|
|
||||||
})
|
|
||||||
.with_context(|| {
|
|
||||||
format!(
|
|
||||||
"failed to set host timeout of {secs} s for plugin {}",
|
|
||||||
plugin_name
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.non_fatal();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn host_exec_cmd(plugin_env: &PluginEnv) {
|
|
||||||
let err_context = || {
|
|
||||||
format!(
|
|
||||||
"failed to execute command on host for plugin '{}'",
|
|
||||||
plugin_env.name()
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut cmdline: Vec<String> = wasi_read_object(&plugin_env.wasi_env)
|
|
||||||
.with_context(err_context)
|
|
||||||
.fatal();
|
|
||||||
let command = cmdline.remove(0);
|
|
||||||
|
|
||||||
// Bail out if we're forbidden to run command
|
|
||||||
if !plugin_env.plugin._allow_exec_host_cmd {
|
|
||||||
warn!("This plugin isn't allow to run command in host side, skip running this command: '{cmd} {args}'.",
|
|
||||||
cmd = command, args = cmdline.join(" "));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Here, we don't wait the command to finish
|
|
||||||
process::Command::new(command)
|
|
||||||
.args(cmdline)
|
|
||||||
.spawn()
|
|
||||||
.with_context(err_context)
|
|
||||||
.non_fatal();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom panic handler for plugins.
|
|
||||||
//
|
|
||||||
// This is called when a panic occurs in a plugin. Since most panics will likely originate in the
|
|
||||||
// code trying to deserialize an `Event` upon a plugin state update, we read some panic message,
|
|
||||||
// formatted as string from the plugin.
|
|
||||||
fn host_report_panic(plugin_env: &PluginEnv) {
|
|
||||||
let msg = wasi_read_string(&plugin_env.wasi_env)
|
|
||||||
.with_context(|| format!("failed to report panic for plugin '{}'", plugin_env.name()))
|
|
||||||
.fatal();
|
|
||||||
panic!("{}", msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper Functions ---------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
pub fn wasi_read_string(wasi_env: &WasiEnv) -> Result<String> {
|
|
||||||
let err_context = || format!("failed to read string from WASI env '{wasi_env:?}'");
|
|
||||||
|
|
||||||
let mut buf = String::new();
|
|
||||||
wasi_env
|
|
||||||
.state()
|
|
||||||
.fs
|
|
||||||
.stdout_mut()
|
|
||||||
.map_err(anyError::new)
|
|
||||||
.and_then(|stdout| {
|
|
||||||
stdout
|
|
||||||
.as_mut()
|
|
||||||
.ok_or(anyhow!("failed to get mutable reference to stdout"))
|
|
||||||
})
|
|
||||||
.and_then(|wasi_file| wasi_file.read_to_string(&mut buf).map_err(anyError::new))
|
|
||||||
.with_context(err_context)?;
|
|
||||||
// https://stackoverflow.com/questions/66450942/in-rust-is-there-a-way-to-make-literal-newlines-in-r-using-windows-c
|
|
||||||
Ok(buf.replace("\n", "\n\r"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wasi_write_string(wasi_env: &WasiEnv, buf: &str) -> Result<()> {
|
|
||||||
wasi_env
|
|
||||||
.state()
|
|
||||||
.fs
|
|
||||||
.stdin_mut()
|
|
||||||
.map_err(anyError::new)
|
|
||||||
.and_then(|stdin| {
|
|
||||||
stdin
|
|
||||||
.as_mut()
|
|
||||||
.ok_or(anyhow!("failed to get mutable reference to stdin"))
|
|
||||||
})
|
|
||||||
.and_then(|stdin| writeln!(stdin, "{}\r", buf).map_err(anyError::new))
|
|
||||||
.with_context(|| format!("failed to write string to WASI env '{wasi_env:?}'"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wasi_write_object(wasi_env: &WasiEnv, object: &(impl Serialize + ?Sized)) -> Result<()> {
|
|
||||||
serde_json::to_string(&object)
|
|
||||||
.map_err(anyError::new)
|
|
||||||
.and_then(|string| wasi_write_string(wasi_env, &string))
|
|
||||||
.with_context(|| format!("failed to serialize object for WASI env '{wasi_env:?}'"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wasi_read_object<T: DeserializeOwned>(wasi_env: &WasiEnv) -> Result<T> {
|
|
||||||
wasi_read_string(wasi_env)
|
|
||||||
.and_then(|string| serde_json::from_str(&string).map_err(anyError::new))
|
|
||||||
.with_context(|| format!("failed to deserialize object from WASI env '{wasi_env:?}'"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply_event_to_plugin(
|
pub fn apply_event_to_plugin(
|
||||||
plugin_id: u32,
|
plugin_id: u32,
|
||||||
client_id: ClientId,
|
client_id: ClientId,
|
||||||
|
|
|
||||||
326
zellij-server/src/plugins/zellij_exports.rs
Normal file
326
zellij-server/src/plugins/zellij_exports.rs
Normal file
|
|
@ -0,0 +1,326 @@
|
||||||
|
use super::PluginInstruction;
|
||||||
|
use crate::plugins::plugin_map::{PluginEnv, Subscriptions};
|
||||||
|
use log::{debug, warn};
|
||||||
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
path::PathBuf,
|
||||||
|
process,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
thread,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
use wasmer::{imports, Function, ImportObject, Store, WasmerEnv};
|
||||||
|
use wasmer_wasi::WasiEnv;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
panes::PaneId,
|
||||||
|
pty::{ClientOrTabIndex, PtyInstruction},
|
||||||
|
screen::ScreenInstruction,
|
||||||
|
};
|
||||||
|
|
||||||
|
use zellij_utils::{
|
||||||
|
consts::VERSION,
|
||||||
|
data::{Event, EventType, PluginIds},
|
||||||
|
errors::prelude::*,
|
||||||
|
input::{command::TerminalAction, plugins::PluginType},
|
||||||
|
serde,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn zellij_exports(
|
||||||
|
store: &Store,
|
||||||
|
plugin_env: &PluginEnv,
|
||||||
|
subscriptions: &Arc<Mutex<Subscriptions>>,
|
||||||
|
) -> ImportObject {
|
||||||
|
macro_rules! zellij_export {
|
||||||
|
($($host_function:ident),+ $(,)?) => {
|
||||||
|
imports! {
|
||||||
|
"zellij" => {
|
||||||
|
$(stringify!($host_function) =>
|
||||||
|
Function::new_native_with_env(store, ForeignFunctionEnv::new(plugin_env, subscriptions), $host_function),)+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zellij_export! {
|
||||||
|
host_subscribe,
|
||||||
|
host_unsubscribe,
|
||||||
|
host_set_selectable,
|
||||||
|
host_get_plugin_ids,
|
||||||
|
host_get_zellij_version,
|
||||||
|
host_open_file,
|
||||||
|
host_switch_tab_to,
|
||||||
|
host_set_timeout,
|
||||||
|
host_exec_cmd,
|
||||||
|
host_report_panic,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(WasmerEnv, Clone)]
|
||||||
|
pub struct ForeignFunctionEnv {
|
||||||
|
pub plugin_env: PluginEnv,
|
||||||
|
pub subscriptions: Arc<Mutex<Subscriptions>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ForeignFunctionEnv {
|
||||||
|
pub fn new(plugin_env: &PluginEnv, subscriptions: &Arc<Mutex<Subscriptions>>) -> Self {
|
||||||
|
ForeignFunctionEnv {
|
||||||
|
plugin_env: plugin_env.clone(),
|
||||||
|
subscriptions: subscriptions.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn host_subscribe(env: &ForeignFunctionEnv) {
|
||||||
|
wasi_read_object::<HashSet<EventType>>(&env.plugin_env.wasi_env)
|
||||||
|
.and_then(|new| {
|
||||||
|
env.subscriptions.lock().to_anyhow()?.extend(new);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.with_context(|| format!("failed to subscribe for plugin {}", env.plugin_env.name()))
|
||||||
|
.fatal();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn host_unsubscribe(env: &ForeignFunctionEnv) {
|
||||||
|
wasi_read_object::<HashSet<EventType>>(&env.plugin_env.wasi_env)
|
||||||
|
.and_then(|old| {
|
||||||
|
env.subscriptions
|
||||||
|
.lock()
|
||||||
|
.to_anyhow()?
|
||||||
|
.retain(|k| !old.contains(k));
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.with_context(|| format!("failed to unsubscribe for plugin {}", env.plugin_env.name()))
|
||||||
|
.fatal();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn host_set_selectable(env: &ForeignFunctionEnv, selectable: i32) {
|
||||||
|
match env.plugin_env.plugin.run {
|
||||||
|
PluginType::Pane(Some(tab_index)) => {
|
||||||
|
let selectable = selectable != 0;
|
||||||
|
env.plugin_env
|
||||||
|
.senders
|
||||||
|
.send_to_screen(ScreenInstruction::SetSelectable(
|
||||||
|
PaneId::Plugin(env.plugin_env.plugin_id),
|
||||||
|
selectable,
|
||||||
|
tab_index,
|
||||||
|
))
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to set plugin {} selectable from plugin {}",
|
||||||
|
selectable,
|
||||||
|
env.plugin_env.name()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.non_fatal();
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
debug!(
|
||||||
|
"{} - Calling method 'host_set_selectable' does nothing for headless plugins",
|
||||||
|
env.plugin_env.plugin.location
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn host_get_plugin_ids(env: &ForeignFunctionEnv) {
|
||||||
|
let ids = PluginIds {
|
||||||
|
plugin_id: env.plugin_env.plugin_id,
|
||||||
|
zellij_pid: process::id(),
|
||||||
|
};
|
||||||
|
wasi_write_object(&env.plugin_env.wasi_env, &ids)
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to query plugin IDs from host for plugin {}",
|
||||||
|
env.plugin_env.name()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.non_fatal();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn host_get_zellij_version(env: &ForeignFunctionEnv) {
|
||||||
|
wasi_write_object(&env.plugin_env.wasi_env, VERSION)
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to request zellij version from host for plugin {}",
|
||||||
|
env.plugin_env.name()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.non_fatal();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn host_open_file(env: &ForeignFunctionEnv) {
|
||||||
|
wasi_read_object::<PathBuf>(&env.plugin_env.wasi_env)
|
||||||
|
.and_then(|path| {
|
||||||
|
env.plugin_env
|
||||||
|
.senders
|
||||||
|
.send_to_pty(PtyInstruction::SpawnTerminal(
|
||||||
|
Some(TerminalAction::OpenFile(path, None, None)),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
ClientOrTabIndex::TabIndex(env.plugin_env.tab_index),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to open file on host from plugin {}",
|
||||||
|
env.plugin_env.name()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.non_fatal();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn host_switch_tab_to(env: &ForeignFunctionEnv, tab_idx: u32) {
|
||||||
|
env.plugin_env
|
||||||
|
.senders
|
||||||
|
.send_to_screen(ScreenInstruction::GoToTab(
|
||||||
|
tab_idx,
|
||||||
|
Some(env.plugin_env.client_id),
|
||||||
|
))
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to switch host to tab {tab_idx} from plugin {}",
|
||||||
|
env.plugin_env.name()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.non_fatal();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn host_set_timeout(env: &ForeignFunctionEnv, secs: f64) {
|
||||||
|
// There is a fancy, high-performance way to do this with zero additional threads:
|
||||||
|
// If the plugin thread keeps a BinaryHeap of timer structs, it can manage multiple and easily `.peek()` at the
|
||||||
|
// next time to trigger in O(1) time. Once the wake-up time is known, the `wasm` thread can use `recv_timeout()`
|
||||||
|
// to wait for an event with the timeout set to be the time of the next wake up. If events come in in the meantime,
|
||||||
|
// they are handled, but if the timeout triggers, we replace the event from `recv()` with an
|
||||||
|
// `Update(pid, TimerEvent)` and pop the timer from the Heap (or reschedule it). No additional threads for as many
|
||||||
|
// timers as we'd like.
|
||||||
|
//
|
||||||
|
// But that's a lot of code, and this is a few lines:
|
||||||
|
let send_plugin_instructions = env.plugin_env.senders.to_plugin.clone();
|
||||||
|
let update_target = Some(env.plugin_env.plugin_id);
|
||||||
|
let client_id = env.plugin_env.client_id;
|
||||||
|
let plugin_name = env.plugin_env.name();
|
||||||
|
thread::spawn(move || {
|
||||||
|
let start_time = Instant::now();
|
||||||
|
thread::sleep(Duration::from_secs_f64(secs));
|
||||||
|
// FIXME: The way that elapsed time is being calculated here is not exact; it doesn't take into account the
|
||||||
|
// time it takes an event to actually reach the plugin after it's sent to the `wasm` thread.
|
||||||
|
let elapsed_time = Instant::now().duration_since(start_time).as_secs_f64();
|
||||||
|
|
||||||
|
send_plugin_instructions
|
||||||
|
.ok_or(anyhow!("found no sender to send plugin instruction to"))
|
||||||
|
.and_then(|sender| {
|
||||||
|
sender
|
||||||
|
.send(PluginInstruction::Update(vec![(
|
||||||
|
update_target,
|
||||||
|
Some(client_id),
|
||||||
|
Event::Timer(elapsed_time),
|
||||||
|
)]))
|
||||||
|
.to_anyhow()
|
||||||
|
})
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to set host timeout of {secs} s for plugin {}",
|
||||||
|
plugin_name
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.non_fatal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn host_exec_cmd(env: &ForeignFunctionEnv) {
|
||||||
|
let err_context = || {
|
||||||
|
format!(
|
||||||
|
"failed to execute command on host for plugin '{}'",
|
||||||
|
env.plugin_env.name()
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut cmdline: Vec<String> = wasi_read_object(&env.plugin_env.wasi_env)
|
||||||
|
.with_context(err_context)
|
||||||
|
.fatal();
|
||||||
|
let command = cmdline.remove(0);
|
||||||
|
|
||||||
|
// Bail out if we're forbidden to run command
|
||||||
|
if !env.plugin_env.plugin._allow_exec_host_cmd {
|
||||||
|
warn!("This plugin isn't allow to run command in host side, skip running this command: '{cmd} {args}'.",
|
||||||
|
cmd = command, args = cmdline.join(" "));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here, we don't wait the command to finish
|
||||||
|
process::Command::new(command)
|
||||||
|
.args(cmdline)
|
||||||
|
.spawn()
|
||||||
|
.with_context(err_context)
|
||||||
|
.non_fatal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom panic handler for plugins.
|
||||||
|
//
|
||||||
|
// This is called when a panic occurs in a plugin. Since most panics will likely originate in the
|
||||||
|
// code trying to deserialize an `Event` upon a plugin state update, we read some panic message,
|
||||||
|
// formatted as string from the plugin.
|
||||||
|
fn host_report_panic(env: &ForeignFunctionEnv) {
|
||||||
|
let msg = wasi_read_string(&env.plugin_env.wasi_env)
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to report panic for plugin '{}'",
|
||||||
|
env.plugin_env.name()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.fatal();
|
||||||
|
panic!("{}", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper Functions ---------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub fn wasi_read_string(wasi_env: &WasiEnv) -> Result<String> {
|
||||||
|
let err_context = || format!("failed to read string from WASI env '{wasi_env:?}'");
|
||||||
|
|
||||||
|
let mut buf = vec![];
|
||||||
|
wasi_env
|
||||||
|
.state()
|
||||||
|
.fs
|
||||||
|
.stdout_mut()
|
||||||
|
.map_err(anyError::new)
|
||||||
|
.and_then(|stdout| {
|
||||||
|
stdout
|
||||||
|
.as_mut()
|
||||||
|
.ok_or(anyhow!("failed to get mutable reference to stdout"))
|
||||||
|
})
|
||||||
|
.and_then(|wasi_file| wasi_file.read_to_end(&mut buf).map_err(anyError::new))
|
||||||
|
.with_context(err_context)?;
|
||||||
|
let buf = String::from_utf8_lossy(&buf);
|
||||||
|
// https://stackoverflow.com/questions/66450942/in-rust-is-there-a-way-to-make-literal-newlines-in-r-using-windows-c
|
||||||
|
Ok(buf.replace("\n", "\n\r"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wasi_write_string(wasi_env: &WasiEnv, buf: &str) -> Result<()> {
|
||||||
|
wasi_env
|
||||||
|
.state()
|
||||||
|
.fs
|
||||||
|
.stdin_mut()
|
||||||
|
.map_err(anyError::new)
|
||||||
|
.and_then(|stdin| {
|
||||||
|
stdin
|
||||||
|
.as_mut()
|
||||||
|
.ok_or(anyhow!("failed to get mutable reference to stdin"))
|
||||||
|
})
|
||||||
|
.and_then(|stdin| writeln!(stdin, "{}\r", buf).map_err(anyError::new))
|
||||||
|
.with_context(|| format!("failed to write string to WASI env '{wasi_env:?}'"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wasi_write_object(wasi_env: &WasiEnv, object: &(impl Serialize + ?Sized)) -> Result<()> {
|
||||||
|
serde_json::to_string(&object)
|
||||||
|
.map_err(anyError::new)
|
||||||
|
.and_then(|string| wasi_write_string(wasi_env, &string))
|
||||||
|
.with_context(|| format!("failed to serialize object for WASI env '{wasi_env:?}'"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wasi_read_object<T: DeserializeOwned>(wasi_env: &WasiEnv) -> Result<T> {
|
||||||
|
wasi_read_string(wasi_env)
|
||||||
|
.and_then(|string| serde_json::from_str(&string).map_err(anyError::new))
|
||||||
|
.with_context(|| format!("failed to deserialize object from WASI env '{wasi_env:?}'"))
|
||||||
|
}
|
||||||
|
|
@ -699,7 +699,6 @@ pub(crate) fn route_action(
|
||||||
.send_to_screen(ScreenInstruction::StartOrReloadPluginPane(
|
.send_to_screen(ScreenInstruction::StartOrReloadPluginPane(
|
||||||
run_plugin_location,
|
run_plugin_location,
|
||||||
None,
|
None,
|
||||||
client_id,
|
|
||||||
))
|
))
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -260,7 +260,7 @@ pub enum ScreenInstruction {
|
||||||
NewTiledPluginPane(RunPluginLocation, Option<String>, ClientId), // Option<String> is
|
NewTiledPluginPane(RunPluginLocation, Option<String>, ClientId), // Option<String> is
|
||||||
// optional pane title
|
// optional pane title
|
||||||
NewFloatingPluginPane(RunPluginLocation, Option<String>, ClientId), // Option<String> is an
|
NewFloatingPluginPane(RunPluginLocation, Option<String>, ClientId), // Option<String> is an
|
||||||
StartOrReloadPluginPane(RunPluginLocation, Option<String>, ClientId), // Option<String> is
|
StartOrReloadPluginPane(RunPluginLocation, Option<String>),
|
||||||
// optional pane title
|
// optional pane title
|
||||||
AddPlugin(
|
AddPlugin(
|
||||||
Option<bool>, // should_float
|
Option<bool>, // should_float
|
||||||
|
|
@ -2528,11 +2528,7 @@ pub(crate) fn screen_thread_main(
|
||||||
size,
|
size,
|
||||||
))?;
|
))?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::StartOrReloadPluginPane(
|
ScreenInstruction::StartOrReloadPluginPane(run_plugin_location, pane_title) => {
|
||||||
run_plugin_location,
|
|
||||||
pane_title,
|
|
||||||
client_id,
|
|
||||||
) => {
|
|
||||||
let tab_index = screen.active_tab_indices.values().next().unwrap_or(&1);
|
let tab_index = screen.active_tab_indices.values().next().unwrap_or(&1);
|
||||||
let size = Size::default();
|
let size = Size::default();
|
||||||
let should_float = Some(false);
|
let should_float = Some(false);
|
||||||
|
|
@ -2548,7 +2544,6 @@ pub(crate) fn screen_thread_main(
|
||||||
pane_title,
|
pane_title,
|
||||||
run_plugin,
|
run_plugin,
|
||||||
*tab_index,
|
*tab_index,
|
||||||
client_id,
|
|
||||||
size,
|
size,
|
||||||
))?;
|
))?;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue