feat(config): allow loading background plugins on startup (#3616)
* remove old partial implementation * feat(plugins): allow loading background plugins on startup * add e2e test * update config * udpate config merging * style(fmt): rustfmt
This commit is contained in:
parent
d92ee89a9d
commit
ce8e3995df
23 changed files with 839 additions and 103 deletions
|
|
@ -2371,3 +2371,55 @@ pub fn send_command_through_the_cli() {
|
||||||
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
pub fn load_plugins_in_background_on_startup() {
|
||||||
|
let fake_win_size = Size {
|
||||||
|
cols: 120,
|
||||||
|
rows: 24,
|
||||||
|
};
|
||||||
|
let config_file_name = "load_background_plugins.kdl";
|
||||||
|
let mut test_attempts = 10;
|
||||||
|
let mut test_timed_out = false;
|
||||||
|
let last_snapshot = loop {
|
||||||
|
RemoteRunner::kill_running_sessions(fake_win_size);
|
||||||
|
let mut runner =
|
||||||
|
RemoteRunner::new_with_config(fake_win_size, config_file_name).add_step(Step {
|
||||||
|
name: "Wait for plugin to load and request permissions",
|
||||||
|
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||||
|
let mut step_is_complete = false;
|
||||||
|
if remote_terminal.snapshot_contains("Allow? (y/n)") {
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
|
remote_terminal.send_key("y".as_bytes());
|
||||||
|
step_is_complete = true;
|
||||||
|
}
|
||||||
|
step_is_complete
|
||||||
|
},
|
||||||
|
});
|
||||||
|
runner.run_all_steps();
|
||||||
|
let last_snapshot = runner.take_snapshot_after(Step {
|
||||||
|
name: "Wait for plugin to disappear after permissions were granted",
|
||||||
|
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||||
|
let mut step_is_complete = false;
|
||||||
|
if !remote_terminal.snapshot_contains("Allow? (y/n)") {
|
||||||
|
step_is_complete = true;
|
||||||
|
}
|
||||||
|
step_is_complete
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if runner.test_timed_out && test_attempts > 0 {
|
||||||
|
test_attempts -= 1;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
test_timed_out = runner.test_timed_out;
|
||||||
|
break last_snapshot;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let last_snapshot = account_for_races_in_snapshot(last_snapshot);
|
||||||
|
assert!(
|
||||||
|
!test_timed_out,
|
||||||
|
"Test timed out, possibly waiting for permission request"
|
||||||
|
);
|
||||||
|
assert_snapshot!(last_snapshot);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,13 @@ fn stop_zellij(channel: &mut ssh2::Channel) {
|
||||||
channel.write_all(b"rm -rf /tmp/*\n").unwrap(); // remove temporary artifacts from previous
|
channel.write_all(b"rm -rf /tmp/*\n").unwrap(); // remove temporary artifacts from previous
|
||||||
// tests
|
// tests
|
||||||
channel.write_all(b"rm -rf /tmp/*\n").unwrap(); // remove temporary artifacts from previous
|
channel.write_all(b"rm -rf /tmp/*\n").unwrap(); // remove temporary artifacts from previous
|
||||||
|
channel.write_all(b"rm -rf /tmp/*\n").unwrap(); // remove temporary artifacts from previous
|
||||||
channel
|
channel
|
||||||
.write_all(b"rm -rf ~/.cache/zellij/*/session_info\n")
|
.write_all(b"rm -rf ~/.cache/zellij/*/session_info\n")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
channel
|
||||||
|
.write_all(b"rm -rf ~/.cache/zellij/permissions.kdl\n")
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_zellij(channel: &mut ssh2::Channel) {
|
fn start_zellij(channel: &mut ssh2::Channel) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
---
|
||||||
|
source: src/tests/e2e/cases.rs
|
||||||
|
assertion_line: 2426
|
||||||
|
expression: last_snapshot
|
||||||
|
---
|
||||||
|
Zellij (e2e-test) Tab #1
|
||||||
|
┌ Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│$ █ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
Ctrl g UNLOCK Alt + <n> New Pane <←↓↑→> Change Focus <f> Floating
|
||||||
392
src/tests/fixtures/configs/load_background_plugins.kdl
vendored
Normal file
392
src/tests/fixtures/configs/load_background_plugins.kdl
vendored
Normal file
|
|
@ -0,0 +1,392 @@
|
||||||
|
keybinds clear-defaults=true {
|
||||||
|
locked {
|
||||||
|
bind "Ctrl g" { SwitchToMode "normal"; }
|
||||||
|
}
|
||||||
|
pane {
|
||||||
|
bind "left" { MoveFocus "left"; }
|
||||||
|
bind "down" { MoveFocus "down"; }
|
||||||
|
bind "up" { MoveFocus "up"; }
|
||||||
|
bind "right" { MoveFocus "right"; }
|
||||||
|
bind "c" { SwitchToMode "renamepane"; PaneNameInput 0; }
|
||||||
|
bind "d" { NewPane "down"; SwitchToMode "locked"; }
|
||||||
|
bind "e" { TogglePaneEmbedOrFloating; SwitchToMode "locked"; }
|
||||||
|
bind "f" { ToggleFocusFullscreen; SwitchToMode "locked"; }
|
||||||
|
bind "h" { MoveFocus "left"; }
|
||||||
|
bind "j" { MoveFocus "down"; }
|
||||||
|
bind "k" { MoveFocus "up"; }
|
||||||
|
bind "l" { MoveFocus "right"; }
|
||||||
|
bind "n" { NewPane; SwitchToMode "locked"; }
|
||||||
|
bind "p" { SwitchToMode "normal"; }
|
||||||
|
bind "r" { NewPane "right"; SwitchToMode "locked"; }
|
||||||
|
bind "w" { ToggleFloatingPanes; SwitchToMode "locked"; }
|
||||||
|
bind "x" { CloseFocus; SwitchToMode "locked"; }
|
||||||
|
bind "z" { TogglePaneFrames; SwitchToMode "locked"; }
|
||||||
|
bind "tab" { SwitchFocus; }
|
||||||
|
}
|
||||||
|
tab {
|
||||||
|
bind "left" { GoToPreviousTab; }
|
||||||
|
bind "down" { GoToNextTab; }
|
||||||
|
bind "up" { GoToPreviousTab; }
|
||||||
|
bind "right" { GoToNextTab; }
|
||||||
|
bind "1" { GoToTab 1; SwitchToMode "locked"; }
|
||||||
|
bind "2" { GoToTab 2; SwitchToMode "locked"; }
|
||||||
|
bind "3" { GoToTab 3; SwitchToMode "locked"; }
|
||||||
|
bind "4" { GoToTab 4; SwitchToMode "locked"; }
|
||||||
|
bind "5" { GoToTab 5; SwitchToMode "locked"; }
|
||||||
|
bind "6" { GoToTab 6; SwitchToMode "locked"; }
|
||||||
|
bind "7" { GoToTab 7; SwitchToMode "locked"; }
|
||||||
|
bind "8" { GoToTab 8; SwitchToMode "locked"; }
|
||||||
|
bind "9" { GoToTab 9; SwitchToMode "locked"; }
|
||||||
|
bind "[" { BreakPaneLeft; SwitchToMode "locked"; }
|
||||||
|
bind "]" { BreakPaneRight; SwitchToMode "locked"; }
|
||||||
|
bind "b" { BreakPane; SwitchToMode "locked"; }
|
||||||
|
bind "h" { GoToPreviousTab; }
|
||||||
|
bind "j" { GoToNextTab; }
|
||||||
|
bind "k" { GoToPreviousTab; }
|
||||||
|
bind "l" { GoToNextTab; }
|
||||||
|
bind "n" { NewTab; SwitchToMode "locked"; }
|
||||||
|
bind "r" { SwitchToMode "renametab"; TabNameInput 0; }
|
||||||
|
bind "s" { ToggleActiveSyncTab; SwitchToMode "locked"; }
|
||||||
|
bind "t" { SwitchToMode "normal"; }
|
||||||
|
bind "x" { CloseTab; SwitchToMode "locked"; }
|
||||||
|
bind "tab" { ToggleTab; }
|
||||||
|
}
|
||||||
|
resize {
|
||||||
|
bind "left" { Resize "Increase left"; }
|
||||||
|
bind "down" { Resize "Increase down"; }
|
||||||
|
bind "up" { Resize "Increase up"; }
|
||||||
|
bind "right" { Resize "Increase right"; }
|
||||||
|
bind "+" { Resize "Increase"; }
|
||||||
|
bind "-" { Resize "Decrease"; }
|
||||||
|
bind "=" { Resize "Increase"; }
|
||||||
|
bind "H" { Resize "Decrease left"; }
|
||||||
|
bind "J" { Resize "Decrease down"; }
|
||||||
|
bind "K" { Resize "Decrease up"; }
|
||||||
|
bind "L" { Resize "Decrease right"; }
|
||||||
|
bind "h" { Resize "Increase left"; }
|
||||||
|
bind "j" { Resize "Increase down"; }
|
||||||
|
bind "k" { Resize "Increase up"; }
|
||||||
|
bind "l" { Resize "Increase right"; }
|
||||||
|
bind "r" { SwitchToMode "normal"; }
|
||||||
|
}
|
||||||
|
move {
|
||||||
|
bind "left" { MovePane "left"; }
|
||||||
|
bind "down" { MovePane "down"; }
|
||||||
|
bind "up" { MovePane "up"; }
|
||||||
|
bind "right" { MovePane "right"; }
|
||||||
|
bind "h" { MovePane "left"; }
|
||||||
|
bind "j" { MovePane "down"; }
|
||||||
|
bind "k" { MovePane "up"; }
|
||||||
|
bind "l" { MovePane "right"; }
|
||||||
|
bind "m" { SwitchToMode "normal"; }
|
||||||
|
bind "n" { MovePane; }
|
||||||
|
bind "p" { MovePaneBackwards; }
|
||||||
|
bind "tab" { MovePane; }
|
||||||
|
}
|
||||||
|
scroll {
|
||||||
|
bind "e" { EditScrollback; SwitchToMode "locked"; }
|
||||||
|
bind "f" { SwitchToMode "entersearch"; SearchInput 0; }
|
||||||
|
bind "s" { SwitchToMode "normal"; }
|
||||||
|
}
|
||||||
|
search {
|
||||||
|
bind "c" { SearchToggleOption "CaseSensitivity"; }
|
||||||
|
bind "n" { Search "down"; }
|
||||||
|
bind "o" { SearchToggleOption "WholeWord"; }
|
||||||
|
bind "p" { Search "up"; }
|
||||||
|
bind "w" { SearchToggleOption "Wrap"; }
|
||||||
|
}
|
||||||
|
session {
|
||||||
|
bind "c" {
|
||||||
|
LaunchOrFocusPlugin "configuration" {
|
||||||
|
floating true
|
||||||
|
move_to_focused_tab true
|
||||||
|
}
|
||||||
|
SwitchToMode "locked"
|
||||||
|
}
|
||||||
|
bind "d" { Detach; }
|
||||||
|
bind "o" { SwitchToMode "normal"; }
|
||||||
|
bind "Ctrl s" { SwitchToMode "scroll"; }
|
||||||
|
bind "w" {
|
||||||
|
LaunchOrFocusPlugin "session-manager" {
|
||||||
|
floating true
|
||||||
|
move_to_focused_tab true
|
||||||
|
}
|
||||||
|
SwitchToMode "locked"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shared_among "normal" "locked" {
|
||||||
|
bind "Alt left" { MoveFocusOrTab "left"; }
|
||||||
|
bind "Alt down" { MoveFocus "down"; }
|
||||||
|
bind "Alt up" { MoveFocus "up"; }
|
||||||
|
bind "Alt right" { MoveFocusOrTab "right"; }
|
||||||
|
bind "Alt +" { Resize "Increase"; }
|
||||||
|
bind "Alt -" { Resize "Decrease"; }
|
||||||
|
bind "Alt =" { Resize "Increase"; }
|
||||||
|
bind "Alt [" { PreviousSwapLayout; }
|
||||||
|
bind "Alt ]" { NextSwapLayout; }
|
||||||
|
bind "Alt f" { ToggleFloatingPanes; }
|
||||||
|
bind "Alt w" {
|
||||||
|
LaunchPlugin "filepicker" {
|
||||||
|
close_on_selection true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bind "Alt h" { MoveFocusOrTab "left"; }
|
||||||
|
bind "Alt i" { MoveTab "left"; }
|
||||||
|
bind "Alt j" { MoveFocus "down"; }
|
||||||
|
bind "Alt k" { MoveFocus "up"; }
|
||||||
|
bind "Alt l" { MoveFocusOrTab "right"; }
|
||||||
|
bind "Alt n" { NewPane; }
|
||||||
|
bind "Alt o" { MoveTab "right"; }
|
||||||
|
}
|
||||||
|
shared_except "locked" "renametab" "renamepane" {
|
||||||
|
bind "Ctrl g" { SwitchToMode "locked"; }
|
||||||
|
bind "Ctrl q" { Quit; }
|
||||||
|
}
|
||||||
|
shared_except "locked" "entersearch" {
|
||||||
|
bind "enter" { SwitchToMode "locked"; }
|
||||||
|
}
|
||||||
|
shared_except "locked" "entersearch" "renametab" "renamepane" "move" {
|
||||||
|
bind "m" { SwitchToMode "move"; }
|
||||||
|
}
|
||||||
|
shared_except "locked" "entersearch" "search" "renametab" "renamepane" "session" {
|
||||||
|
bind "o" { SwitchToMode "session"; }
|
||||||
|
}
|
||||||
|
shared_except "locked" "tab" "entersearch" "renametab" "renamepane" {
|
||||||
|
bind "t" { SwitchToMode "tab"; }
|
||||||
|
}
|
||||||
|
shared_except "locked" "tab" "scroll" "entersearch" "renametab" "renamepane" {
|
||||||
|
bind "s" { SwitchToMode "scroll"; }
|
||||||
|
}
|
||||||
|
shared_except "locked" "pane" "entersearch" "search" "renametab" "renamepane" "move" {
|
||||||
|
bind "p" { SwitchToMode "pane"; }
|
||||||
|
}
|
||||||
|
shared_except "locked" "resize" "pane" "tab" "entersearch" "renametab" "renamepane" {
|
||||||
|
bind "r" { SwitchToMode "resize"; }
|
||||||
|
}
|
||||||
|
shared_among "scroll" "search" {
|
||||||
|
bind "PageDown" { PageScrollDown; }
|
||||||
|
bind "PageUp" { PageScrollUp; }
|
||||||
|
bind "left" { PageScrollUp; }
|
||||||
|
bind "down" { ScrollDown; }
|
||||||
|
bind "up" { ScrollUp; }
|
||||||
|
bind "right" { PageScrollDown; }
|
||||||
|
bind "Ctrl b" { PageScrollUp; }
|
||||||
|
bind "Ctrl c" { ScrollToBottom; SwitchToMode "locked"; }
|
||||||
|
bind "d" { HalfPageScrollDown; }
|
||||||
|
bind "Ctrl f" { PageScrollDown; }
|
||||||
|
bind "h" { PageScrollUp; }
|
||||||
|
bind "j" { ScrollDown; }
|
||||||
|
bind "k" { ScrollUp; }
|
||||||
|
bind "l" { PageScrollDown; }
|
||||||
|
bind "u" { HalfPageScrollUp; }
|
||||||
|
}
|
||||||
|
entersearch {
|
||||||
|
bind "Ctrl c" { SwitchToMode "scroll"; }
|
||||||
|
bind "esc" { SwitchToMode "scroll"; }
|
||||||
|
bind "enter" { SwitchToMode "search"; }
|
||||||
|
}
|
||||||
|
renametab {
|
||||||
|
bind "esc" { UndoRenameTab; SwitchToMode "tab"; }
|
||||||
|
}
|
||||||
|
shared_among "renametab" "renamepane" {
|
||||||
|
bind "Ctrl c" { SwitchToMode "locked"; }
|
||||||
|
}
|
||||||
|
renamepane {
|
||||||
|
bind "esc" { UndoRenamePane; SwitchToMode "pane"; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
plugins {
|
||||||
|
compact-bar location="zellij:compact-bar"
|
||||||
|
configuration location="zellij:configuration"
|
||||||
|
filepicker location="zellij:strider" {
|
||||||
|
cwd "/"
|
||||||
|
}
|
||||||
|
session-manager location="zellij:session-manager"
|
||||||
|
status-bar location="zellij:status-bar"
|
||||||
|
strider location="zellij:strider"
|
||||||
|
tab-bar location="zellij:tab-bar"
|
||||||
|
welcome-screen location="zellij:session-manager" {
|
||||||
|
welcome_screen true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
load_plugins {
|
||||||
|
"file:/usr/src/zellij/wasm32-wasi/release/fixture-plugin-for-tests.wasm" {
|
||||||
|
config_key "config_value"
|
||||||
|
config_key2 "config_value2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a simplified UI without special fonts (arrow glyphs)
|
||||||
|
// Options:
|
||||||
|
// - true
|
||||||
|
// - false (Default)
|
||||||
|
//
|
||||||
|
// simplified_ui true
|
||||||
|
|
||||||
|
// Choose the theme that is specified in the themes section.
|
||||||
|
// Default: default
|
||||||
|
//
|
||||||
|
// theme "default"
|
||||||
|
|
||||||
|
// Choose the base input mode of zellij.
|
||||||
|
// Default: normal
|
||||||
|
//
|
||||||
|
default_mode "locked"
|
||||||
|
|
||||||
|
// Choose the path to the default shell that zellij will use for opening new panes
|
||||||
|
// Default: $SHELL
|
||||||
|
//
|
||||||
|
// default_shell "fish"
|
||||||
|
|
||||||
|
// Choose the path to override cwd that zellij will use for opening new panes
|
||||||
|
//
|
||||||
|
// default_cwd "/tmp"
|
||||||
|
|
||||||
|
// The name of the default layout to load on startup
|
||||||
|
// Default: "default"
|
||||||
|
//
|
||||||
|
// default_layout "compact"
|
||||||
|
|
||||||
|
// The folder in which Zellij will look for layouts
|
||||||
|
//
|
||||||
|
// layout_dir "/tmp"
|
||||||
|
|
||||||
|
// The folder in which Zellij will look for themes
|
||||||
|
//
|
||||||
|
// theme_dir "/tmp"
|
||||||
|
|
||||||
|
// Toggle enabling the mouse mode.
|
||||||
|
// On certain configurations, or terminals this could
|
||||||
|
// potentially interfere with copying text.
|
||||||
|
// Options:
|
||||||
|
// - true (default)
|
||||||
|
// - false
|
||||||
|
//
|
||||||
|
// mouse_mode false
|
||||||
|
|
||||||
|
// Toggle having pane frames around the panes
|
||||||
|
// Options:
|
||||||
|
// - true (default, enabled)
|
||||||
|
// - false
|
||||||
|
//
|
||||||
|
// pane_frames false
|
||||||
|
|
||||||
|
// When attaching to an existing session with other users,
|
||||||
|
// should the session be mirrored (true)
|
||||||
|
// or should each user have their own cursor (false)
|
||||||
|
// Default: false
|
||||||
|
//
|
||||||
|
// mirror_session true
|
||||||
|
|
||||||
|
// Choose what to do when zellij receives SIGTERM, SIGINT, SIGQUIT or SIGHUP
|
||||||
|
// eg. when terminal window with an active zellij session is closed
|
||||||
|
// Options:
|
||||||
|
// - detach (Default)
|
||||||
|
// - quit
|
||||||
|
//
|
||||||
|
// on_force_close "quit"
|
||||||
|
|
||||||
|
// Configure the scroll back buffer size
|
||||||
|
// This is the number of lines zellij stores for each pane in the scroll back
|
||||||
|
// buffer. Excess number of lines are discarded in a FIFO fashion.
|
||||||
|
// Valid values: positive integers
|
||||||
|
// Default value: 10000
|
||||||
|
//
|
||||||
|
// scroll_buffer_size 10000
|
||||||
|
|
||||||
|
// Provide a command to execute when copying text. The text will be piped to
|
||||||
|
// the stdin of the program to perform the copy. This can be used with
|
||||||
|
// terminal emulators which do not support the OSC 52 ANSI control sequence
|
||||||
|
// that will be used by default if this option is not set.
|
||||||
|
// Examples:
|
||||||
|
//
|
||||||
|
// copy_command "xclip -selection clipboard" // x11
|
||||||
|
// copy_command "wl-copy" // wayland
|
||||||
|
// copy_command "pbcopy" // osx
|
||||||
|
//
|
||||||
|
|
||||||
|
// Choose the destination for copied text
|
||||||
|
// Allows using the primary selection buffer (on x11/wayland) instead of the system clipboard.
|
||||||
|
// Does not apply when using copy_command.
|
||||||
|
// Options:
|
||||||
|
// - system (default)
|
||||||
|
// - primary
|
||||||
|
//
|
||||||
|
// copy_clipboard "primary"
|
||||||
|
|
||||||
|
// Enable automatic copying (and clearing) of selection when releasing mouse
|
||||||
|
// Default: true
|
||||||
|
//
|
||||||
|
// copy_on_select true
|
||||||
|
|
||||||
|
// Path to the default editor to use to edit pane scrollbuffer
|
||||||
|
// Default: $EDITOR or $VISUAL
|
||||||
|
// scrollback_editor "nvim"
|
||||||
|
|
||||||
|
// A fixed name to always give the Zellij session.
|
||||||
|
// Consider also setting `attach_to_session true,`
|
||||||
|
// otherwise this will error if such a session exists.
|
||||||
|
// Default: <RANDOM>
|
||||||
|
//
|
||||||
|
// session_name "My singleton session"
|
||||||
|
|
||||||
|
// When `session_name` is provided, attaches to that session
|
||||||
|
// if it is already running or creates it otherwise.
|
||||||
|
// Default: false
|
||||||
|
//
|
||||||
|
// attach_to_session true
|
||||||
|
|
||||||
|
// Toggle between having Zellij lay out panes according to a predefined set of layouts whenever possible
|
||||||
|
// Options:
|
||||||
|
// - true (default)
|
||||||
|
// - false
|
||||||
|
//
|
||||||
|
// auto_layout false
|
||||||
|
|
||||||
|
// Whether sessions should be serialized to the cache folder (including their tabs/panes, cwds and running commands) so that they can later be resurrected
|
||||||
|
// Options:
|
||||||
|
// - true (default)
|
||||||
|
// - false
|
||||||
|
//
|
||||||
|
// session_serialization false
|
||||||
|
|
||||||
|
// Whether pane viewports are serialized along with the session, default is false
|
||||||
|
// Options:
|
||||||
|
// - true
|
||||||
|
// - false (default)
|
||||||
|
//
|
||||||
|
// serialize_pane_viewport false
|
||||||
|
|
||||||
|
// Scrollback lines to serialize along with the pane viewport when serializing sessions, 0
|
||||||
|
// defaults to the scrollback size. If this number is higher than the scrollback size, it will
|
||||||
|
// also default to the scrollback size. This does nothing if `serialize_pane_viewport` is not true.
|
||||||
|
//
|
||||||
|
// scrollback_lines_to_serialize 10000
|
||||||
|
|
||||||
|
// Enable or disable the rendering of styled and colored underlines (undercurl).
|
||||||
|
// May need to be disabled for certain unsupported terminals
|
||||||
|
// Default: true
|
||||||
|
//
|
||||||
|
// styled_underlines false
|
||||||
|
|
||||||
|
// How often in seconds sessions are serialized
|
||||||
|
//
|
||||||
|
// serialization_interval 10000
|
||||||
|
|
||||||
|
// Enable or disable writing of session metadata to disk (if disabled, other sessions might not know
|
||||||
|
// metadata info on this session)
|
||||||
|
// Default: false
|
||||||
|
//
|
||||||
|
// disable_session_metadata false
|
||||||
|
|
||||||
|
// Enable or disable support for the enhanced Kitty Keyboard Protocol (the host terminal must also support it)
|
||||||
|
// Default: true (if the host terminal supports it)
|
||||||
|
//
|
||||||
|
// support_kitty_keyboard_protocol false
|
||||||
|
|
||||||
|
// ui {
|
||||||
|
// pane_frames {
|
||||||
|
// rounded_corners true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
@ -550,6 +550,7 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
||||||
},
|
},
|
||||||
*config.clone(),
|
*config.clone(),
|
||||||
plugin_aliases,
|
plugin_aliases,
|
||||||
|
client_id,
|
||||||
);
|
);
|
||||||
let mut runtime_configuration = config.clone();
|
let mut runtime_configuration = config.clone();
|
||||||
runtime_configuration.options = *runtime_config_options.clone();
|
runtime_configuration.options = *runtime_config_options.clone();
|
||||||
|
|
@ -1138,6 +1139,7 @@ fn init_session(
|
||||||
options: SessionOptions,
|
options: SessionOptions,
|
||||||
mut config: Config,
|
mut config: Config,
|
||||||
plugin_aliases: Box<PluginAliases>,
|
plugin_aliases: Box<PluginAliases>,
|
||||||
|
client_id: ClientId,
|
||||||
) -> SessionMetaData {
|
) -> SessionMetaData {
|
||||||
let SessionOptions {
|
let SessionOptions {
|
||||||
opts,
|
opts,
|
||||||
|
|
@ -1224,7 +1226,8 @@ fn init_session(
|
||||||
.spawn({
|
.spawn({
|
||||||
let screen_bus = Bus::new(
|
let screen_bus = Bus::new(
|
||||||
vec![screen_receiver, bounded_screen_receiver],
|
vec![screen_receiver, bounded_screen_receiver],
|
||||||
None,
|
Some(&to_screen), // there are certain occasions (eg. caching) where the screen
|
||||||
|
// needs to send messages to itself
|
||||||
Some(&to_pty),
|
Some(&to_pty),
|
||||||
Some(&to_plugin),
|
Some(&to_plugin),
|
||||||
Some(&to_server),
|
Some(&to_server),
|
||||||
|
|
@ -1237,6 +1240,7 @@ fn init_session(
|
||||||
let client_attributes_clone = client_attributes.clone();
|
let client_attributes_clone = client_attributes.clone();
|
||||||
let debug = opts.debug;
|
let debug = opts.debug;
|
||||||
let layout = layout.clone();
|
let layout = layout.clone();
|
||||||
|
let config = config.clone();
|
||||||
move || {
|
move || {
|
||||||
screen_thread_main(
|
screen_thread_main(
|
||||||
screen_bus,
|
screen_bus,
|
||||||
|
|
@ -1272,6 +1276,7 @@ fn init_session(
|
||||||
let default_shell = default_shell.clone();
|
let default_shell = default_shell.clone();
|
||||||
let capabilities = capabilities.clone();
|
let capabilities = capabilities.clone();
|
||||||
let layout_dir = config_options.layout_dir.clone();
|
let layout_dir = config_options.layout_dir.clone();
|
||||||
|
let background_plugins = config.background_plugins.clone();
|
||||||
move || {
|
move || {
|
||||||
plugin_thread_main(
|
plugin_thread_main(
|
||||||
plugin_bus,
|
plugin_bus,
|
||||||
|
|
@ -1287,6 +1292,8 @@ fn init_session(
|
||||||
plugin_aliases,
|
plugin_aliases,
|
||||||
default_mode,
|
default_mode,
|
||||||
default_keybinds,
|
default_keybinds,
|
||||||
|
background_plugins,
|
||||||
|
client_id,
|
||||||
)
|
)
|
||||||
.fatal()
|
.fatal()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,7 @@ pub(crate) struct PluginPane {
|
||||||
debug: bool,
|
debug: bool,
|
||||||
arrow_fonts: bool,
|
arrow_fonts: bool,
|
||||||
styled_underlines: bool,
|
styled_underlines: bool,
|
||||||
|
should_be_suppressed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PluginPane {
|
impl PluginPane {
|
||||||
|
|
@ -152,6 +153,7 @@ impl PluginPane {
|
||||||
debug,
|
debug,
|
||||||
arrow_fonts,
|
arrow_fonts,
|
||||||
styled_underlines,
|
styled_underlines,
|
||||||
|
should_be_suppressed: false,
|
||||||
};
|
};
|
||||||
for client_id in currently_connected_clients {
|
for client_id in currently_connected_clients {
|
||||||
plugin.handle_plugin_bytes(client_id, initial_loading_message.as_bytes().to_vec());
|
plugin.handle_plugin_bytes(client_id, initial_loading_message.as_bytes().to_vec());
|
||||||
|
|
@ -692,6 +694,12 @@ impl Pane for PluginPane {
|
||||||
self.style.rounded_corners = rounded_corners;
|
self.style.rounded_corners = rounded_corners;
|
||||||
self.frame.clear();
|
self.frame.clear();
|
||||||
}
|
}
|
||||||
|
fn set_should_be_suppressed(&mut self, should_be_suppressed: bool) {
|
||||||
|
self.should_be_suppressed = should_be_suppressed;
|
||||||
|
}
|
||||||
|
fn query_should_be_suppressed(&self) -> bool {
|
||||||
|
self.should_be_suppressed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PluginPane {
|
impl PluginPane {
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ use zellij_utils::{
|
||||||
command::TerminalAction,
|
command::TerminalAction,
|
||||||
keybinds::Keybinds,
|
keybinds::Keybinds,
|
||||||
layout::{FloatingPaneLayout, Layout, Run, RunPlugin, RunPluginOrAlias, TiledPaneLayout},
|
layout::{FloatingPaneLayout, Layout, Run, RunPlugin, RunPluginOrAlias, TiledPaneLayout},
|
||||||
plugins::PluginAliases,
|
plugins::{PluginAliases, PluginConfig},
|
||||||
},
|
},
|
||||||
ipc::ClientAttributes,
|
ipc::ClientAttributes,
|
||||||
pane_size::Size,
|
pane_size::Size,
|
||||||
|
|
@ -216,6 +216,12 @@ pub(crate) fn plugin_thread_main(
|
||||||
plugin_aliases: Box<PluginAliases>,
|
plugin_aliases: Box<PluginAliases>,
|
||||||
default_mode: InputMode,
|
default_mode: InputMode,
|
||||||
default_keybinds: Keybinds,
|
default_keybinds: Keybinds,
|
||||||
|
background_plugins: HashSet<RunPluginOrAlias>,
|
||||||
|
// the client id that started the session,
|
||||||
|
// we need it here because the thread's own list of connected clients might not yet be updated
|
||||||
|
// on session start when we need to load the background plugins, and so we must have an
|
||||||
|
// explicit client_id that has started the session
|
||||||
|
initiating_client_id: ClientId,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
info!("Wasm main thread starts");
|
info!("Wasm main thread starts");
|
||||||
let plugin_dir = data_dir.join("plugins/");
|
let plugin_dir = data_dir.join("plugins/");
|
||||||
|
|
@ -241,6 +247,16 @@ pub(crate) fn plugin_thread_main(
|
||||||
default_keybinds,
|
default_keybinds,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for mut run_plugin_or_alias in background_plugins {
|
||||||
|
load_background_plugin(
|
||||||
|
run_plugin_or_alias,
|
||||||
|
&mut wasm_bridge,
|
||||||
|
&bus,
|
||||||
|
&plugin_aliases,
|
||||||
|
initiating_client_id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel");
|
let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel");
|
||||||
err_ctx.add_call(ContextType::Plugin((&event).into()));
|
err_ctx.add_call(ContextType::Plugin((&event).into()));
|
||||||
|
|
@ -933,6 +949,53 @@ fn pipe_to_specific_plugins(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_background_plugin(
|
||||||
|
mut run_plugin_or_alias: RunPluginOrAlias,
|
||||||
|
wasm_bridge: &mut WasmBridge,
|
||||||
|
bus: &Bus<PluginInstruction>,
|
||||||
|
plugin_aliases: &PluginAliases,
|
||||||
|
client_id: ClientId,
|
||||||
|
) {
|
||||||
|
run_plugin_or_alias.populate_run_plugin_if_needed(&plugin_aliases);
|
||||||
|
let cwd = run_plugin_or_alias.get_initial_cwd();
|
||||||
|
let run_plugin = run_plugin_or_alias.get_run_plugin();
|
||||||
|
let size = Size::default();
|
||||||
|
let skip_cache = false;
|
||||||
|
match wasm_bridge.load_plugin(
|
||||||
|
&run_plugin,
|
||||||
|
None,
|
||||||
|
size,
|
||||||
|
cwd.clone(),
|
||||||
|
skip_cache,
|
||||||
|
Some(client_id),
|
||||||
|
None,
|
||||||
|
) {
|
||||||
|
Ok((plugin_id, client_id)) => {
|
||||||
|
let should_float = None;
|
||||||
|
let should_be_open_in_place = false;
|
||||||
|
let pane_title = None;
|
||||||
|
let pane_id_to_replace = None;
|
||||||
|
let start_suppressed = true;
|
||||||
|
drop(bus.senders.send_to_screen(ScreenInstruction::AddPlugin(
|
||||||
|
should_float,
|
||||||
|
should_be_open_in_place,
|
||||||
|
run_plugin_or_alias,
|
||||||
|
pane_title,
|
||||||
|
None,
|
||||||
|
plugin_id,
|
||||||
|
pane_id_to_replace,
|
||||||
|
cwd,
|
||||||
|
start_suppressed,
|
||||||
|
// None,
|
||||||
|
Some(client_id),
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to load plugin: {e}");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const EXIT_TIMEOUT: Duration = Duration::from_secs(3);
|
const EXIT_TIMEOUT: Duration = Duration::from_secs(3);
|
||||||
|
|
||||||
#[path = "./unit/plugin_tests.rs"]
|
#[path = "./unit/plugin_tests.rs"]
|
||||||
|
|
|
||||||
|
|
@ -836,9 +836,6 @@ impl<'a> PluginLoader<'a> {
|
||||||
)))));
|
)))));
|
||||||
let wasi_ctx = wasi_ctx_builder.build_p1();
|
let wasi_ctx = wasi_ctx_builder.build_p1();
|
||||||
let mut mut_plugin = self.plugin.clone();
|
let mut mut_plugin = self.plugin.clone();
|
||||||
if let Some(tab_index) = self.tab_index {
|
|
||||||
mut_plugin.set_tab_index(tab_index);
|
|
||||||
}
|
|
||||||
let plugin_env = PluginEnv {
|
let plugin_env = PluginEnv {
|
||||||
plugin_id: self.plugin_id,
|
plugin_id: self.plugin_id,
|
||||||
client_id: self.client_id,
|
client_id: self.client_id,
|
||||||
|
|
|
||||||
|
|
@ -301,6 +301,7 @@ fn create_plugin_thread(
|
||||||
Box<dyn FnOnce()>,
|
Box<dyn FnOnce()>,
|
||||||
) {
|
) {
|
||||||
let zellij_cwd = zellij_cwd.unwrap_or_else(|| PathBuf::from("."));
|
let zellij_cwd = zellij_cwd.unwrap_or_else(|| PathBuf::from("."));
|
||||||
|
let initiating_client_id = 1;
|
||||||
let (to_server, _server_receiver): ChannelWithContext<ServerInstruction> =
|
let (to_server, _server_receiver): ChannelWithContext<ServerInstruction> =
|
||||||
channels::bounded(50);
|
channels::bounded(50);
|
||||||
let to_server = SenderWithContext::new(to_server);
|
let to_server = SenderWithContext::new(to_server);
|
||||||
|
|
@ -367,6 +368,8 @@ fn create_plugin_thread(
|
||||||
Box::new(plugin_aliases),
|
Box::new(plugin_aliases),
|
||||||
InputMode::Normal,
|
InputMode::Normal,
|
||||||
Keybinds::default(),
|
Keybinds::default(),
|
||||||
|
Default::default(),
|
||||||
|
initiating_client_id,
|
||||||
)
|
)
|
||||||
.expect("TEST")
|
.expect("TEST")
|
||||||
})
|
})
|
||||||
|
|
@ -430,6 +433,7 @@ fn create_plugin_thread_with_server_receiver(
|
||||||
let plugin_capabilities = PluginCapabilities::default();
|
let plugin_capabilities = PluginCapabilities::default();
|
||||||
let client_attributes = ClientAttributes::default();
|
let client_attributes = ClientAttributes::default();
|
||||||
let default_shell_action = None; // TODO: change me
|
let default_shell_action = None; // TODO: change me
|
||||||
|
let initiating_client_id = 1;
|
||||||
let plugin_thread = std::thread::Builder::new()
|
let plugin_thread = std::thread::Builder::new()
|
||||||
.name("plugin_thread".to_string())
|
.name("plugin_thread".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
|
|
@ -448,6 +452,8 @@ fn create_plugin_thread_with_server_receiver(
|
||||||
Box::new(PluginAliases::default()),
|
Box::new(PluginAliases::default()),
|
||||||
InputMode::Normal,
|
InputMode::Normal,
|
||||||
Keybinds::default(),
|
Keybinds::default(),
|
||||||
|
Default::default(),
|
||||||
|
initiating_client_id,
|
||||||
)
|
)
|
||||||
.expect("TEST");
|
.expect("TEST");
|
||||||
})
|
})
|
||||||
|
|
@ -517,6 +523,7 @@ fn create_plugin_thread_with_pty_receiver(
|
||||||
let plugin_capabilities = PluginCapabilities::default();
|
let plugin_capabilities = PluginCapabilities::default();
|
||||||
let client_attributes = ClientAttributes::default();
|
let client_attributes = ClientAttributes::default();
|
||||||
let default_shell_action = None; // TODO: change me
|
let default_shell_action = None; // TODO: change me
|
||||||
|
let initiating_client_id = 1;
|
||||||
let plugin_thread = std::thread::Builder::new()
|
let plugin_thread = std::thread::Builder::new()
|
||||||
.name("plugin_thread".to_string())
|
.name("plugin_thread".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
|
|
@ -535,6 +542,8 @@ fn create_plugin_thread_with_pty_receiver(
|
||||||
Box::new(PluginAliases::default()),
|
Box::new(PluginAliases::default()),
|
||||||
InputMode::Normal,
|
InputMode::Normal,
|
||||||
Keybinds::default(),
|
Keybinds::default(),
|
||||||
|
Default::default(),
|
||||||
|
initiating_client_id,
|
||||||
)
|
)
|
||||||
.expect("TEST")
|
.expect("TEST")
|
||||||
})
|
})
|
||||||
|
|
@ -599,6 +608,7 @@ fn create_plugin_thread_with_background_jobs_receiver(
|
||||||
let plugin_capabilities = PluginCapabilities::default();
|
let plugin_capabilities = PluginCapabilities::default();
|
||||||
let client_attributes = ClientAttributes::default();
|
let client_attributes = ClientAttributes::default();
|
||||||
let default_shell_action = None; // TODO: change me
|
let default_shell_action = None; // TODO: change me
|
||||||
|
let initiating_client_id = 1;
|
||||||
let plugin_thread = std::thread::Builder::new()
|
let plugin_thread = std::thread::Builder::new()
|
||||||
.name("plugin_thread".to_string())
|
.name("plugin_thread".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
|
|
@ -617,6 +627,8 @@ fn create_plugin_thread_with_background_jobs_receiver(
|
||||||
Box::new(PluginAliases::default()),
|
Box::new(PluginAliases::default()),
|
||||||
InputMode::Normal,
|
InputMode::Normal,
|
||||||
Keybinds::default(),
|
Keybinds::default(),
|
||||||
|
Default::default(),
|
||||||
|
initiating_client_id,
|
||||||
)
|
)
|
||||||
.expect("TEST")
|
.expect("TEST")
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ use zellij_utils::{
|
||||||
actions::Action,
|
actions::Action,
|
||||||
command::{OpenFilePayload, RunCommand, RunCommandAction, TerminalAction},
|
command::{OpenFilePayload, RunCommand, RunCommandAction, TerminalAction},
|
||||||
layout::{Layout, RunPluginOrAlias},
|
layout::{Layout, RunPluginOrAlias},
|
||||||
plugins::PluginType,
|
|
||||||
},
|
},
|
||||||
plugin_api::{
|
plugin_api::{
|
||||||
plugin_command::ProtobufPluginCommand,
|
plugin_command::ProtobufPluginCommand,
|
||||||
|
|
@ -406,14 +405,10 @@ fn unsubscribe(env: &PluginEnv, event_list: HashSet<EventType>) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_selectable(env: &PluginEnv, selectable: bool) {
|
fn set_selectable(env: &PluginEnv, selectable: bool) {
|
||||||
match env.plugin.run {
|
|
||||||
PluginType::Pane(Some(tab_index)) => {
|
|
||||||
// let selectable = selectable != 0;
|
|
||||||
env.senders
|
env.senders
|
||||||
.send_to_screen(ScreenInstruction::SetSelectable(
|
.send_to_screen(ScreenInstruction::SetSelectable(
|
||||||
PaneId::Plugin(env.plugin_id),
|
PaneId::Plugin(env.plugin_id),
|
||||||
selectable,
|
selectable,
|
||||||
tab_index,
|
|
||||||
))
|
))
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
|
|
@ -423,21 +418,12 @@ fn set_selectable(env: &PluginEnv, selectable: bool) {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.non_fatal();
|
.non_fatal();
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
debug!(
|
|
||||||
"{} - Calling method 'set_selectable' does nothing for headless plugins",
|
|
||||||
env.plugin.location
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request_permission(env: &PluginEnv, permissions: Vec<PermissionType>) -> Result<()> {
|
fn request_permission(env: &PluginEnv, permissions: Vec<PermissionType>) -> Result<()> {
|
||||||
if PermissionCache::from_path_or_default(None)
|
if PermissionCache::from_path_or_default(None)
|
||||||
.check_permissions(env.plugin.location.to_string(), &permissions)
|
.check_permissions(env.plugin.location.to_string(), &permissions)
|
||||||
{
|
{
|
||||||
log::info!("PermissionRequestResult 1");
|
|
||||||
return env
|
return env
|
||||||
.senders
|
.senders
|
||||||
.send_to_plugin(PluginInstruction::PermissionRequestResult(
|
.send_to_plugin(PluginInstruction::PermissionRequestResult(
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ use crate::{
|
||||||
panes::PaneId,
|
panes::PaneId,
|
||||||
plugins::{PluginId, PluginInstruction, PluginRenderAsset},
|
plugins::{PluginId, PluginInstruction, PluginRenderAsset},
|
||||||
pty::{ClientTabIndexOrPaneId, PtyInstruction, VteBytes},
|
pty::{ClientTabIndexOrPaneId, PtyInstruction, VteBytes},
|
||||||
tab::Tab,
|
tab::{SuppressedPanes, Tab},
|
||||||
thread_bus::Bus,
|
thread_bus::Bus,
|
||||||
ui::{
|
ui::{
|
||||||
loading_indication::LoadingIndication,
|
loading_indication::LoadingIndication,
|
||||||
|
|
@ -200,7 +200,7 @@ pub enum ScreenInstruction {
|
||||||
CloseFocusedPane(ClientId),
|
CloseFocusedPane(ClientId),
|
||||||
ToggleActiveTerminalFullscreen(ClientId),
|
ToggleActiveTerminalFullscreen(ClientId),
|
||||||
TogglePaneFrames,
|
TogglePaneFrames,
|
||||||
SetSelectable(PaneId, bool, usize),
|
SetSelectable(PaneId, bool),
|
||||||
ClosePane(PaneId, Option<ClientId>),
|
ClosePane(PaneId, Option<ClientId>),
|
||||||
HoldPane(
|
HoldPane(
|
||||||
PaneId,
|
PaneId,
|
||||||
|
|
@ -822,6 +822,27 @@ impl Screen {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn move_suppressed_panes_from_closed_tab(
|
||||||
|
&mut self,
|
||||||
|
suppressed_panes: SuppressedPanes,
|
||||||
|
) -> Result<()> {
|
||||||
|
// TODO: this is not entirely accurate, these also sometimes contain a pane who's
|
||||||
|
// scrollback is being edited - in this case we need to close it or to move it to the
|
||||||
|
// appropriate tab
|
||||||
|
let err_context = || "Failed to move suppressed panes from closed tab";
|
||||||
|
let first_tab_index = *self
|
||||||
|
.tabs
|
||||||
|
.keys()
|
||||||
|
.next()
|
||||||
|
.context("screen contains no tabs")
|
||||||
|
.with_context(err_context)?;
|
||||||
|
self.tabs
|
||||||
|
.get_mut(&first_tab_index)
|
||||||
|
.with_context(err_context)?
|
||||||
|
.add_suppressed_panes(suppressed_panes);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn move_clients_between_tabs(
|
fn move_clients_between_tabs(
|
||||||
&mut self,
|
&mut self,
|
||||||
source_tab_index: usize,
|
source_tab_index: usize,
|
||||||
|
|
@ -1053,7 +1074,16 @@ impl Screen {
|
||||||
let err_context = || format!("failed to close tab at index {tab_index:?}");
|
let err_context = || format!("failed to close tab at index {tab_index:?}");
|
||||||
|
|
||||||
let mut tab_to_close = self.tabs.remove(&tab_index).with_context(err_context)?;
|
let mut tab_to_close = self.tabs.remove(&tab_index).with_context(err_context)?;
|
||||||
let pane_ids = tab_to_close.get_all_pane_ids();
|
let mut pane_ids = tab_to_close.get_all_pane_ids();
|
||||||
|
|
||||||
|
// here we extract the suppressed panes (these are background panes that don't care which
|
||||||
|
// tab they are in, and in the future we should probably make them global to screen rather
|
||||||
|
// than to each tab) and move them to another tab if there is one
|
||||||
|
let suppressed_panes = tab_to_close.extract_suppressed_panes();
|
||||||
|
for suppressed_pane_id in suppressed_panes.keys() {
|
||||||
|
pane_ids.retain(|p| p != suppressed_pane_id);
|
||||||
|
}
|
||||||
|
|
||||||
// below we don't check the result of sending the CloseTab instruction to the pty thread
|
// below we don't check the result of sending the CloseTab instruction to the pty thread
|
||||||
// because this might be happening when the app is closing, at which point the pty thread
|
// because this might be happening when the app is closing, at which point the pty thread
|
||||||
// has already closed and this would result in an error
|
// has already closed and this would result in an error
|
||||||
|
|
@ -1071,6 +1101,8 @@ impl Screen {
|
||||||
let client_mode_infos_in_closed_tab = tab_to_close.drain_connected_clients(None);
|
let client_mode_infos_in_closed_tab = tab_to_close.drain_connected_clients(None);
|
||||||
self.move_clients_from_closed_tab(client_mode_infos_in_closed_tab)
|
self.move_clients_from_closed_tab(client_mode_infos_in_closed_tab)
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
|
self.move_suppressed_panes_from_closed_tab(suppressed_panes)
|
||||||
|
.with_context(err_context)?;
|
||||||
let visible_tab_indices: HashSet<usize> =
|
let visible_tab_indices: HashSet<usize> =
|
||||||
self.active_tab_indices.values().copied().collect();
|
self.active_tab_indices.values().copied().collect();
|
||||||
for t in self.tabs.values_mut() {
|
for t in self.tabs.values_mut() {
|
||||||
|
|
@ -2676,7 +2708,7 @@ pub(crate) fn screen_thread_main(
|
||||||
let mut pending_tab_ids: HashSet<usize> = HashSet::new();
|
let mut pending_tab_ids: HashSet<usize> = HashSet::new();
|
||||||
let mut pending_tab_switches: HashSet<(usize, ClientId)> = HashSet::new(); // usize is the
|
let mut pending_tab_switches: HashSet<(usize, ClientId)> = HashSet::new(); // usize is the
|
||||||
// tab_index
|
// tab_index
|
||||||
|
let mut pending_events_waiting_for_client: Vec<ScreenInstruction> = vec![];
|
||||||
let mut plugin_loading_message_cache = HashMap::new();
|
let mut plugin_loading_message_cache = HashMap::new();
|
||||||
loop {
|
loop {
|
||||||
let (event, mut err_ctx) = screen
|
let (event, mut err_ctx) = screen
|
||||||
|
|
@ -3263,18 +3295,14 @@ pub(crate) fn screen_thread_main(
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.log_and_report_session_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::SetSelectable(id, selectable, tab_index) => {
|
ScreenInstruction::SetSelectable(pid, selectable) => {
|
||||||
screen.get_indexed_tab_mut(tab_index).map_or_else(
|
let all_tabs = screen.get_tabs_mut();
|
||||||
|| {
|
for tab in all_tabs.values_mut() {
|
||||||
log::warn!(
|
if tab.has_pane_with_pid(&pid) {
|
||||||
"Tab index #{} not found, could not set selectable for plugin #{:?}.",
|
tab.set_pane_selectable(pid, selectable);
|
||||||
tab_index,
|
break;
|
||||||
id
|
}
|
||||||
)
|
}
|
||||||
},
|
|
||||||
|tab| tab.set_pane_selectable(id, selectable),
|
|
||||||
);
|
|
||||||
|
|
||||||
screen.render(None)?;
|
screen.render(None)?;
|
||||||
screen.log_and_report_session_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
},
|
},
|
||||||
|
|
@ -3448,6 +3476,10 @@ pub(crate) fn screen_thread_main(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for event in pending_events_waiting_for_client.drain(..) {
|
||||||
|
screen.bus.senders.send_to_screen(event).non_fatal();
|
||||||
|
}
|
||||||
|
|
||||||
screen.unblock_input()?;
|
screen.unblock_input()?;
|
||||||
screen.render(None)?;
|
screen.render(None)?;
|
||||||
// we do this here in order to recover from a race condition on app start
|
// we do this here in order to recover from a race condition on app start
|
||||||
|
|
@ -3674,6 +3706,9 @@ pub(crate) fn screen_thread_main(
|
||||||
} else if let Some(tab_position_to_focus) = tab_position_to_focus {
|
} else if let Some(tab_position_to_focus) = tab_position_to_focus {
|
||||||
screen.go_to_tab(tab_position_to_focus, client_id)?;
|
screen.go_to_tab(tab_position_to_focus, client_id)?;
|
||||||
}
|
}
|
||||||
|
for event in pending_events_waiting_for_client.drain(..) {
|
||||||
|
screen.bus.senders.send_to_screen(event).non_fatal();
|
||||||
|
}
|
||||||
screen.log_and_report_session_state()?;
|
screen.log_and_report_session_state()?;
|
||||||
screen.render(None)?;
|
screen.render(None)?;
|
||||||
},
|
},
|
||||||
|
|
@ -3944,6 +3979,21 @@ pub(crate) fn screen_thread_main(
|
||||||
start_suppressed,
|
start_suppressed,
|
||||||
client_id,
|
client_id,
|
||||||
) => {
|
) => {
|
||||||
|
if screen.active_tab_indices.is_empty() && tab_index.is_none() {
|
||||||
|
pending_events_waiting_for_client.push(ScreenInstruction::AddPlugin(
|
||||||
|
should_float,
|
||||||
|
should_be_in_place,
|
||||||
|
run_plugin_or_alias,
|
||||||
|
pane_title,
|
||||||
|
tab_index,
|
||||||
|
plugin_id,
|
||||||
|
pane_id_to_replace,
|
||||||
|
cwd,
|
||||||
|
start_suppressed,
|
||||||
|
client_id,
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let pane_title = pane_title.unwrap_or_else(|| {
|
let pane_title = pane_title.unwrap_or_else(|| {
|
||||||
format!(
|
format!(
|
||||||
"({}) - {}",
|
"({}) - {}",
|
||||||
|
|
@ -4208,7 +4258,7 @@ pub(crate) fn screen_thread_main(
|
||||||
let all_tabs = screen.get_tabs_mut();
|
let all_tabs = screen.get_tabs_mut();
|
||||||
for tab in all_tabs.values_mut() {
|
for tab in all_tabs.values_mut() {
|
||||||
if tab.has_non_suppressed_pane_with_pid(&pane_id) {
|
if tab.has_non_suppressed_pane_with_pid(&pane_id) {
|
||||||
tab.suppress_pane(pane_id, client_id);
|
tab.suppress_pane(pane_id, Some(client_id));
|
||||||
drop(screen.render(None));
|
drop(screen.render(None));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -4255,9 +4305,9 @@ pub(crate) fn screen_thread_main(
|
||||||
});
|
});
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
log::error!(
|
log::error!("PluginId '{}' not found - caching request", plugin_id);
|
||||||
"PluginId '{}' not found - cannot request permissions",
|
pending_events_waiting_for_client.push(
|
||||||
plugin_id
|
ScreenInstruction::RequestPluginPermissions(plugin_id, plugin_permission),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,7 @@ pub const MIN_TERMINAL_WIDTH: usize = 5;
|
||||||
const MAX_PENDING_VTE_EVENTS: usize = 7000;
|
const MAX_PENDING_VTE_EVENTS: usize = 7000;
|
||||||
|
|
||||||
type HoldForCommand = Option<RunCommand>;
|
type HoldForCommand = Option<RunCommand>;
|
||||||
|
pub type SuppressedPanes = HashMap<PaneId, (bool, Box<dyn Pane>)>; // bool => is scrollback editor
|
||||||
|
|
||||||
enum BufferedTabInstruction {
|
enum BufferedTabInstruction {
|
||||||
SetPaneSelectable(PaneId, bool),
|
SetPaneSelectable(PaneId, bool),
|
||||||
|
|
@ -150,7 +151,7 @@ pub(crate) struct Tab {
|
||||||
pub prev_name: String,
|
pub prev_name: String,
|
||||||
tiled_panes: TiledPanes,
|
tiled_panes: TiledPanes,
|
||||||
floating_panes: FloatingPanes,
|
floating_panes: FloatingPanes,
|
||||||
suppressed_panes: HashMap<PaneId, (bool, Box<dyn Pane>)>, // bool => is scrollback editor
|
suppressed_panes: SuppressedPanes,
|
||||||
max_panes: Option<usize>,
|
max_panes: Option<usize>,
|
||||||
viewport: Rc<RefCell<Viewport>>, // includes all non-UI panes
|
viewport: Rc<RefCell<Viewport>>, // includes all non-UI panes
|
||||||
display_area: Rc<RefCell<Size>>, // includes all panes (including eg. the status bar and tab bar in the default layout)
|
display_area: Rc<RefCell<Size>>, // includes all panes (including eg. the status bar and tab bar in the default layout)
|
||||||
|
|
@ -495,6 +496,10 @@ pub trait Pane {
|
||||||
fn update_theme(&mut self, _theme: Palette) {}
|
fn update_theme(&mut self, _theme: Palette) {}
|
||||||
fn update_arrow_fonts(&mut self, _should_support_arrow_fonts: bool) {}
|
fn update_arrow_fonts(&mut self, _should_support_arrow_fonts: bool) {}
|
||||||
fn update_rounded_corners(&mut self, _rounded_corners: bool) {}
|
fn update_rounded_corners(&mut self, _rounded_corners: bool) {}
|
||||||
|
fn set_should_be_suppressed(&mut self, _should_be_suppressed: bool) {}
|
||||||
|
fn query_should_be_suppressed(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -1855,7 +1860,7 @@ impl Tab {
|
||||||
let mut should_update_ui = false;
|
let mut should_update_ui = false;
|
||||||
let is_sync_panes_active = self.is_sync_panes_active();
|
let is_sync_panes_active = self.is_sync_panes_active();
|
||||||
|
|
||||||
let active_terminal = self
|
let active_pane = self
|
||||||
.floating_panes
|
.floating_panes
|
||||||
.get_mut(&pane_id)
|
.get_mut(&pane_id)
|
||||||
.or_else(|| self.tiled_panes.get_pane_mut(pane_id))
|
.or_else(|| self.tiled_panes.get_pane_mut(pane_id))
|
||||||
|
|
@ -1867,8 +1872,7 @@ impl Tab {
|
||||||
// However if the terminal is part of a tab-sync, we need to
|
// However if the terminal is part of a tab-sync, we need to
|
||||||
// check if the terminal should receive input or not (depending on its
|
// check if the terminal should receive input or not (depending on its
|
||||||
// 'exclude_from_sync' configuration).
|
// 'exclude_from_sync' configuration).
|
||||||
let should_not_write_to_terminal =
|
let should_not_write_to_terminal = is_sync_panes_active && active_pane.exclude_from_sync();
|
||||||
is_sync_panes_active && active_terminal.exclude_from_sync();
|
|
||||||
|
|
||||||
if should_not_write_to_terminal {
|
if should_not_write_to_terminal {
|
||||||
return Ok(should_update_ui);
|
return Ok(should_update_ui);
|
||||||
|
|
@ -1876,7 +1880,7 @@ impl Tab {
|
||||||
|
|
||||||
match pane_id {
|
match pane_id {
|
||||||
PaneId::Terminal(active_terminal_id) => {
|
PaneId::Terminal(active_terminal_id) => {
|
||||||
match active_terminal.adjust_input_to_terminal(
|
match active_pane.adjust_input_to_terminal(
|
||||||
key_with_modifier,
|
key_with_modifier,
|
||||||
raw_input_bytes,
|
raw_input_bytes,
|
||||||
raw_input_bytes_are_kitty,
|
raw_input_bytes_are_kitty,
|
||||||
|
|
@ -1918,7 +1922,7 @@ impl Tab {
|
||||||
None => {},
|
None => {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PaneId::Plugin(pid) => match active_terminal.adjust_input_to_terminal(
|
PaneId::Plugin(pid) => match active_pane.adjust_input_to_terminal(
|
||||||
key_with_modifier,
|
key_with_modifier,
|
||||||
raw_input_bytes,
|
raw_input_bytes,
|
||||||
raw_input_bytes_are_kitty,
|
raw_input_bytes_are_kitty,
|
||||||
|
|
@ -1942,6 +1946,10 @@ impl Tab {
|
||||||
.with_context(err_context)?;
|
.with_context(err_context)?;
|
||||||
},
|
},
|
||||||
Some(AdjustedInput::PermissionRequestResult(permissions, status)) => {
|
Some(AdjustedInput::PermissionRequestResult(permissions, status)) => {
|
||||||
|
if active_pane.query_should_be_suppressed() {
|
||||||
|
active_pane.set_should_be_suppressed(false);
|
||||||
|
self.suppress_pane(PaneId::Plugin(pid), client_id);
|
||||||
|
}
|
||||||
self.request_plugin_permissions(pid, None);
|
self.request_plugin_permissions(pid, None);
|
||||||
self.senders
|
self.senders
|
||||||
.send_to_plugin(PluginInstruction::PermissionRequestResult(
|
.send_to_plugin(PluginInstruction::PermissionRequestResult(
|
||||||
|
|
@ -4113,12 +4121,24 @@ impl Tab {
|
||||||
None => Ok(()),
|
None => Ok(()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn suppress_pane(&mut self, pane_id: PaneId, client_id: ClientId) {
|
pub fn focus_suppressed_pane_for_all_clients(&mut self, pane_id: PaneId) {
|
||||||
|
match self.suppressed_panes.remove(&pane_id) {
|
||||||
|
Some(pane) => {
|
||||||
|
self.show_floating_panes();
|
||||||
|
self.add_floating_pane(pane.1, pane_id, None, None);
|
||||||
|
self.floating_panes.focus_pane_for_all_clients(pane_id);
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
log::error!("Could not find suppressed pane wiht id: {:?}", pane_id);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn suppress_pane(&mut self, pane_id: PaneId, client_id: Option<ClientId>) {
|
||||||
// this method places a pane in the suppressed pane with its own ID - this means we'll
|
// this method places a pane in the suppressed pane with its own ID - this means we'll
|
||||||
// not take it out of there when another pane is closed (eg. like happens with the
|
// not take it out of there when another pane is closed (eg. like happens with the
|
||||||
// scrollback editor), but it has to take itself out on its own (eg. a plugin using the
|
// scrollback editor), but it has to take itself out on its own (eg. a plugin using the
|
||||||
// show_self() method)
|
// show_self() method)
|
||||||
if let Some(pane) = self.extract_pane(pane_id, true, Some(client_id)) {
|
if let Some(pane) = self.extract_pane(pane_id, true, client_id) {
|
||||||
let is_scrollback_editor = false;
|
let is_scrollback_editor = false;
|
||||||
self.suppressed_panes
|
self.suppressed_panes
|
||||||
.insert(pane_id, (is_scrollback_editor, pane));
|
.insert(pane_id, (is_scrollback_editor, pane));
|
||||||
|
|
@ -4205,19 +4225,36 @@ impl Tab {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn request_plugin_permissions(&mut self, pid: u32, permissions: Option<PluginPermission>) {
|
pub fn request_plugin_permissions(&mut self, pid: u32, permissions: Option<PluginPermission>) {
|
||||||
|
let mut should_focus_pane = false;
|
||||||
if let Some(plugin_pane) = self
|
if let Some(plugin_pane) = self
|
||||||
.tiled_panes
|
.tiled_panes
|
||||||
.get_pane_mut(PaneId::Plugin(pid))
|
.get_pane_mut(PaneId::Plugin(pid))
|
||||||
.or_else(|| self.floating_panes.get_pane_mut(PaneId::Plugin(pid)))
|
.or_else(|| self.floating_panes.get_pane_mut(PaneId::Plugin(pid)))
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
self.suppressed_panes
|
let mut suppressed_pane = self
|
||||||
|
.suppressed_panes
|
||||||
.values_mut()
|
.values_mut()
|
||||||
.find(|s_p| s_p.1.pid() == PaneId::Plugin(pid))
|
.find(|s_p| s_p.1.pid() == PaneId::Plugin(pid))
|
||||||
.map(|s_p| &mut s_p.1)
|
.map(|s_p| &mut s_p.1);
|
||||||
|
if let Some(suppressed_pane) = suppressed_pane.as_mut() {
|
||||||
|
if permissions.is_some() {
|
||||||
|
// here what happens is that we're requesting permissions for a pane that
|
||||||
|
// is suppressed, meaning the user cannot see the permission request
|
||||||
|
// so we temporarily focus this pane as a floating pane, marking it so that
|
||||||
|
// once the permissions are accepted/rejected by the user, it will be
|
||||||
|
// suppressed again
|
||||||
|
suppressed_pane.set_should_be_suppressed(true);
|
||||||
|
should_focus_pane = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
suppressed_pane
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
plugin_pane.request_permissions_from_user(permissions);
|
plugin_pane.request_permissions_from_user(permissions);
|
||||||
}
|
}
|
||||||
|
if should_focus_pane {
|
||||||
|
self.focus_suppressed_pane_for_all_clients(PaneId::Plugin(pid));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn rerun_terminal_pane_with_id(&mut self, terminal_pane_id: u32) {
|
pub fn rerun_terminal_pane_with_id(&mut self, terminal_pane_id: u32) {
|
||||||
let pane_id = PaneId::Terminal(terminal_pane_id);
|
let pane_id = PaneId::Terminal(terminal_pane_id);
|
||||||
|
|
@ -4331,6 +4368,14 @@ impl Tab {
|
||||||
pub fn update_auto_layout(&mut self, auto_layout: bool) {
|
pub fn update_auto_layout(&mut self, auto_layout: bool) {
|
||||||
self.auto_layout = auto_layout;
|
self.auto_layout = auto_layout;
|
||||||
}
|
}
|
||||||
|
pub fn extract_suppressed_panes(&mut self) -> SuppressedPanes {
|
||||||
|
self.suppressed_panes.drain().collect()
|
||||||
|
}
|
||||||
|
pub fn add_suppressed_panes(&mut self, mut suppressed_panes: SuppressedPanes) {
|
||||||
|
for (pane_id, suppressed_pane_entry) in suppressed_panes.drain() {
|
||||||
|
self.suppressed_panes.insert(pane_id, suppressed_pane_entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
fn new_scrollback_editor_pane(&self, pid: u32) -> TerminalPane {
|
fn new_scrollback_editor_pane(&self, pid: u32) -> TerminalPane {
|
||||||
let next_terminal_position = self.get_next_terminal_position();
|
let next_terminal_position = self.get_next_terminal_position();
|
||||||
let mut new_pane = TerminalPane::new(
|
let mut new_pane = TerminalPane::new(
|
||||||
|
|
|
||||||
|
|
@ -210,6 +210,12 @@ plugins {
|
||||||
configuration location="zellij:configuration"
|
configuration location="zellij:configuration"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Plugins to load in the background when a new session starts
|
||||||
|
load_plugins {
|
||||||
|
// "file:/path/to/my-plugin.wasm"
|
||||||
|
// "https://example.com/my-plugin.wasm"
|
||||||
|
}
|
||||||
|
|
||||||
// Choose what to do when zellij receives SIGTERM, SIGINT, SIGQUIT or SIGHUP
|
// Choose what to do when zellij receives SIGTERM, SIGINT, SIGQUIT or SIGHUP
|
||||||
// eg. when terminal window with an active zellij session is closed
|
// eg. when terminal window with an active zellij session is closed
|
||||||
// (Requires restart)
|
// (Requires restart)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::data::Palette;
|
use crate::data::Palette;
|
||||||
use miette::{Diagnostic, LabeledSpan, NamedSource, SourceCode};
|
use miette::{Diagnostic, LabeledSpan, NamedSource, SourceCode};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, Read};
|
use std::io::{self, Read};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
@ -9,6 +10,7 @@ use thiserror::Error;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use super::keybinds::Keybinds;
|
use super::keybinds::Keybinds;
|
||||||
|
use super::layout::{RunPlugin, RunPluginOrAlias};
|
||||||
use super::options::Options;
|
use super::options::Options;
|
||||||
use super::plugins::{PluginAliases, PluginsConfigError};
|
use super::plugins::{PluginAliases, PluginsConfigError};
|
||||||
use super::theme::{Themes, UiConfig};
|
use super::theme::{Themes, UiConfig};
|
||||||
|
|
@ -29,6 +31,7 @@ pub struct Config {
|
||||||
pub plugins: PluginAliases,
|
pub plugins: PluginAliases,
|
||||||
pub ui: UiConfig,
|
pub ui: UiConfig,
|
||||||
pub env: EnvironmentVariables,
|
pub env: EnvironmentVariables,
|
||||||
|
pub background_plugins: HashSet<RunPluginOrAlias>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
|
@ -400,7 +403,7 @@ mod config_test {
|
||||||
use crate::data::{InputMode, Palette, PaletteColor, PluginTag};
|
use crate::data::{InputMode, Palette, PaletteColor, PluginTag};
|
||||||
use crate::input::layout::{RunPlugin, RunPluginLocation};
|
use crate::input::layout::{RunPlugin, RunPluginLocation};
|
||||||
use crate::input::options::{Clipboard, OnForceClose};
|
use crate::input::options::{Clipboard, OnForceClose};
|
||||||
use crate::input::plugins::{PluginConfig, PluginType};
|
use crate::input::plugins::PluginConfig;
|
||||||
use crate::input::theme::{FrameConfig, Theme, Themes, UiConfig};
|
use crate::input::theme::{FrameConfig, Theme, Themes, UiConfig};
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,6 @@ impl PluginAliases {
|
||||||
pub struct PluginConfig {
|
pub struct PluginConfig {
|
||||||
/// Path of the plugin, see resolve_wasm_bytes for resolution semantics
|
/// Path of the plugin, see resolve_wasm_bytes for resolution semantics
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
/// Plugin type
|
|
||||||
pub run: PluginType,
|
|
||||||
/// Allow command execution from plugin
|
/// Allow command execution from plugin
|
||||||
pub _allow_exec_host_cmd: bool,
|
pub _allow_exec_host_cmd: bool,
|
||||||
/// Original location of the
|
/// Original location of the
|
||||||
|
|
@ -52,7 +50,6 @@ impl PluginConfig {
|
||||||
match &run_plugin.location {
|
match &run_plugin.location {
|
||||||
RunPluginLocation::File(path) => Some(PluginConfig {
|
RunPluginLocation::File(path) => Some(PluginConfig {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
run: PluginType::Pane(None),
|
|
||||||
_allow_exec_host_cmd: run_plugin._allow_exec_host_cmd,
|
_allow_exec_host_cmd: run_plugin._allow_exec_host_cmd,
|
||||||
location: run_plugin.location.clone(),
|
location: run_plugin.location.clone(),
|
||||||
userspace_configuration: run_plugin.configuration.clone(),
|
userspace_configuration: run_plugin.configuration.clone(),
|
||||||
|
|
@ -69,7 +66,6 @@ impl PluginConfig {
|
||||||
{
|
{
|
||||||
Some(PluginConfig {
|
Some(PluginConfig {
|
||||||
path: PathBuf::from(&tag),
|
path: PathBuf::from(&tag),
|
||||||
run: PluginType::Pane(None),
|
|
||||||
_allow_exec_host_cmd: run_plugin._allow_exec_host_cmd,
|
_allow_exec_host_cmd: run_plugin._allow_exec_host_cmd,
|
||||||
location: RunPluginLocation::parse(&format!("zellij:{}", tag), None)
|
location: RunPluginLocation::parse(&format!("zellij:{}", tag), None)
|
||||||
.ok()?,
|
.ok()?,
|
||||||
|
|
@ -82,7 +78,6 @@ impl PluginConfig {
|
||||||
},
|
},
|
||||||
RunPluginLocation::Remote(_) => Some(PluginConfig {
|
RunPluginLocation::Remote(_) => Some(PluginConfig {
|
||||||
path: PathBuf::new(),
|
path: PathBuf::new(),
|
||||||
run: PluginType::Pane(None),
|
|
||||||
_allow_exec_host_cmd: run_plugin._allow_exec_host_cmd,
|
_allow_exec_host_cmd: run_plugin._allow_exec_host_cmd,
|
||||||
location: run_plugin.location.clone(),
|
location: run_plugin.location.clone(),
|
||||||
userspace_configuration: run_plugin.configuration.clone(),
|
userspace_configuration: run_plugin.configuration.clone(),
|
||||||
|
|
@ -187,41 +182,11 @@ impl PluginConfig {
|
||||||
return last_err;
|
return last_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the tab index inside of the plugin type of the run field.
|
|
||||||
pub fn set_tab_index(&mut self, tab_index: usize) {
|
|
||||||
match self.run {
|
|
||||||
PluginType::Pane(..) => {
|
|
||||||
self.run = PluginType::Pane(Some(tab_index));
|
|
||||||
},
|
|
||||||
PluginType::Headless => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_builtin(&self) -> bool {
|
pub fn is_builtin(&self) -> bool {
|
||||||
matches!(self.location, RunPluginLocation::Zellij(_))
|
matches!(self.location, RunPluginLocation::Zellij(_))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type of the plugin. Defaults to Pane.
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Hash, Eq)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub enum PluginType {
|
|
||||||
// TODO: A plugin with output that's cloned across every pane in a tab, or across the entire
|
|
||||||
// application might be useful
|
|
||||||
// Tab
|
|
||||||
// Static
|
|
||||||
/// Starts immediately when Zellij is started and runs without a visible pane
|
|
||||||
Headless,
|
|
||||||
/// Runs once per pane declared inside a layout file
|
|
||||||
Pane(Option<usize>), // tab_index
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PluginType {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Pane(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug, PartialEq)]
|
#[derive(Error, Debug, PartialEq)]
|
||||||
pub enum PluginsConfigError {
|
pub enum PluginsConfigError {
|
||||||
#[error("Duplication in plugin tag names is not allowed: '{}'", String::from(.0.clone()))]
|
#[error("Duplication in plugin tag names is not allowed: '{}'", String::from(.0.clone()))]
|
||||||
|
|
|
||||||
|
|
@ -3616,6 +3616,10 @@ impl Config {
|
||||||
let config_plugins = PluginAliases::from_kdl(kdl_plugin_aliases)?;
|
let config_plugins = PluginAliases::from_kdl(kdl_plugin_aliases)?;
|
||||||
config.plugins.merge(config_plugins);
|
config.plugins.merge(config_plugins);
|
||||||
}
|
}
|
||||||
|
if let Some(kdl_load_plugins) = kdl_config.get("load_plugins") {
|
||||||
|
let load_plugins = load_plugins_from_kdl(kdl_load_plugins)?;
|
||||||
|
config.background_plugins = load_plugins;
|
||||||
|
}
|
||||||
if let Some(kdl_ui_config) = kdl_config.get("ui") {
|
if let Some(kdl_ui_config) = kdl_config.get("ui") {
|
||||||
let config_ui = UiConfig::from_kdl(&kdl_ui_config)?;
|
let config_ui = UiConfig::from_kdl(&kdl_ui_config)?;
|
||||||
config.ui = config.ui.merge(config_ui);
|
config.ui = config.ui.merge(config_ui);
|
||||||
|
|
@ -3640,6 +3644,9 @@ impl Config {
|
||||||
let plugins = self.plugins.to_kdl(add_comments);
|
let plugins = self.plugins.to_kdl(add_comments);
|
||||||
document.nodes_mut().push(plugins);
|
document.nodes_mut().push(plugins);
|
||||||
|
|
||||||
|
let load_plugins = load_plugins_to_kdl(&self.background_plugins, add_comments);
|
||||||
|
document.nodes_mut().push(load_plugins);
|
||||||
|
|
||||||
if let Some(ui_config) = self.ui.to_kdl() {
|
if let Some(ui_config) = self.ui.to_kdl() {
|
||||||
document.nodes_mut().push(ui_config);
|
document.nodes_mut().push(ui_config);
|
||||||
}
|
}
|
||||||
|
|
@ -3729,6 +3736,103 @@ impl PluginAliases {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn load_plugins_to_kdl(
|
||||||
|
background_plugins: &HashSet<RunPluginOrAlias>,
|
||||||
|
add_comments: bool,
|
||||||
|
) -> KdlNode {
|
||||||
|
let mut load_plugins = KdlNode::new("load_plugins");
|
||||||
|
let mut load_plugins_children = KdlDocument::new();
|
||||||
|
for run_plugin_or_alias in background_plugins.iter() {
|
||||||
|
let mut background_plugin_node = KdlNode::new(run_plugin_or_alias.location_string());
|
||||||
|
let mut background_plugin_children = KdlDocument::new();
|
||||||
|
|
||||||
|
let cwd = match run_plugin_or_alias {
|
||||||
|
RunPluginOrAlias::RunPlugin(run_plugin) => run_plugin.initial_cwd.clone(),
|
||||||
|
RunPluginOrAlias::Alias(plugin_alias) => plugin_alias.initial_cwd.clone(),
|
||||||
|
};
|
||||||
|
let mut has_children = false;
|
||||||
|
if let Some(cwd) = cwd.as_ref() {
|
||||||
|
has_children = true;
|
||||||
|
let mut cwd_node = KdlNode::new("cwd");
|
||||||
|
cwd_node.push(cwd.display().to_string());
|
||||||
|
background_plugin_children.nodes_mut().push(cwd_node);
|
||||||
|
}
|
||||||
|
let configuration = match run_plugin_or_alias {
|
||||||
|
RunPluginOrAlias::RunPlugin(run_plugin) => {
|
||||||
|
Some(run_plugin.configuration.inner().clone())
|
||||||
|
},
|
||||||
|
RunPluginOrAlias::Alias(plugin_alias) => plugin_alias
|
||||||
|
.configuration
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| c.inner().clone()),
|
||||||
|
};
|
||||||
|
if let Some(configuration) = configuration {
|
||||||
|
if !configuration.is_empty() {
|
||||||
|
has_children = true;
|
||||||
|
for (config_key, config_value) in configuration {
|
||||||
|
let mut node = KdlNode::new(config_key.to_owned());
|
||||||
|
if config_value == "true" {
|
||||||
|
node.push(KdlValue::Bool(true));
|
||||||
|
} else if config_value == "false" {
|
||||||
|
node.push(KdlValue::Bool(false));
|
||||||
|
} else {
|
||||||
|
node.push(config_value.to_string());
|
||||||
|
}
|
||||||
|
background_plugin_children.nodes_mut().push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if has_children {
|
||||||
|
background_plugin_node.set_children(background_plugin_children);
|
||||||
|
}
|
||||||
|
load_plugins_children
|
||||||
|
.nodes_mut()
|
||||||
|
.push(background_plugin_node);
|
||||||
|
}
|
||||||
|
load_plugins.set_children(load_plugins_children);
|
||||||
|
|
||||||
|
if add_comments {
|
||||||
|
load_plugins.set_leading(format!(
|
||||||
|
"\n{}\n{}\n{}\n",
|
||||||
|
"// Plugins to load in the background when a new session starts",
|
||||||
|
"// eg. \"file:/path/to/my-plugin.wasm\"",
|
||||||
|
"// eg. \"https://example.com/my-plugin.wasm\"",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
load_plugins
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_plugins_from_kdl(
|
||||||
|
kdl_load_plugins: &KdlNode,
|
||||||
|
) -> Result<HashSet<RunPluginOrAlias>, ConfigError> {
|
||||||
|
let mut load_plugins: HashSet<RunPluginOrAlias> = HashSet::new();
|
||||||
|
if let Some(kdl_load_plugins) = kdl_children_nodes!(kdl_load_plugins) {
|
||||||
|
for plugin_block in kdl_load_plugins {
|
||||||
|
let url_node = plugin_block.name();
|
||||||
|
let string_url = url_node.value();
|
||||||
|
let configuration = KdlLayoutParser::parse_plugin_user_configuration(&plugin_block)?;
|
||||||
|
let cwd = kdl_get_string_property_or_child_value!(&plugin_block, "cwd")
|
||||||
|
.map(|s| PathBuf::from(s));
|
||||||
|
let run_plugin_or_alias = RunPluginOrAlias::from_url(
|
||||||
|
&string_url,
|
||||||
|
&Some(configuration.inner().clone()),
|
||||||
|
None,
|
||||||
|
cwd.clone(),
|
||||||
|
)
|
||||||
|
.map_err(|e| {
|
||||||
|
ConfigError::new_kdl_error(
|
||||||
|
format!("Failed to parse plugin: {}", e),
|
||||||
|
url_node.span().offset(),
|
||||||
|
url_node.span().len(),
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.with_initial_cwd(cwd);
|
||||||
|
load_plugins.insert(run_plugin_or_alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(load_plugins)
|
||||||
|
}
|
||||||
|
|
||||||
impl UiConfig {
|
impl UiConfig {
|
||||||
pub fn from_kdl(kdl_ui_config: &KdlNode) -> Result<UiConfig, ConfigError> {
|
pub fn from_kdl(kdl_ui_config: &KdlNode) -> Result<UiConfig, ConfigError> {
|
||||||
let mut ui_config = UiConfig::default();
|
let mut ui_config = UiConfig::default();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: zellij-utils/src/kdl/mod.rs
|
source: zellij-utils/src/kdl/mod.rs
|
||||||
assertion_line: 5060
|
assertion_line: 5525
|
||||||
expression: fake_config_stringified
|
expression: fake_config_stringified
|
||||||
---
|
---
|
||||||
keybinds clear-defaults=true {
|
keybinds clear-defaults=true {
|
||||||
|
|
@ -237,4 +237,6 @@ plugins {
|
||||||
welcome_screen true
|
welcome_screen true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
load_plugins {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: zellij-utils/src/kdl/mod.rs
|
source: zellij-utils/src/kdl/mod.rs
|
||||||
assertion_line: 5441
|
assertion_line: 5537
|
||||||
expression: fake_config_stringified
|
expression: fake_config_stringified
|
||||||
---
|
---
|
||||||
keybinds clear-defaults=true {
|
keybinds clear-defaults=true {
|
||||||
|
|
@ -241,6 +241,12 @@ plugins {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Plugins to load in the background when a new session starts
|
||||||
|
// eg. "file:/path/to/my-plugin.wasm"
|
||||||
|
// eg. "https://example.com/my-plugin.wasm"
|
||||||
|
load_plugins {
|
||||||
|
}
|
||||||
|
|
||||||
// Use a simplified UI without special fonts (arrow glyphs)
|
// Use a simplified UI without special fonts (arrow glyphs)
|
||||||
// Options:
|
// Options:
|
||||||
// - true
|
// - true
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: zellij-utils/src/setup.rs
|
source: zellij-utils/src/setup.rs
|
||||||
assertion_line: 753
|
assertion_line: 754
|
||||||
expression: "format!(\"{:#?}\", config)"
|
expression: "format!(\"{:#?}\", config)"
|
||||||
---
|
---
|
||||||
Config {
|
Config {
|
||||||
|
|
@ -5682,4 +5682,5 @@ Config {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
env: {},
|
env: {},
|
||||||
|
background_plugins: {},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: zellij-utils/src/setup.rs
|
source: zellij-utils/src/setup.rs
|
||||||
assertion_line: 811
|
assertion_line: 812
|
||||||
expression: "format!(\"{:#?}\", config)"
|
expression: "format!(\"{:#?}\", config)"
|
||||||
---
|
---
|
||||||
Config {
|
Config {
|
||||||
|
|
@ -5686,4 +5686,5 @@ Config {
|
||||||
"LAYOUT_ENV_VAR": "make sure I'm also here",
|
"LAYOUT_ENV_VAR": "make sure I'm also here",
|
||||||
"MY_ENV_VAR": "from layout",
|
"MY_ENV_VAR": "from layout",
|
||||||
},
|
},
|
||||||
|
background_plugins: {},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: zellij-utils/src/setup.rs
|
source: zellij-utils/src/setup.rs
|
||||||
assertion_line: 853
|
assertion_line: 854
|
||||||
expression: "format!(\"{:#?}\", config)"
|
expression: "format!(\"{:#?}\", config)"
|
||||||
---
|
---
|
||||||
Config {
|
Config {
|
||||||
|
|
@ -231,4 +231,5 @@ Config {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
env: {},
|
env: {},
|
||||||
|
background_plugins: {},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5989,4 +5989,5 @@ Config {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
env: {},
|
env: {},
|
||||||
|
background_plugins: {},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: zellij-utils/src/setup.rs
|
source: zellij-utils/src/setup.rs
|
||||||
assertion_line: 825
|
assertion_line: 826
|
||||||
expression: "format!(\"{:#?}\", config)"
|
expression: "format!(\"{:#?}\", config)"
|
||||||
---
|
---
|
||||||
Config {
|
Config {
|
||||||
|
|
@ -5682,4 +5682,5 @@ Config {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
env: {},
|
env: {},
|
||||||
|
background_plugins: {},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue