feat: stack pane action (#4255)

* refactor: group placement properties

* add stackpane cli and keybinding

* add test

* refactor: move spawn vertically/horizontally to spawnterminal

* fix tests and cleanups

* some cleanups and minor fixes

* more cleanups

* add stack action to the UI

* style(fmt): rustfmt

* fix serialization

* add to default config

* fix e2e tests

* style(fmt): rustfmt

* fix cli

* fix tests

* docs(changelog): add PR
This commit is contained in:
Aram Drevekenin 2025-07-01 20:17:37 +02:00 committed by GitHub
parent 02a0d055b6
commit ca0048bdcb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 1202 additions and 863 deletions

View file

@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
* feat: web-client, allowing users to share sessions in the browser (https://github.com/zellij-org/zellij/pull/4242) * feat: web-client, allowing users to share sessions in the browser (https://github.com/zellij-org/zellij/pull/4242)
* performance: consolidate renders (https://github.com/zellij-org/zellij/pull/4245) * performance: consolidate renders (https://github.com/zellij-org/zellij/pull/4245)
* feat: add plugin API to replace a pane with another existing pane (https://github.com/zellij-org/zellij/pull/4246) * feat: add plugin API to replace a pane with another existing pane (https://github.com/zellij-org/zellij/pull/4246)
* feat: add "stack" keybinding and CLI action to add a stacked pane to the current pane (https://github.com/zellij-org/zellij/pull/4255)
## [0.42.2] - 2025-04-15 ## [0.42.2] - 2025-04-15
* refactor(terminal): track scroll_region as tuple rather than Option (https://github.com/zellij-org/zellij/pull/4082) * refactor(terminal): track scroll_region as tuple rather than Option (https://github.com/zellij-org/zellij/pull/4082)

View file

@ -31,6 +31,7 @@ keybinds clear-defaults=true {{
bind "n" {{ NewPane; SwitchToMode "Locked"; }} bind "n" {{ NewPane; SwitchToMode "Locked"; }}
bind "d" {{ NewPane "Down"; SwitchToMode "Locked"; }} bind "d" {{ NewPane "Down"; SwitchToMode "Locked"; }}
bind "r" {{ NewPane "Right"; SwitchToMode "Locked"; }} bind "r" {{ NewPane "Right"; SwitchToMode "Locked"; }}
bind "s" {{ NewPane "stacked"; SwitchToMode "Locked"; }}
bind "x" {{ CloseFocus; SwitchToMode "Locked"; }} bind "x" {{ CloseFocus; SwitchToMode "Locked"; }}
bind "f" {{ ToggleFocusFullscreen; SwitchToMode "Locked"; }} bind "f" {{ ToggleFocusFullscreen; SwitchToMode "Locked"; }}
bind "z" {{ TogglePaneFrames; SwitchToMode "Locked"; }} bind "z" {{ TogglePaneFrames; SwitchToMode "Locked"; }}
@ -244,6 +245,7 @@ keybinds clear-defaults=true {{
bind "n" {{ NewPane; SwitchToMode "Normal"; }} bind "n" {{ NewPane; SwitchToMode "Normal"; }}
bind "d" {{ NewPane "Down"; SwitchToMode "Normal"; }} bind "d" {{ NewPane "Down"; SwitchToMode "Normal"; }}
bind "r" {{ NewPane "Right"; SwitchToMode "Normal"; }} bind "r" {{ NewPane "Right"; SwitchToMode "Normal"; }}
bind "s" {{ NewPane "stacked"; SwitchToMode "Normal"; }}
bind "x" {{ CloseFocus; SwitchToMode "Normal"; }} bind "x" {{ CloseFocus; SwitchToMode "Normal"; }}
bind "f" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }} bind "f" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }}
bind "z" {{ TogglePaneFrames; SwitchToMode "Normal"; }} bind "z" {{ TogglePaneFrames; SwitchToMode "Normal"; }}
@ -468,6 +470,7 @@ keybinds clear-defaults=true {{
bind "n" {{ NewPane; SwitchToMode "Normal"; }} bind "n" {{ NewPane; SwitchToMode "Normal"; }}
bind "d" {{ NewPane "Down"; SwitchToMode "Normal"; }} bind "d" {{ NewPane "Down"; SwitchToMode "Normal"; }}
bind "r" {{ NewPane "Right"; SwitchToMode "Normal"; }} bind "r" {{ NewPane "Right"; SwitchToMode "Normal"; }}
bind "s" {{ NewPane "stacked"; SwitchToMode "Normal"; }}
bind "x" {{ CloseFocus; SwitchToMode "Normal"; }} bind "x" {{ CloseFocus; SwitchToMode "Normal"; }}
bind "f" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }} bind "f" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }}
bind "z" {{ TogglePaneFrames; SwitchToMode "Normal"; }} bind "z" {{ TogglePaneFrames; SwitchToMode "Normal"; }}
@ -663,6 +666,7 @@ keybinds clear-defaults=true {{
bind "n" {{ NewPane; SwitchToMode "Normal"; }} bind "n" {{ NewPane; SwitchToMode "Normal"; }}
bind "d" {{ NewPane "Down"; SwitchToMode "Normal"; }} bind "d" {{ NewPane "Down"; SwitchToMode "Normal"; }}
bind "r" {{ NewPane "Right"; SwitchToMode "Normal"; }} bind "r" {{ NewPane "Right"; SwitchToMode "Normal"; }}
bind "s" {{ NewPane "stacked"; SwitchToMode "Normal"; }}
bind "x" {{ CloseFocus; SwitchToMode "Normal"; }} bind "x" {{ CloseFocus; SwitchToMode "Normal"; }}
bind "f" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }} bind "f" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }}
bind "z" {{ TogglePaneFrames; SwitchToMode "Normal"; }} bind "z" {{ TogglePaneFrames; SwitchToMode "Normal"; }}
@ -865,6 +869,7 @@ keybinds clear-defaults=true {{
bind "n" {{ NewPane; SwitchToMode "Normal"; }} bind "n" {{ NewPane; SwitchToMode "Normal"; }}
bind "d" {{ NewPane "Down"; SwitchToMode "Normal"; }} bind "d" {{ NewPane "Down"; SwitchToMode "Normal"; }}
bind "r" {{ NewPane "Right"; SwitchToMode "Normal"; }} bind "r" {{ NewPane "Right"; SwitchToMode "Normal"; }}
bind "s" {{ NewPane "stacked"; SwitchToMode "Normal"; }}
bind "x" {{ CloseFocus; SwitchToMode "Normal"; }} bind "x" {{ CloseFocus; SwitchToMode "Normal"; }}
bind "f" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }} bind "f" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }}
bind "z" {{ TogglePaneFrames; SwitchToMode "Normal"; }} bind "z" {{ TogglePaneFrames; SwitchToMode "Normal"; }}
@ -1042,6 +1047,7 @@ keybinds clear-defaults=true {{
bind "n" {{ NewPane; SwitchToMode "Normal"; }} bind "n" {{ NewPane; SwitchToMode "Normal"; }}
bind "d" {{ NewPane "Down"; SwitchToMode "Normal"; }} bind "d" {{ NewPane "Down"; SwitchToMode "Normal"; }}
bind "r" {{ NewPane "Right"; SwitchToMode "Normal"; }} bind "r" {{ NewPane "Right"; SwitchToMode "Normal"; }}
bind "s" {{ NewPane "stacked"; SwitchToMode "Normal"; }}
bind "x" {{ CloseFocus; SwitchToMode "Normal"; }} bind "x" {{ CloseFocus; SwitchToMode "Normal"; }}
bind "f" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }} bind "f" {{ ToggleFocusFullscreen; SwitchToMode "Normal"; }}
bind "z" {{ TogglePaneFrames; SwitchToMode "Normal"; }} bind "z" {{ TogglePaneFrames; SwitchToMode "Normal"; }}

View file

@ -1276,6 +1276,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec<KeyWithModifier
(s("Toggle Embed"), s("Embed"), single_action_key(&km, &[A::TogglePaneEmbedOrFloating, TO_NORMAL])), (s("Toggle Embed"), s("Embed"), single_action_key(&km, &[A::TogglePaneEmbedOrFloating, TO_NORMAL])),
(s("Split Right"), s("Right"), single_action_key(&km, &[A::NewPane(Some(Direction::Right), None, false), TO_NORMAL])), (s("Split Right"), s("Right"), single_action_key(&km, &[A::NewPane(Some(Direction::Right), None, false), TO_NORMAL])),
(s("Split Down"), s("Down"), single_action_key(&km, &[A::NewPane(Some(Direction::Down), None, false), TO_NORMAL])), (s("Split Down"), s("Down"), single_action_key(&km, &[A::NewPane(Some(Direction::Down), None, false), TO_NORMAL])),
(s("Stack"), s("Stack"), single_action_key(&km, &[A::NewStackedPane(None, None), TO_NORMAL])),
(s("Select pane"), s("Select"), to_basemode_key), (s("Select pane"), s("Select"), to_basemode_key),
]} else if mi.mode == IM::Tab { ]} else if mi.mode == IM::Tab {
// With the default bindings, "Move focus" for tabs is tricky: It binds all the arrow keys // With the default bindings, "Move focus" for tabs is tricky: It binds all the arrow keys

View file

@ -38,6 +38,7 @@ fn main() {
width, width,
height, height,
pinned, pinned,
stacked,
})) = opts.command })) = opts.command
{ {
let cwd = cwd.or_else(|| std::env::current_dir().ok()); let cwd = cwd.or_else(|| std::env::current_dir().ok());
@ -59,6 +60,7 @@ fn main() {
width, width,
height, height,
pinned, pinned,
stacked,
}; };
commands::send_action_to_session(command_cli_action, opts.session, config); commands::send_action_to_session(command_cli_action, opts.session, config);
std::process::exit(0); std::process::exit(0);
@ -77,6 +79,7 @@ fn main() {
})) = opts.command })) = opts.command
{ {
let cwd = None; let cwd = None;
let stacked = false;
let command_cli_action = CliAction::NewPane { let command_cli_action = CliAction::NewPane {
command: vec![], command: vec![],
plugin: Some(url), plugin: Some(url),
@ -94,6 +97,7 @@ fn main() {
width, width,
height, height,
pinned, pinned,
stacked,
}; };
commands::send_action_to_session(command_cli_action, opts.session, config); commands::send_action_to_session(command_cli_action, opts.session, config);
std::process::exit(0); std::process::exit(0);

View file

@ -1045,7 +1045,6 @@ pub fn resize_terminal_window() {
name: "wait for terminal to be resized and app to be re-rendered", name: "wait for terminal to be resized and app to be re-rendered",
instruction: |remote_terminal: RemoteTerminal| -> bool { instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false; let mut step_is_complete = false;
eprintln!("current_snapshot: {}", remote_terminal.current_snapshot());
if remote_terminal.cursor_position_is(53, 2) && remote_terminal.ctrl_plus_appears() if remote_terminal.cursor_position_is(53, 2) && remote_terminal.ctrl_plus_appears()
{ {
// size has been changed // size has been changed

View file

@ -395,6 +395,72 @@ impl TiledPanes {
}, },
} }
} }
pub fn add_pane_to_stack_of_active_pane(
&mut self,
pane_id: PaneId,
mut pane: Box<dyn Pane>,
client_id: ClientId,
) {
let mut pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
let Some(active_pane_id) = self.active_panes.get(&client_id) else {
log::error!("Could not find active pane id for client_id");
return;
};
let pane_id_is_stacked = pane_grid
.get_pane_geom(active_pane_id)
.map(|p| p.is_stacked())
.unwrap_or(false);
if !pane_id_is_stacked {
let _ = pane_grid.make_pane_stacked(&active_pane_id);
}
match pane_grid.make_room_in_stack_of_pane_id_for_pane(active_pane_id) {
Ok(new_pane_geom) => {
pane.set_geom(new_pane_geom);
self.panes.insert(pane_id, pane);
self.set_force_render(); // TODO: why do we need this?
return;
},
Err(e) => {
log::error!("Failed to add pane to stack: {}", e);
},
}
}
pub fn add_pane_to_stack_of_pane_id(
&mut self,
pane_id: PaneId,
mut pane: Box<dyn Pane>,
root_pane_id: PaneId,
) {
let mut pane_grid = TiledPaneGrid::new(
&mut self.panes,
&self.panes_to_hide,
*self.display_area.borrow(),
*self.viewport.borrow(),
);
let pane_id_is_stacked = pane_grid
.get_pane_geom(&root_pane_id)
.map(|p| p.is_stacked())
.unwrap_or(false);
if !pane_id_is_stacked {
let _ = pane_grid.make_pane_stacked(&root_pane_id);
}
match pane_grid.make_room_in_stack_of_pane_id_for_pane(&root_pane_id) {
Ok(new_pane_geom) => {
pane.set_geom(new_pane_geom);
self.panes.insert(pane_id, pane);
self.set_force_render(); // TODO: why do we need this?
return;
},
Err(e) => {
log::error!("Failed to add pane to stack: {}", e);
},
}
}
pub fn fixed_pane_geoms(&self) -> Vec<Viewport> { pub fn fixed_pane_geoms(&self) -> Vec<Viewport> {
self.panes self.panes
.values() .values()

View file

@ -1,7 +1,6 @@
--- ---
source: zellij-server/src/plugins/./unit/plugin_tests.rs source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 6878 expression: "format!(\"{:#?}\",\nnew_tab_event).replace(&format!(\"{:?}\", temp_folder.path()), \"\\\"CWD\\\"\")"
expression: "format!(\"{:#?}\",\n new_tab_event).replace(&format!(\"{:?}\", temp_folder.path()),\n \"\\\"CWD\\\"\")"
--- ---
Some( Some(
SpawnTerminal( SpawnTerminal(
@ -29,8 +28,7 @@ Some(
), ),
), ),
None, None,
None, NoPreference,
None,
true, true,
ClientId( ClientId(
1, 1,

View file

@ -1,6 +1,5 @@
--- ---
source: zellij-server/src/plugins/./unit/plugin_tests.rs source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 4557
expression: "format!(\"{:#?}\", new_tab_event)" expression: "format!(\"{:#?}\", new_tab_event)"
--- ---
Some( Some(
@ -26,11 +25,10 @@ Some(
}, },
), ),
), ),
Some( None,
true, Floating(
None,
), ),
None,
None,
false, false,
ClientId( ClientId(
1, 1,

View file

@ -1,6 +1,5 @@
--- ---
source: zellij-server/src/plugins/./unit/plugin_tests.rs source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 4479
expression: "format!(\"{:#?}\", new_tab_event)" expression: "format!(\"{:#?}\", new_tab_event)"
--- ---
Some( Some(
@ -26,11 +25,10 @@ Some(
}, },
), ),
), ),
Some( None,
false, Tiled(
None,
), ),
None,
None,
false, false,
ClientId( ClientId(
1, 1,

View file

@ -1,7 +1,6 @@
--- ---
source: zellij-server/src/plugins/./unit/plugin_tests.rs source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 3996 expression: "format!(\"{:#?}\",\nnew_tab_event).replace(&format!(\"{:?}\", temp_folder.path()), \"\\\"CWD\\\"\")"
expression: "format!(\"{:#?}\",\n new_tab_event).replace(&format!(\"{:?}\", temp_folder.path()),\n \"\\\"CWD\\\"\")"
--- ---
Some( Some(
SpawnTerminal( SpawnTerminal(
@ -23,13 +22,12 @@ Some(
}, },
), ),
), ),
Some(
true,
),
Some( Some(
"Editing: /path/to/my/file.rs", "Editing: /path/to/my/file.rs",
), ),
Floating(
None, None,
),
false, false,
ClientId( ClientId(
1, 1,

View file

@ -1,7 +1,6 @@
--- ---
source: zellij-server/src/plugins/./unit/plugin_tests.rs source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 4078 expression: "format!(\"{:#?}\",\nnew_tab_event).replace(&format!(\"{:?}\", temp_folder.path()), \"\\\"CWD\\\"\")"
expression: "format!(\"{:#?}\",\n new_tab_event).replace(&format!(\"{:?}\", temp_folder.path()),\n \"\\\"CWD\\\"\")"
--- ---
Some( Some(
SpawnTerminal( SpawnTerminal(
@ -23,13 +22,12 @@ Some(
}, },
), ),
), ),
Some(
false,
),
Some( Some(
"Editing: /path/to/my/file.rs", "Editing: /path/to/my/file.rs",
), ),
Tiled(
None, None,
),
false, false,
ClientId( ClientId(
1, 1,

View file

@ -1,7 +1,6 @@
--- ---
source: zellij-server/src/plugins/./unit/plugin_tests.rs source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 4243 expression: "format!(\"{:#?}\",\nnew_tab_event).replace(&format!(\"{:?}\", temp_folder.path()), \"\\\"CWD\\\"\")"
expression: "format!(\"{:#?}\",\n new_tab_event).replace(&format!(\"{:?}\", temp_folder.path()),\n \"\\\"CWD\\\"\")"
--- ---
Some( Some(
SpawnTerminal( SpawnTerminal(
@ -25,13 +24,12 @@ Some(
}, },
), ),
), ),
Some(
true,
),
Some( Some(
"Editing: /path/to/my/file.rs", "Editing: /path/to/my/file.rs",
), ),
Floating(
None, None,
),
false, false,
ClientId( ClientId(
1, 1,

View file

@ -1,7 +1,6 @@
--- ---
source: zellij-server/src/plugins/./unit/plugin_tests.rs source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 4161 expression: "format!(\"{:#?}\",\nnew_tab_event).replace(&format!(\"{:?}\", temp_folder.path()), \"\\\"CWD\\\"\")"
expression: "format!(\"{:#?}\",\n new_tab_event).replace(&format!(\"{:?}\", temp_folder.path()),\n \"\\\"CWD\\\"\")"
--- ---
Some( Some(
SpawnTerminal( SpawnTerminal(
@ -25,13 +24,12 @@ Some(
}, },
), ),
), ),
Some(
false,
),
Some( Some(
"Editing: /path/to/my/file.rs", "Editing: /path/to/my/file.rs",
), ),
Tiled(
None, None,
),
false, false,
ClientId( ClientId(
1, 1,

View file

@ -1,6 +1,5 @@
--- ---
source: zellij-server/src/plugins/./unit/plugin_tests.rs source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 4401
expression: "format!(\"{:#?}\", new_tab_event)" expression: "format!(\"{:#?}\", new_tab_event)"
--- ---
Some( Some(
@ -19,11 +18,10 @@ Some(
}, },
), ),
), ),
Some( None,
true, Floating(
None,
), ),
None,
None,
false, false,
ClientId( ClientId(
1, 1,

View file

@ -1,6 +1,5 @@
--- ---
source: zellij-server/src/plugins/./unit/plugin_tests.rs source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 4323
expression: "format!(\"{:#?}\", new_tab_event)" expression: "format!(\"{:#?}\", new_tab_event)"
--- ---
Some( Some(
@ -19,11 +18,10 @@ Some(
}, },
), ),
), ),
Some( None,
false, Tiled(
None,
), ),
None,
None,
false, false,
ClientId( ClientId(
1, 1,

View file

@ -2,7 +2,7 @@ use super::PluginInstruction;
use crate::background_jobs::BackgroundJob; use crate::background_jobs::BackgroundJob;
use crate::plugins::plugin_map::PluginEnv; use crate::plugins::plugin_map::PluginEnv;
use crate::plugins::wasm_bridge::handle_plugin_crash; use crate::plugins::wasm_bridge::handle_plugin_crash;
use crate::pty::{ClientTabIndexOrPaneId, PtyInstruction}; use crate::pty::{ClientTabIndexOrPaneId, NewPanePlacement, PtyInstruction};
use crate::route::route_action; use crate::route::route_action;
use crate::ServerInstruction; use crate::ServerInstruction;
use async_std::task; use async_std::task;
@ -744,14 +744,12 @@ fn open_file_near_plugin(
OriginatingPlugin::new(env.plugin_id, env.client_id, context), OriginatingPlugin::new(env.plugin_id, env.client_id, context),
); );
let title = format!("Editing: {}", open_file_payload.path.display()); let title = format!("Editing: {}", open_file_payload.path.display());
let should_float = false;
let start_suppressed = false; let start_suppressed = false;
let open_file = TerminalAction::OpenFile(open_file_payload); let open_file = TerminalAction::OpenFile(open_file_payload);
let pty_instr = PtyInstruction::SpawnTerminal( let pty_instr = PtyInstruction::SpawnTerminal(
Some(open_file), Some(open_file),
Some(should_float),
Some(title), Some(title),
None, NewPanePlacement::default(),
start_suppressed, start_suppressed,
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)), ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
); );
@ -774,14 +772,12 @@ fn open_file_floating_near_plugin(
OriginatingPlugin::new(env.plugin_id, env.client_id, context), OriginatingPlugin::new(env.plugin_id, env.client_id, context),
); );
let title = format!("Editing: {}", open_file_payload.path.display()); let title = format!("Editing: {}", open_file_payload.path.display());
let should_float = true;
let start_suppressed = false; let start_suppressed = false;
let open_file = TerminalAction::OpenFile(open_file_payload); let open_file = TerminalAction::OpenFile(open_file_payload);
let pty_instr = PtyInstruction::SpawnTerminal( let pty_instr = PtyInstruction::SpawnTerminal(
Some(open_file), Some(open_file),
Some(should_float),
Some(title), Some(title),
floating_pane_coordinates, NewPanePlacement::Floating(floating_pane_coordinates),
start_suppressed, start_suppressed,
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)), ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
); );
@ -834,7 +830,6 @@ fn open_terminal(env: &PluginEnv, cwd: PathBuf) {
fn open_terminal_near_plugin(env: &PluginEnv, cwd: PathBuf) { fn open_terminal_near_plugin(env: &PluginEnv, cwd: PathBuf) {
let cwd = env.plugin_cwd.join(cwd); let cwd = env.plugin_cwd.join(cwd);
let should_float = false;
let mut default_shell = env.default_shell.clone().unwrap_or_else(|| { let mut default_shell = env.default_shell.clone().unwrap_or_else(|| {
TerminalAction::RunCommand(RunCommand { TerminalAction::RunCommand(RunCommand {
command: env.path_to_default_shell.clone(), command: env.path_to_default_shell.clone(),
@ -845,9 +840,8 @@ fn open_terminal_near_plugin(env: &PluginEnv, cwd: PathBuf) {
default_shell.change_cwd(cwd); default_shell.change_cwd(cwd);
let _ = env.senders.send_to_pty(PtyInstruction::SpawnTerminal( let _ = env.senders.send_to_pty(PtyInstruction::SpawnTerminal(
Some(default_shell), Some(default_shell),
Some(should_float),
name, name,
None, NewPanePlacement::Tiled(None),
false, false,
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)), ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
)); ));
@ -881,7 +875,6 @@ fn open_terminal_floating_near_plugin(
floating_pane_coordinates: Option<FloatingPaneCoordinates>, floating_pane_coordinates: Option<FloatingPaneCoordinates>,
) { ) {
let cwd = env.plugin_cwd.join(cwd); let cwd = env.plugin_cwd.join(cwd);
let should_float = true;
let mut default_shell = env.default_shell.clone().unwrap_or_else(|| { let mut default_shell = env.default_shell.clone().unwrap_or_else(|| {
TerminalAction::RunCommand(RunCommand { TerminalAction::RunCommand(RunCommand {
command: env.path_to_default_shell.clone(), command: env.path_to_default_shell.clone(),
@ -892,9 +885,8 @@ fn open_terminal_floating_near_plugin(
let name = None; let name = None;
let _ = env.senders.send_to_pty(PtyInstruction::SpawnTerminal( let _ = env.senders.send_to_pty(PtyInstruction::SpawnTerminal(
Some(default_shell), Some(default_shell),
Some(should_float),
name, name,
floating_pane_coordinates, NewPanePlacement::Floating(floating_pane_coordinates),
false, false,
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)), ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
)); ));
@ -1021,7 +1013,6 @@ fn open_command_pane_near_plugin(
let hold_on_close = true; let hold_on_close = true;
let hold_on_start = false; let hold_on_start = false;
let name = None; let name = None;
let should_float = false;
let run_command_action = RunCommandAction { let run_command_action = RunCommandAction {
command, command,
args, args,
@ -1038,9 +1029,8 @@ fn open_command_pane_near_plugin(
let run_cmd = TerminalAction::RunCommand(run_command_action.into()); let run_cmd = TerminalAction::RunCommand(run_command_action.into());
let _ = env.senders.send_to_pty(PtyInstruction::SpawnTerminal( let _ = env.senders.send_to_pty(PtyInstruction::SpawnTerminal(
Some(run_cmd), Some(run_cmd),
Some(should_float),
name, name,
None, NewPanePlacement::Tiled(None),
false, false,
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)), ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
)); ));
@ -1090,7 +1080,6 @@ fn open_command_pane_floating_near_plugin(
let hold_on_close = true; let hold_on_close = true;
let hold_on_start = false; let hold_on_start = false;
let name = None; let name = None;
let should_float = true;
let run_command_action = RunCommandAction { let run_command_action = RunCommandAction {
command, command,
args, args,
@ -1107,9 +1096,8 @@ fn open_command_pane_floating_near_plugin(
let run_cmd = TerminalAction::RunCommand(run_command_action.into()); let run_cmd = TerminalAction::RunCommand(run_command_action.into());
let _ = env.senders.send_to_pty(PtyInstruction::SpawnTerminal( let _ = env.senders.send_to_pty(PtyInstruction::SpawnTerminal(
Some(run_cmd), Some(run_cmd),
Some(should_float),
name, name,
floating_pane_coordinates, NewPanePlacement::Floating(floating_pane_coordinates),
false, false,
ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)), ClientTabIndexOrPaneId::PaneId(PaneId::Plugin(env.plugin_id)),
)); ));
@ -1177,9 +1165,8 @@ fn open_command_pane_background(
let run_cmd = TerminalAction::RunCommand(run_command_action.into()); let run_cmd = TerminalAction::RunCommand(run_command_action.into());
let _ = env.senders.send_to_pty(PtyInstruction::SpawnTerminal( let _ = env.senders.send_to_pty(PtyInstruction::SpawnTerminal(
Some(run_cmd), Some(run_cmd),
None,
name, name,
None, NewPanePlacement::default(),
start_suppressed, start_suppressed,
ClientTabIndexOrPaneId::ClientId(env.client_id), ClientTabIndexOrPaneId::ClientId(env.client_id),
)); ));

View file

@ -16,7 +16,7 @@ use nix::unistd::Pid;
use std::sync::Arc; use std::sync::Arc;
use std::{collections::HashMap, os::unix::io::RawFd, path::PathBuf}; use std::{collections::HashMap, os::unix::io::RawFd, path::PathBuf};
use zellij_utils::{ use zellij_utils::{
data::{Event, FloatingPaneCoordinates, OriginatingPlugin}, data::{Direction, Event, FloatingPaneCoordinates, OriginatingPlugin},
errors::prelude::*, errors::prelude::*,
errors::{ContextType, PtyContext}, errors::{ContextType, PtyContext},
input::{ input::{
@ -37,26 +37,95 @@ pub enum ClientTabIndexOrPaneId {
PaneId(PaneId), PaneId(PaneId),
} }
// TODO: move elsewhere
#[derive(Clone, Debug)]
pub enum NewPanePlacement {
NoPreference,
Tiled(Option<Direction>),
Floating(Option<FloatingPaneCoordinates>),
InPlace {
pane_id_to_replace: Option<PaneId>,
close_replaced_pane: bool,
},
Stacked(Option<PaneId>),
}
impl Default for NewPanePlacement {
fn default() -> Self {
NewPanePlacement::NoPreference
}
}
impl NewPanePlacement {
pub fn with_floating_pane_coordinates(
floating_pane_coordinates: Option<FloatingPaneCoordinates>,
) -> Self {
NewPanePlacement::Floating(floating_pane_coordinates)
}
pub fn with_should_be_in_place(
self,
should_be_in_place: bool,
close_replaced_pane: bool,
) -> Self {
if should_be_in_place {
NewPanePlacement::InPlace {
pane_id_to_replace: None,
close_replaced_pane,
}
} else {
self
}
}
pub fn with_pane_id_to_replace(
pane_id_to_replace: Option<PaneId>,
close_replaced_pane: bool,
) -> Self {
NewPanePlacement::InPlace {
pane_id_to_replace,
close_replaced_pane,
}
}
pub fn should_float(&self) -> Option<bool> {
match self {
NewPanePlacement::Floating(_) => Some(true),
NewPanePlacement::Tiled(_) => Some(false),
_ => None,
}
}
pub fn floating_pane_coordinates(&self) -> Option<FloatingPaneCoordinates> {
match self {
NewPanePlacement::Floating(floating_pane_coordinates) => {
floating_pane_coordinates.clone()
},
_ => None,
}
}
pub fn should_stack(&self) -> bool {
match self {
NewPanePlacement::Stacked(_) => true,
_ => false,
}
}
pub fn id_of_stack_root(&self) -> Option<PaneId> {
match self {
NewPanePlacement::Stacked(id) => *id,
_ => None,
}
}
}
/// Instructions related to PTYs (pseudoterminals). /// Instructions related to PTYs (pseudoterminals).
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum PtyInstruction { pub enum PtyInstruction {
SpawnTerminal( SpawnTerminal(
Option<TerminalAction>, Option<TerminalAction>,
Option<bool>,
Option<String>, Option<String>,
Option<FloatingPaneCoordinates>, NewPanePlacement,
bool, // start suppressed bool, // start suppressed
ClientTabIndexOrPaneId, ClientTabIndexOrPaneId,
), // bool (if Some) is ), // bool (if Some) is
// should_float, String is an optional pane name // should_float, String is an optional pane name
OpenInPlaceEditor(PathBuf, Option<usize>, ClientTabIndexOrPaneId), // Option<usize> is the optional line number OpenInPlaceEditor(PathBuf, Option<usize>, ClientTabIndexOrPaneId), // Option<usize> is the optional line number
SpawnTerminalVertically(Option<TerminalAction>, Option<String>, ClientId), // String is an
// optional pane
// name
// bool is start_suppressed
SpawnTerminalHorizontally(Option<TerminalAction>, Option<String>, ClientId), // String is an
// optional pane
// name
UpdateActivePane(Option<PaneId>, ClientId), UpdateActivePane(Option<PaneId>, ClientId),
GoToTab(TabIndex, ClientId), GoToTab(TabIndex, ClientId),
NewTab( NewTab(
@ -114,8 +183,6 @@ impl From<&PtyInstruction> for PtyContext {
match *pty_instruction { match *pty_instruction {
PtyInstruction::SpawnTerminal(..) => PtyContext::SpawnTerminal, PtyInstruction::SpawnTerminal(..) => PtyContext::SpawnTerminal,
PtyInstruction::OpenInPlaceEditor(..) => PtyContext::OpenInPlaceEditor, PtyInstruction::OpenInPlaceEditor(..) => PtyContext::OpenInPlaceEditor,
PtyInstruction::SpawnTerminalVertically(..) => PtyContext::SpawnTerminalVertically,
PtyInstruction::SpawnTerminalHorizontally(..) => PtyContext::SpawnTerminalHorizontally,
PtyInstruction::UpdateActivePane(..) => PtyContext::UpdateActivePane, PtyInstruction::UpdateActivePane(..) => PtyContext::UpdateActivePane,
PtyInstruction::GoToTab(..) => PtyContext::GoToTab, PtyInstruction::GoToTab(..) => PtyContext::GoToTab,
PtyInstruction::ClosePane(_) => PtyContext::ClosePane, PtyInstruction::ClosePane(_) => PtyContext::ClosePane,
@ -153,9 +220,8 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
match event { match event {
PtyInstruction::SpawnTerminal( PtyInstruction::SpawnTerminal(
terminal_action, terminal_action,
should_float,
name, name,
floating_pane_coordinates, new_pane_placement,
start_suppressed, start_suppressed,
client_or_tab_index, client_or_tab_index,
) => { ) => {
@ -235,10 +301,9 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
.send_to_screen(ScreenInstruction::NewPane( .send_to_screen(ScreenInstruction::NewPane(
PaneId::Terminal(pid), PaneId::Terminal(pid),
pane_title, pane_title,
should_float,
hold_for_command, hold_for_command,
invoked_with, invoked_with,
floating_pane_coordinates, new_pane_placement,
start_suppressed, start_suppressed,
client_or_tab_index, client_or_tab_index,
)) ))
@ -253,10 +318,9 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
.send_to_screen(ScreenInstruction::NewPane( .send_to_screen(ScreenInstruction::NewPane(
PaneId::Terminal(*terminal_id), PaneId::Terminal(*terminal_id),
pane_title, pane_title,
should_float,
hold_for_command, hold_for_command,
invoked_with, invoked_with,
floating_pane_coordinates, new_pane_placement,
start_suppressed, start_suppressed,
client_or_tab_index, client_or_tab_index,
)) ))
@ -390,144 +454,6 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
}, },
} }
}, },
PtyInstruction::SpawnTerminalVertically(terminal_action, name, client_id) => {
let err_context =
|| format!("failed to spawn terminal vertically for client {client_id}");
let (hold_on_close, run_command, pane_title) = match &terminal_action {
Some(TerminalAction::RunCommand(run_command)) => (
run_command.hold_on_close,
Some(run_command.clone()),
Some(name.unwrap_or_else(|| run_command.to_string())),
),
_ => (false, None, name),
};
match pty
.spawn_terminal(terminal_action, ClientTabIndexOrPaneId::ClientId(client_id))
.with_context(err_context)
{
Ok((pid, starts_held)) => {
let hold_for_command = if starts_held { run_command } else { None };
pty.bus
.senders
.send_to_screen(ScreenInstruction::VerticalSplit(
PaneId::Terminal(pid),
pane_title,
hold_for_command,
client_id,
))
.with_context(err_context)?;
},
Err(err) => match err.downcast_ref::<ZellijError>() {
Some(ZellijError::CommandNotFound { terminal_id, .. }) => {
let hold_for_command = None; // we do not hold an "error" pane
if hold_on_close {
pty.bus
.senders
.send_to_screen(ScreenInstruction::VerticalSplit(
PaneId::Terminal(*terminal_id),
pane_title,
hold_for_command,
client_id,
))
.with_context(err_context)?;
if let Some(run_command) = run_command {
pty.bus
.senders
.send_to_screen(ScreenInstruction::PtyBytes(
*terminal_id,
format!(
"Command not found: {}",
run_command.command.display()
)
.as_bytes()
.to_vec(),
))
.with_context(err_context)?;
pty.bus
.senders
.send_to_screen(ScreenInstruction::HoldPane(
PaneId::Terminal(*terminal_id),
Some(2), // exit status
run_command,
))
.with_context(err_context)?;
}
}
},
_ => Err::<(), _>(err).non_fatal(),
},
}
},
PtyInstruction::SpawnTerminalHorizontally(terminal_action, name, client_id) => {
let err_context =
|| format!("failed to spawn terminal horizontally for client {client_id}");
let (hold_on_close, run_command, pane_title) = match &terminal_action {
Some(TerminalAction::RunCommand(run_command)) => (
run_command.hold_on_close,
Some(run_command.clone()),
Some(name.unwrap_or_else(|| run_command.to_string())),
),
_ => (false, None, name),
};
match pty
.spawn_terminal(terminal_action, ClientTabIndexOrPaneId::ClientId(client_id))
.with_context(err_context)
{
Ok((pid, starts_held)) => {
let hold_for_command = if starts_held { run_command } else { None };
pty.bus
.senders
.send_to_screen(ScreenInstruction::HorizontalSplit(
PaneId::Terminal(pid),
pane_title,
hold_for_command,
client_id,
))
.with_context(err_context)?;
},
Err(err) => match err.downcast_ref::<ZellijError>() {
Some(ZellijError::CommandNotFound { terminal_id, .. }) => {
if hold_on_close {
let hold_for_command = None; // we do not hold an "error" pane
pty.bus
.senders
.send_to_screen(ScreenInstruction::HorizontalSplit(
PaneId::Terminal(*terminal_id),
pane_title,
hold_for_command,
client_id,
))
.with_context(err_context)?;
if let Some(run_command) = run_command {
pty.bus
.senders
.send_to_screen(ScreenInstruction::PtyBytes(
*terminal_id,
format!(
"Command not found: {}",
run_command.command.display()
)
.as_bytes()
.to_vec(),
))
.with_context(err_context)?;
pty.bus
.senders
.send_to_screen(ScreenInstruction::HoldPane(
PaneId::Terminal(*terminal_id),
Some(2), // exit status
run_command,
))
.with_context(err_context)?;
}
}
},
_ => Err::<(), _>(err).non_fatal(),
},
}
},
PtyInstruction::UpdateActivePane(pane_id, client_id) => { PtyInstruction::UpdateActivePane(pane_id, client_id) => {
pty.set_active_pane(pane_id, client_id); pty.set_active_pane(pane_id, client_id);
}, },

View file

@ -6,7 +6,7 @@ use crate::{
os_input_output::ServerOsApi, os_input_output::ServerOsApi,
panes::PaneId, panes::PaneId,
plugins::PluginInstruction, plugins::PluginInstruction,
pty::{ClientTabIndexOrPaneId, PtyInstruction}, pty::{ClientTabIndexOrPaneId, NewPanePlacement, PtyInstruction},
screen::ScreenInstruction, screen::ScreenInstruction,
ServerInstruction, SessionMetaData, SessionState, ServerInstruction, SessionMetaData, SessionState,
}; };
@ -257,30 +257,13 @@ pub(crate) fn route_action(
}, },
Action::NewPane(direction, name, start_suppressed) => { Action::NewPane(direction, name, start_suppressed) => {
let shell = default_shell.clone(); let shell = default_shell.clone();
let pty_instr = match direction { senders.send_to_pty(PtyInstruction::SpawnTerminal(
Some(Direction::Left) => {
PtyInstruction::SpawnTerminalVertically(shell, name, client_id)
},
Some(Direction::Right) => {
PtyInstruction::SpawnTerminalVertically(shell, name, client_id)
},
Some(Direction::Up) => {
PtyInstruction::SpawnTerminalHorizontally(shell, name, client_id)
},
Some(Direction::Down) => {
PtyInstruction::SpawnTerminalHorizontally(shell, name, client_id)
},
// No direction specified - try to put it in the biggest available spot
None => PtyInstruction::SpawnTerminal(
shell, shell,
None,
name, name,
None, NewPanePlacement::Tiled(direction),
start_suppressed, start_suppressed,
ClientTabIndexOrPaneId::ClientId(client_id), ClientTabIndexOrPaneId::ClientId(client_id),
), ));
};
senders.send_to_pty(pty_instr).with_context(err_context)?;
}, },
Action::EditFile( Action::EditFile(
open_file_payload, open_file_payload,
@ -292,25 +275,8 @@ pub(crate) fn route_action(
) => { ) => {
let title = format!("Editing: {}", open_file_payload.path.display()); let title = format!("Editing: {}", open_file_payload.path.display());
let open_file = TerminalAction::OpenFile(open_file_payload); let open_file = TerminalAction::OpenFile(open_file_payload);
let pty_instr = match (split_direction, should_float, should_open_in_place) { let pty_instr = if should_open_in_place {
(Some(Direction::Left), false, false) => { match pane_id {
PtyInstruction::SpawnTerminalVertically(Some(open_file), Some(title), client_id)
},
(Some(Direction::Right), false, false) => {
PtyInstruction::SpawnTerminalVertically(Some(open_file), Some(title), client_id)
},
(Some(Direction::Up), false, false) => PtyInstruction::SpawnTerminalHorizontally(
Some(open_file),
Some(title),
client_id,
),
(Some(Direction::Down), false, false) => PtyInstruction::SpawnTerminalHorizontally(
Some(open_file),
Some(title),
client_id,
),
// open terminal in place
(_, _, true) => match pane_id {
Some(pane_id) => PtyInstruction::SpawnInPlaceTerminal( Some(pane_id) => PtyInstruction::SpawnInPlaceTerminal(
Some(open_file), Some(open_file),
Some(title), Some(title),
@ -323,17 +289,19 @@ pub(crate) fn route_action(
false, false,
ClientTabIndexOrPaneId::ClientId(client_id), ClientTabIndexOrPaneId::ClientId(client_id),
), ),
}, }
// Open either floating terminal if we were asked with should_float or defer } else {
// placement to screen PtyInstruction::SpawnTerminal(
(None, _, _) | (_, true, _) => PtyInstruction::SpawnTerminal(
Some(open_file), Some(open_file),
Some(should_float),
Some(title), Some(title),
floating_pane_coordinates, if should_float {
NewPanePlacement::Floating(floating_pane_coordinates)
} else {
NewPanePlacement::Tiled(split_direction)
},
start_suppressed, start_suppressed,
ClientTabIndexOrPaneId::ClientId(client_id), ClientTabIndexOrPaneId::ClientId(client_id),
), )
}; };
senders.send_to_pty(pty_instr).with_context(err_context)?; senders.send_to_pty(pty_instr).with_context(err_context)?;
}, },
@ -368,16 +336,14 @@ pub(crate) fn route_action(
.with_context(err_context)?; .with_context(err_context)?;
}, },
Action::NewFloatingPane(run_command, name, floating_pane_coordinates) => { Action::NewFloatingPane(run_command, name, floating_pane_coordinates) => {
let should_float = true;
let run_cmd = run_command let run_cmd = run_command
.map(|cmd| TerminalAction::RunCommand(cmd.into())) .map(|cmd| TerminalAction::RunCommand(cmd.into()))
.or_else(|| default_shell.clone()); .or_else(|| default_shell.clone());
senders senders
.send_to_pty(PtyInstruction::SpawnTerminal( .send_to_pty(PtyInstruction::SpawnTerminal(
run_cmd, run_cmd,
Some(should_float),
name, name,
floating_pane_coordinates, NewPanePlacement::Floating(floating_pane_coordinates),
false, false,
ClientTabIndexOrPaneId::ClientId(client_id), ClientTabIndexOrPaneId::ClientId(client_id),
)) ))
@ -410,35 +376,46 @@ pub(crate) fn route_action(
}, },
} }
}, },
Action::NewTiledPane(direction, run_command, name) => { Action::NewStackedPane(run_command, name) => {
let should_float = false;
let run_cmd = run_command let run_cmd = run_command
.map(|cmd| TerminalAction::RunCommand(cmd.into())) .map(|cmd| TerminalAction::RunCommand(cmd.into()))
.or_else(|| default_shell.clone()); .or_else(|| default_shell.clone());
let pty_instr = match direction { match pane_id {
Some(Direction::Left) => { Some(pane_id) => {
PtyInstruction::SpawnTerminalVertically(run_cmd, name, client_id) senders
}, .send_to_pty(PtyInstruction::SpawnTerminal(
Some(Direction::Right) => {
PtyInstruction::SpawnTerminalVertically(run_cmd, name, client_id)
},
Some(Direction::Up) => {
PtyInstruction::SpawnTerminalHorizontally(run_cmd, name, client_id)
},
Some(Direction::Down) => {
PtyInstruction::SpawnTerminalHorizontally(run_cmd, name, client_id)
},
// No direction specified - try to put it in the biggest available spot
None => PtyInstruction::SpawnTerminal(
run_cmd, run_cmd,
Some(should_float),
name, name,
None, NewPanePlacement::Stacked(Some(pane_id)),
false,
ClientTabIndexOrPaneId::PaneId(pane_id),
))
.with_context(err_context)?;
},
None => {
senders
.send_to_pty(PtyInstruction::SpawnTerminal(
run_cmd,
name,
NewPanePlacement::Stacked(None),
false, false,
ClientTabIndexOrPaneId::ClientId(client_id), ClientTabIndexOrPaneId::ClientId(client_id),
), ))
}; .with_context(err_context)?;
senders.send_to_pty(pty_instr).with_context(err_context)?; },
}
},
Action::NewTiledPane(direction, run_command, name) => {
let run_cmd = run_command
.map(|cmd| TerminalAction::RunCommand(cmd.into()))
.or_else(|| default_shell.clone());
let _ = senders.send_to_pty(PtyInstruction::SpawnTerminal(
run_cmd,
name,
NewPanePlacement::Tiled(direction),
false,
ClientTabIndexOrPaneId::ClientId(client_id),
));
}, },
Action::TogglePaneEmbedOrFloating => { Action::TogglePaneEmbedOrFloating => {
senders senders
@ -465,30 +442,13 @@ pub(crate) fn route_action(
}, },
Action::Run(command) => { Action::Run(command) => {
let run_cmd = Some(TerminalAction::RunCommand(command.clone().into())); let run_cmd = Some(TerminalAction::RunCommand(command.clone().into()));
let pty_instr = match command.direction { let _ = senders.send_to_pty(PtyInstruction::SpawnTerminal(
Some(Direction::Left) => {
PtyInstruction::SpawnTerminalVertically(run_cmd, None, client_id)
},
Some(Direction::Right) => {
PtyInstruction::SpawnTerminalVertically(run_cmd, None, client_id)
},
Some(Direction::Up) => {
PtyInstruction::SpawnTerminalHorizontally(run_cmd, None, client_id)
},
Some(Direction::Down) => {
PtyInstruction::SpawnTerminalHorizontally(run_cmd, None, client_id)
},
// No direction specified - try to put it in the biggest available spot
None => PtyInstruction::SpawnTerminal(
run_cmd, run_cmd,
None, None,
None, NewPanePlacement::Tiled(command.direction),
None,
false, false,
ClientTabIndexOrPaneId::ClientId(client_id), ClientTabIndexOrPaneId::ClientId(client_id),
), ));
};
senders.send_to_pty(pty_instr).with_context(err_context)?;
}, },
Action::CloseFocus => { Action::CloseFocus => {
senders senders

View file

@ -43,7 +43,7 @@ use crate::{
panes::sixel::SixelImageStore, panes::sixel::SixelImageStore,
panes::PaneId, panes::PaneId,
plugins::{PluginId, PluginInstruction, PluginRenderAsset}, plugins::{PluginId, PluginInstruction, PluginRenderAsset},
pty::{get_default_shell, ClientTabIndexOrPaneId, PtyInstruction, VteBytes}, pty::{get_default_shell, ClientTabIndexOrPaneId, NewPanePlacement, PtyInstruction, VteBytes},
tab::{SuppressedPanes, Tab}, tab::{SuppressedPanes, Tab},
thread_bus::Bus, thread_bus::Bus,
ui::{ ui::{
@ -138,7 +138,6 @@ macro_rules! active_tab_and_connected_client_id {
} }
type InitialTitle = String; type InitialTitle = String;
type ShouldFloat = bool;
type HoldForCommand = Option<RunCommand>; type HoldForCommand = Option<RunCommand>;
/// Instructions that can be sent to the [`Screen`]. /// Instructions that can be sent to the [`Screen`].
@ -151,10 +150,9 @@ pub enum ScreenInstruction {
NewPane( NewPane(
PaneId, PaneId,
Option<InitialTitle>, Option<InitialTitle>,
Option<ShouldFloat>,
HoldForCommand, HoldForCommand,
Option<Run>, // invoked with Option<Run>, // invoked with
Option<FloatingPaneCoordinates>, NewPanePlacement,
bool, // start suppressed bool, // start suppressed
ClientTabIndexOrPaneId, ClientTabIndexOrPaneId,
), ),
@ -3335,10 +3333,9 @@ pub(crate) fn screen_thread_main(
ScreenInstruction::NewPane( ScreenInstruction::NewPane(
pid, pid,
initial_pane_title, initial_pane_title,
should_float,
hold_for_command, hold_for_command,
invoked_with, invoked_with,
floating_pane_coordinates, new_pane_placement,
start_suppressed, start_suppressed,
client_or_tab_index, client_or_tab_index,
) => { ) => {
@ -3347,11 +3344,10 @@ pub(crate) fn screen_thread_main(
active_tab_and_connected_client_id!(screen, client_id, |tab: &mut Tab, client_id: ClientId| { active_tab_and_connected_client_id!(screen, client_id, |tab: &mut Tab, client_id: ClientId| {
tab.new_pane(pid, tab.new_pane(pid,
initial_pane_title, initial_pane_title,
should_float,
invoked_with, invoked_with,
floating_pane_coordinates,
start_suppressed, start_suppressed,
true, true,
new_pane_placement,
Some(client_id) Some(client_id)
) )
}, ?); }, ?);
@ -3374,11 +3370,10 @@ pub(crate) fn screen_thread_main(
active_tab.new_pane( active_tab.new_pane(
pid, pid,
initial_pane_title, initial_pane_title,
should_float,
invoked_with, invoked_with,
floating_pane_coordinates,
start_suppressed, start_suppressed,
true, true,
new_pane_placement,
None, None,
)?; )?;
if let Some(hold_for_command) = hold_for_command { if let Some(hold_for_command) = hold_for_command {
@ -3397,11 +3392,10 @@ pub(crate) fn screen_thread_main(
tab.new_pane( tab.new_pane(
pid, pid,
initial_pane_title, initial_pane_title,
should_float,
invoked_with, invoked_with,
floating_pane_coordinates,
start_suppressed, start_suppressed,
true, true,
new_pane_placement,
None, None,
)?; )?;
if let Some(hold_for_command) = hold_for_command { if let Some(hold_for_command) = hold_for_command {
@ -4659,9 +4653,28 @@ pub(crate) fn screen_thread_main(
should_focus_plugin, should_focus_plugin,
client_id, client_id,
) => { ) => {
let close_replaced_pane = false; // TODO: support this
let mut new_pane_placement = NewPanePlacement::default();
let maybe_should_float = should_float;
let should_be_tiled = maybe_should_float.map(|f| !f).unwrap_or(false);
let should_float = maybe_should_float.unwrap_or(false);
if floating_pane_coordinates.is_some() || should_float {
new_pane_placement = NewPanePlacement::with_floating_pane_coordinates(
floating_pane_coordinates.clone(),
);
}
if should_be_tiled {
new_pane_placement = NewPanePlacement::Tiled(None);
}
if should_be_in_place {
new_pane_placement = NewPanePlacement::with_pane_id_to_replace(
pane_id_to_replace,
close_replaced_pane,
);
}
if screen.active_tab_indices.is_empty() && tab_index.is_none() { if screen.active_tab_indices.is_empty() && tab_index.is_none() {
pending_events_waiting_for_client.push(ScreenInstruction::AddPlugin( pending_events_waiting_for_client.push(ScreenInstruction::AddPlugin(
should_float, maybe_should_float,
should_be_in_place, should_be_in_place,
run_plugin_or_alias, run_plugin_or_alias,
pane_title, pane_title,
@ -4718,11 +4731,10 @@ pub(crate) fn screen_thread_main(
active_tab.new_pane( active_tab.new_pane(
PaneId::Plugin(plugin_id), PaneId::Plugin(plugin_id),
Some(pane_title), Some(pane_title),
should_float,
Some(run_plugin), Some(run_plugin),
floating_pane_coordinates,
start_suppressed, start_suppressed,
should_focus_plugin.unwrap_or(true), should_focus_plugin.unwrap_or(true),
new_pane_placement,
Some(client_id), Some(client_id),
) )
}, ?); }, ?);
@ -4732,11 +4744,10 @@ pub(crate) fn screen_thread_main(
active_tab.new_pane( active_tab.new_pane(
PaneId::Plugin(plugin_id), PaneId::Plugin(plugin_id),
Some(pane_title), Some(pane_title),
should_float,
Some(run_plugin), Some(run_plugin),
None,
start_suppressed, start_suppressed,
should_focus_plugin.unwrap_or(true), should_focus_plugin.unwrap_or(true),
new_pane_placement,
None, None,
)?; )?;
} else { } else {

View file

@ -39,7 +39,7 @@ use crate::{
panes::{FloatingPanes, TiledPanes}, panes::{FloatingPanes, TiledPanes},
panes::{LinkHandler, PaneId, PluginPane, TerminalPane}, panes::{LinkHandler, PaneId, PluginPane, TerminalPane},
plugins::PluginInstruction, plugins::PluginInstruction,
pty::{ClientTabIndexOrPaneId, PtyInstruction, VteBytes}, pty::{ClientTabIndexOrPaneId, NewPanePlacement, PtyInstruction, VteBytes},
thread_bus::ThreadSenders, thread_bus::ThreadSenders,
ClientId, ServerInstruction, ClientId, ServerInstruction,
}; };
@ -1238,7 +1238,6 @@ impl Tab {
}, },
None => { None => {
let name = None; let name = None;
let should_float = true;
let client_id_or_tab_index = match client_id { let client_id_or_tab_index = match client_id {
Some(client_id) => ClientTabIndexOrPaneId::ClientId(client_id), Some(client_id) => ClientTabIndexOrPaneId::ClientId(client_id),
None => ClientTabIndexOrPaneId::TabIndex(self.index), None => ClientTabIndexOrPaneId::TabIndex(self.index),
@ -1246,9 +1245,8 @@ impl Tab {
let should_start_suppressed = false; let should_start_suppressed = false;
let instruction = PtyInstruction::SpawnTerminal( let instruction = PtyInstruction::SpawnTerminal(
default_shell, default_shell,
Some(should_float),
name, name,
None, NewPanePlacement::Floating(None),
should_start_suppressed, should_start_suppressed,
client_id_or_tab_index, client_id_or_tab_index,
); );
@ -1266,21 +1264,81 @@ impl Tab {
&mut self, &mut self,
pid: PaneId, pid: PaneId,
initial_pane_title: Option<String>, initial_pane_title: Option<String>,
should_float: Option<bool>,
invoked_with: Option<Run>, invoked_with: Option<Run>,
floating_pane_coordinates: Option<FloatingPaneCoordinates>, start_suppressed: bool,
should_focus_pane: bool,
new_pane_placement: NewPanePlacement,
client_id: Option<ClientId>,
) -> Result<()> {
match new_pane_placement {
NewPanePlacement::NoPreference => self.new_no_preference_pane(
pid,
initial_pane_title,
invoked_with,
start_suppressed,
should_focus_pane,
client_id,
),
NewPanePlacement::Tiled(None) => self.new_tiled_pane(
pid,
initial_pane_title,
invoked_with,
start_suppressed,
should_focus_pane,
client_id,
),
NewPanePlacement::Tiled(Some(direction)) => {
if let Some(client_id) = client_id {
if direction == Direction::Left || direction == Direction::Right {
self.vertical_split(pid, initial_pane_title, client_id)?;
} else {
self.horizontal_split(pid, initial_pane_title, client_id)?;
}
}
Ok(())
},
NewPanePlacement::Floating(floating_pane_coordinates) => self.new_floating_pane(
pid,
initial_pane_title,
invoked_with,
start_suppressed,
should_focus_pane,
floating_pane_coordinates,
),
NewPanePlacement::InPlace {
pane_id_to_replace,
close_replaced_pane,
} => self.new_in_place_pane(
pid,
initial_pane_title,
invoked_with,
start_suppressed,
should_focus_pane,
pane_id_to_replace,
close_replaced_pane,
client_id,
),
NewPanePlacement::Stacked(pane_id_to_stack_under) => self.new_stacked_pane(
pid,
initial_pane_title,
invoked_with,
start_suppressed,
should_focus_pane,
pane_id_to_stack_under,
client_id,
),
}
}
pub fn new_no_preference_pane(
&mut self,
pid: PaneId,
initial_pane_title: Option<String>,
invoked_with: Option<Run>,
start_suppressed: bool, start_suppressed: bool,
should_focus_pane: bool, should_focus_pane: bool,
client_id: Option<ClientId>, client_id: Option<ClientId>,
) -> Result<()> { ) -> Result<()> {
let err_context = || format!("failed to create new pane with id {pid:?}"); let err_context = || format!("failed to create new pane with id {pid:?}");
if should_focus_pane {
match should_float {
Some(true) => self.show_floating_panes(),
Some(false) => self.hide_floating_panes(),
None => {},
};
}
self.close_down_to_max_terminals() self.close_down_to_max_terminals()
.with_context(err_context)?; .with_context(err_context)?;
let mut new_pane = match pid { let mut new_pane = match pid {
@ -1357,23 +1415,340 @@ impl Tab {
Ok(()) Ok(())
} else if should_focus_pane { } else if should_focus_pane {
if self.floating_panes.panes_are_visible() { if self.floating_panes.panes_are_visible() {
self.add_floating_pane(new_pane, pid, floating_pane_coordinates, true) self.add_floating_pane(new_pane, pid, None, true)
} else { } else {
self.add_tiled_pane(new_pane, pid, client_id) self.add_tiled_pane(new_pane, pid, client_id)
} }
} else { } else {
match should_float {
Some(true) => {
self.add_floating_pane(new_pane, pid, floating_pane_coordinates, false)
},
Some(false) => self.add_tiled_pane(new_pane, pid, client_id),
None => {
if self.floating_panes.panes_are_visible() { if self.floating_panes.panes_are_visible() {
self.add_floating_pane(new_pane, pid, floating_pane_coordinates, false) self.add_floating_pane(new_pane, pid, None, false)
} else { } else {
self.add_tiled_pane(new_pane, pid, client_id) self.add_tiled_pane(new_pane, pid, client_id)
} }
}
}
pub fn new_tiled_pane(
&mut self,
pid: PaneId,
initial_pane_title: Option<String>,
invoked_with: Option<Run>,
start_suppressed: bool,
should_focus_pane: bool,
client_id: Option<ClientId>,
) -> Result<()> {
let err_context = || format!("failed to create new pane with id {pid:?}");
if should_focus_pane {
self.hide_floating_panes();
}
self.close_down_to_max_terminals()
.with_context(err_context)?;
let mut new_pane = match pid {
PaneId::Terminal(term_pid) => {
let next_terminal_position = self.get_next_terminal_position();
Box::new(TerminalPane::new(
term_pid,
PaneGeom::default(), // this will be filled out later
self.style,
next_terminal_position,
String::new(),
self.link_handler.clone(),
self.character_cell_size.clone(),
self.sixel_image_store.clone(),
self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(),
initial_pane_title,
invoked_with,
self.debug,
self.arrow_fonts,
self.styled_underlines,
self.explicitly_disable_kitty_keyboard_protocol,
)) as Box<dyn Pane>
}, },
PaneId::Plugin(plugin_pid) => {
Box::new(PluginPane::new(
plugin_pid,
PaneGeom::default(), // this will be filled out later
self.senders
.to_plugin
.as_ref()
.with_context(err_context)?
.clone(),
initial_pane_title.unwrap_or("".to_owned()),
String::new(),
self.sixel_image_store.clone(),
self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(),
self.link_handler.clone(),
self.character_cell_size.clone(),
self.connected_clients.borrow().iter().copied().collect(),
self.style,
invoked_with,
self.debug,
self.arrow_fonts,
self.styled_underlines,
)) as Box<dyn Pane>
},
};
if start_suppressed {
// this pane needs to start in the background (suppressed), only accessible if a plugin takes it out
// of there in one way or another
// we need to do some bookkeeping for this pane, namely setting its geom and
// content_offset so that things will appear properly in the terminal - we set it to
// the default geom of the first floating pane - this is just in order to give it some
// reasonable size, when it is shown - if needed - it will be given the proper geom as if it were
// resized
let viewport = { self.viewport.borrow().clone() };
let new_pane_geom = half_size_middle_geom(&viewport, 0);
new_pane.set_active_at(Instant::now());
new_pane.set_geom(new_pane_geom);
new_pane.set_content_offset(Offset::frame(1));
resize_pty!(
new_pane,
self.os_api,
self.senders,
self.character_cell_size
)
.with_context(err_context)?;
let is_scrollback_editor = false;
self.suppressed_panes
.insert(pid, (is_scrollback_editor, new_pane));
Ok(())
} else {
self.add_tiled_pane(new_pane, pid, client_id)
}
}
pub fn new_floating_pane(
&mut self,
pid: PaneId,
initial_pane_title: Option<String>,
invoked_with: Option<Run>,
start_suppressed: bool,
should_focus_pane: bool,
floating_pane_coordinates: Option<FloatingPaneCoordinates>,
) -> Result<()> {
let err_context = || format!("failed to create new pane with id {pid:?}");
if should_focus_pane {
self.show_floating_panes();
}
self.close_down_to_max_terminals()
.with_context(err_context)?;
let mut new_pane = match pid {
PaneId::Terminal(term_pid) => {
let next_terminal_position = self.get_next_terminal_position();
Box::new(TerminalPane::new(
term_pid,
PaneGeom::default(), // this will be filled out later
self.style,
next_terminal_position,
String::new(),
self.link_handler.clone(),
self.character_cell_size.clone(),
self.sixel_image_store.clone(),
self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(),
initial_pane_title,
invoked_with,
self.debug,
self.arrow_fonts,
self.styled_underlines,
self.explicitly_disable_kitty_keyboard_protocol,
)) as Box<dyn Pane>
},
PaneId::Plugin(plugin_pid) => {
Box::new(PluginPane::new(
plugin_pid,
PaneGeom::default(), // this will be filled out later
self.senders
.to_plugin
.as_ref()
.with_context(err_context)?
.clone(),
initial_pane_title.unwrap_or("".to_owned()),
String::new(),
self.sixel_image_store.clone(),
self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(),
self.link_handler.clone(),
self.character_cell_size.clone(),
self.connected_clients.borrow().iter().copied().collect(),
self.style,
invoked_with,
self.debug,
self.arrow_fonts,
self.styled_underlines,
)) as Box<dyn Pane>
},
};
if start_suppressed {
// this pane needs to start in the background (suppressed), only accessible if a plugin takes it out
// of there in one way or another
// we need to do some bookkeeping for this pane, namely setting its geom and
// content_offset so that things will appear properly in the terminal - we set it to
// the default geom of the first floating pane - this is just in order to give it some
// reasonable size, when it is shown - if needed - it will be given the proper geom as if it were
// resized
let viewport = { self.viewport.borrow().clone() };
let new_pane_geom = half_size_middle_geom(&viewport, 0);
new_pane.set_active_at(Instant::now());
new_pane.set_geom(new_pane_geom);
new_pane.set_content_offset(Offset::frame(1));
resize_pty!(
new_pane,
self.os_api,
self.senders,
self.character_cell_size
)
.with_context(err_context)?;
let is_scrollback_editor = false;
self.suppressed_panes
.insert(pid, (is_scrollback_editor, new_pane));
Ok(())
} else {
self.add_floating_pane(new_pane, pid, floating_pane_coordinates, should_focus_pane)
}
}
pub fn new_in_place_pane(
&mut self,
pid: PaneId,
initial_pane_title: Option<String>,
invoked_with: Option<Run>,
start_suppressed: bool,
should_focus_pane: bool,
pane_id_to_replace: Option<PaneId>,
close_replaced_pane: bool,
client_id: Option<ClientId>,
) -> Result<()> {
match (pane_id_to_replace, client_id) {
(Some(pane_id_to_replace), _) => {
self.suppress_pane_and_replace_with_pid(
pane_id_to_replace,
pid,
close_replaced_pane,
invoked_with,
)?;
},
(None, Some(client_id)) => match self.get_active_pane_id(client_id) {
Some(active_pane_id) => {
self.suppress_pane_and_replace_with_pid(
active_pane_id,
pid,
close_replaced_pane,
invoked_with,
)?;
},
None => {
log::error!("Cannot find active pane");
},
},
_ => {
log::error!("Must have pane id to replace or client id to start pane in place>");
},
}
if let Some(initial_pane_title) = initial_pane_title {
let _ = self.rename_pane(initial_pane_title.as_bytes().to_vec(), pid);
}
Ok(())
}
pub fn new_stacked_pane(
&mut self,
pid: PaneId,
initial_pane_title: Option<String>,
invoked_with: Option<Run>,
start_suppressed: bool,
should_focus_pane: bool,
pane_id_to_stack_under: Option<PaneId>,
client_id: Option<ClientId>,
) -> Result<()> {
let err_context = || format!("failed to create new pane with id {pid:?}");
if should_focus_pane {
self.hide_floating_panes();
}
self.close_down_to_max_terminals()
.with_context(err_context)?;
let mut new_pane = match pid {
PaneId::Terminal(term_pid) => {
let next_terminal_position = self.get_next_terminal_position();
Box::new(TerminalPane::new(
term_pid,
PaneGeom::default(), // this will be filled out later
self.style,
next_terminal_position,
String::new(),
self.link_handler.clone(),
self.character_cell_size.clone(),
self.sixel_image_store.clone(),
self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(),
initial_pane_title,
invoked_with,
self.debug,
self.arrow_fonts,
self.styled_underlines,
self.explicitly_disable_kitty_keyboard_protocol,
)) as Box<dyn Pane>
},
PaneId::Plugin(plugin_pid) => {
Box::new(PluginPane::new(
plugin_pid,
PaneGeom::default(), // this will be filled out later
self.senders
.to_plugin
.as_ref()
.with_context(err_context)?
.clone(),
initial_pane_title.unwrap_or("".to_owned()),
String::new(),
self.sixel_image_store.clone(),
self.terminal_emulator_colors.clone(),
self.terminal_emulator_color_codes.clone(),
self.link_handler.clone(),
self.character_cell_size.clone(),
self.connected_clients.borrow().iter().copied().collect(),
self.style,
invoked_with,
self.debug,
self.arrow_fonts,
self.styled_underlines,
)) as Box<dyn Pane>
},
};
if start_suppressed {
// this pane needs to start in the background (suppressed), only accessible if a plugin takes it out
// of there in one way or another
// we need to do some bookkeeping for this pane, namely setting its geom and
// content_offset so that things will appear properly in the terminal - we set it to
// the default geom of the first floating pane - this is just in order to give it some
// reasonable size, when it is shown - if needed - it will be given the proper geom as if it were
// resized
let viewport = { self.viewport.borrow().clone() };
let new_pane_geom = half_size_middle_geom(&viewport, 0);
new_pane.set_active_at(Instant::now());
new_pane.set_geom(new_pane_geom);
new_pane.set_content_offset(Offset::frame(1));
resize_pty!(
new_pane,
self.os_api,
self.senders,
self.character_cell_size
)
.with_context(err_context)?;
let is_scrollback_editor = false;
self.suppressed_panes
.insert(pid, (is_scrollback_editor, new_pane));
Ok(())
} else {
if let Some(pane_id_to_stack_under) = pane_id_to_stack_under {
// TODO: also focus pane if should_focus_pane? in cases where we did this from the CLI in an unfocused
// pane...
self.add_stacked_pane_to_pane_id(new_pane, pid, pane_id_to_stack_under)
} else if let Some(client_id) = client_id {
self.add_stacked_pane_to_active_pane(new_pane, pid, client_id)
} else {
log::error!("Must have client id or pane id to stack pane");
return Ok(());
} }
} }
} }
@ -4653,6 +5028,38 @@ impl Tab {
} }
Ok(()) Ok(())
} }
pub fn add_stacked_pane_to_pane_id(
&mut self,
pane: Box<dyn Pane>,
pane_id: PaneId,
root_pane_id: PaneId,
) -> Result<()> {
if self.tiled_panes.fullscreen_is_active() {
self.tiled_panes.unset_fullscreen();
}
self.tiled_panes
.add_pane_to_stack_of_pane_id(pane_id, pane, root_pane_id);
self.set_should_clear_display_before_rendering();
self.tiled_panes.expand_pane_in_stack(pane_id); // so that it will get focused by all
// clients
self.swap_layouts.set_is_tiled_damaged();
Ok(())
}
pub fn add_stacked_pane_to_active_pane(
&mut self,
pane: Box<dyn Pane>,
pane_id: PaneId,
client_id: ClientId,
) -> Result<()> {
if self.tiled_panes.fullscreen_is_active() {
self.tiled_panes.unset_fullscreen();
}
self.tiled_panes
.add_pane_to_stack_of_active_pane(pane_id, pane, client_id);
self.tiled_panes.focus_pane(pane_id, client_id);
self.swap_layouts.set_is_tiled_damaged();
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; let mut should_focus_pane = false;
if let Some(plugin_pane) = self if let Some(plugin_pane) = self

View file

@ -0,0 +1,24 @@
---
source: zellij-server/src/tab/./unit/tab_integration_tests.rs
expression: snapshot
---
00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
01 (C): ┌ Pane #2 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
02 (C): │ │
03 (C): │ │
04 (C): │ │
05 (C): │ │
06 (C): │ │
07 (C): │ │
08 (C): │ │
09 (C): │ │
10 (C): │ │
11 (C): │ │
12 (C): │ │
13 (C): │ │
14 (C): │ │
15 (C): │ │
16 (C): │ │
17 (C): │ │
18 (C): │ │
19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
use super::Tab; use super::Tab;
use crate::pane_groups::PaneGroups; use crate::pane_groups::PaneGroups;
use crate::panes::sixel::SixelImageStore; use crate::panes::sixel::SixelImageStore;
use crate::pty::NewPanePlacement;
use crate::screen::CopyOptions; use crate::screen::CopyOptions;
use crate::{ use crate::{
os_input_output::{AsyncReader, Pid, ServerOsApi}, os_input_output::{AsyncReader, Pid, ServerOsApi},
@ -625,7 +626,15 @@ fn split_largest_pane() {
let mut tab = create_new_tab(size, stacked_resize); let mut tab = create_new_tab(size, stacked_resize);
for i in 2..5 { for i in 2..5 {
let new_pane_id = PaneId::Terminal(i); let new_pane_id = PaneId::Terminal(i);
tab.new_pane(new_pane_id, None, None, None, None, false, true, Some(1)) tab.new_pane(
new_pane_id,
None,
None,
false,
true,
NewPanePlacement::default(),
Some(1),
)
.unwrap(); .unwrap();
} }
assert_eq!(tab.tiled_panes.panes.len(), 4, "The tab has four panes"); assert_eq!(tab.tiled_panes.panes.len(), 4, "The tab has four panes");
@ -838,10 +847,9 @@ pub fn cannot_split_largest_pane_when_there_is_no_room() {
PaneId::Terminal(2), PaneId::Terminal(2),
None, None,
None, None,
None,
None,
false, false,
true, true,
NewPanePlacement::default(),
Some(1), Some(1),
) )
.unwrap(); .unwrap();
@ -888,7 +896,15 @@ pub fn toggle_focused_pane_fullscreen() {
let mut tab = create_new_tab(size, stacked_resize); let mut tab = create_new_tab(size, stacked_resize);
for i in 2..5 { for i in 2..5 {
let new_pane_id = PaneId::Terminal(i); let new_pane_id = PaneId::Terminal(i);
tab.new_pane(new_pane_id, None, None, None, None, false, true, Some(1)) tab.new_pane(
new_pane_id,
None,
None,
false,
true,
NewPanePlacement::default(),
Some(1),
)
.unwrap(); .unwrap();
} }
tab.toggle_active_pane_fullscreen(1); tab.toggle_active_pane_fullscreen(1);
@ -964,7 +980,15 @@ pub fn toggle_focused_pane_fullscreen_with_stacked_resizes() {
let mut tab = create_new_tab(size, stacked_resize); let mut tab = create_new_tab(size, stacked_resize);
for i in 2..5 { for i in 2..5 {
let new_pane_id = PaneId::Terminal(i); let new_pane_id = PaneId::Terminal(i);
tab.new_pane(new_pane_id, None, None, None, None, false, true, Some(1)) tab.new_pane(
new_pane_id,
None,
None,
false,
true,
NewPanePlacement::default(),
Some(1),
)
.unwrap(); .unwrap();
} }
tab.toggle_active_pane_fullscreen(1); tab.toggle_active_pane_fullscreen(1);
@ -1044,10 +1068,9 @@ fn switch_to_next_pane_fullscreen() {
PaneId::Terminal(1), PaneId::Terminal(1),
None, None,
None, None,
None,
None,
false, false,
true, true,
NewPanePlacement::default(),
Some(1), Some(1),
) )
.unwrap(); .unwrap();
@ -1056,10 +1079,9 @@ fn switch_to_next_pane_fullscreen() {
PaneId::Terminal(2), PaneId::Terminal(2),
None, None,
None, None,
None,
None,
false, false,
true, true,
NewPanePlacement::default(),
Some(1), Some(1),
) )
.unwrap(); .unwrap();
@ -1068,10 +1090,9 @@ fn switch_to_next_pane_fullscreen() {
PaneId::Terminal(3), PaneId::Terminal(3),
None, None,
None, None,
None,
None,
false, false,
true, true,
NewPanePlacement::default(),
Some(1), Some(1),
) )
.unwrap(); .unwrap();
@ -1080,10 +1101,9 @@ fn switch_to_next_pane_fullscreen() {
PaneId::Terminal(4), PaneId::Terminal(4),
None, None,
None, None,
None,
None,
false, false,
true, true,
NewPanePlacement::default(),
Some(1), Some(1),
) )
.unwrap(); .unwrap();
@ -1121,10 +1141,9 @@ fn switch_to_prev_pane_fullscreen() {
PaneId::Terminal(1), PaneId::Terminal(1),
None, None,
None, None,
None,
None,
false, false,
true, true,
NewPanePlacement::default(),
Some(1), Some(1),
) )
.unwrap(); .unwrap();
@ -1133,10 +1152,9 @@ fn switch_to_prev_pane_fullscreen() {
PaneId::Terminal(2), PaneId::Terminal(2),
None, None,
None, None,
None,
None,
false, false,
true, true,
NewPanePlacement::default(),
Some(1), Some(1),
) )
.unwrap(); .unwrap();
@ -1145,10 +1163,9 @@ fn switch_to_prev_pane_fullscreen() {
PaneId::Terminal(3), PaneId::Terminal(3),
None, None,
None, None,
None,
None,
false, false,
true, true,
NewPanePlacement::default(),
Some(1), Some(1),
) )
.unwrap(); .unwrap();
@ -1157,10 +1174,9 @@ fn switch_to_prev_pane_fullscreen() {
PaneId::Terminal(4), PaneId::Terminal(4),
None, None,
None, None,
None,
None,
false, false,
true, true,
NewPanePlacement::default(),
Some(1), Some(1),
) )
.unwrap(); .unwrap();
@ -14758,10 +14774,9 @@ fn correctly_resize_frameless_panes_on_pane_close() {
PaneId::Terminal(2), PaneId::Terminal(2),
None, None,
None, None,
None,
None,
false, false,
true, true,
NewPanePlacement::default(),
Some(1), Some(1),
) )
.unwrap(); .unwrap();

View file

@ -34,7 +34,7 @@ use std::sync::{Arc, Mutex};
use crate::{ use crate::{
plugins::PluginInstruction, plugins::PluginInstruction,
pty::{ClientTabIndexOrPaneId, PtyInstruction}, pty::{ClientTabIndexOrPaneId, NewPanePlacement, PtyInstruction},
}; };
use zellij_utils::ipc::PixelDimensions; use zellij_utils::ipc::PixelDimensions;
@ -1235,10 +1235,9 @@ fn switch_to_tab_with_fullscreen() {
PaneId::Terminal(2), PaneId::Terminal(2),
None, None,
None, None,
None,
None,
false, false,
true, true,
NewPanePlacement::default(),
Some(1), Some(1),
) )
.unwrap(); .unwrap();
@ -1359,10 +1358,9 @@ fn attach_after_first_tab_closed() {
PaneId::Terminal(2), PaneId::Terminal(2),
None, None,
None, None,
None,
None,
false, false,
true, true,
NewPanePlacement::default(),
Some(1), Some(1),
) )
.unwrap(); .unwrap();
@ -1385,22 +1383,20 @@ fn open_new_floating_pane_with_custom_coordinates() {
new_tab(&mut screen, 1, 0); new_tab(&mut screen, 1, 0);
let active_tab = screen.get_active_tab_mut(1).unwrap(); let active_tab = screen.get_active_tab_mut(1).unwrap();
let should_float = Some(true);
active_tab active_tab
.new_pane( .new_pane(
PaneId::Terminal(2), PaneId::Terminal(2),
None, None,
should_float,
None, None,
Some(FloatingPaneCoordinates { false,
true,
NewPanePlacement::Floating(Some(FloatingPaneCoordinates {
x: Some(SplitSize::Percent(10)), x: Some(SplitSize::Percent(10)),
y: Some(SplitSize::Fixed(5)), y: Some(SplitSize::Fixed(5)),
width: Some(SplitSize::Percent(1)), width: Some(SplitSize::Percent(1)),
height: Some(SplitSize::Fixed(2)), height: Some(SplitSize::Fixed(2)),
pinned: None, pinned: None,
}), })),
false,
true,
Some(1), Some(1),
) )
.unwrap(); .unwrap();
@ -1421,22 +1417,20 @@ fn open_new_floating_pane_with_custom_coordinates_exceeding_viewport() {
new_tab(&mut screen, 1, 0); new_tab(&mut screen, 1, 0);
let active_tab = screen.get_active_tab_mut(1).unwrap(); let active_tab = screen.get_active_tab_mut(1).unwrap();
let should_float = Some(true);
active_tab active_tab
.new_pane( .new_pane(
PaneId::Terminal(2), PaneId::Terminal(2),
None, None,
should_float,
None, None,
Some(FloatingPaneCoordinates { false,
true,
NewPanePlacement::Floating(Some(FloatingPaneCoordinates {
x: Some(SplitSize::Fixed(122)), x: Some(SplitSize::Fixed(122)),
y: Some(SplitSize::Fixed(21)), y: Some(SplitSize::Fixed(21)),
width: Some(SplitSize::Fixed(10)), width: Some(SplitSize::Fixed(10)),
height: Some(SplitSize::Fixed(10)), height: Some(SplitSize::Fixed(10)),
pinned: None, pinned: None,
}), })),
false,
true,
Some(1), Some(1),
) )
.unwrap(); .unwrap();
@ -1611,17 +1605,15 @@ fn group_panes_following_focus() {
{ {
let active_tab = screen.get_active_tab_mut(client_id).unwrap(); let active_tab = screen.get_active_tab_mut(client_id).unwrap();
let should_float = Some(false);
for i in 2..5 { for i in 2..5 {
active_tab active_tab
.new_pane( .new_pane(
PaneId::Terminal(i), PaneId::Terminal(i),
None, None,
should_float,
None,
None, None,
false, false,
true, true,
NewPanePlacement::Tiled(None),
Some(client_id), Some(client_id),
) )
.unwrap(); .unwrap();
@ -1670,17 +1662,15 @@ fn break_group_with_mouse() {
{ {
let active_tab = screen.get_active_tab_mut(client_id).unwrap(); let active_tab = screen.get_active_tab_mut(client_id).unwrap();
let should_float = Some(false);
for i in 2..5 { for i in 2..5 {
active_tab active_tab
.new_pane( .new_pane(
PaneId::Terminal(i), PaneId::Terminal(i),
None, None,
should_float,
None,
None, None,
false, false,
true, true,
NewPanePlacement::Tiled(None),
Some(client_id), Some(client_id),
) )
.unwrap(); .unwrap();
@ -2596,6 +2586,7 @@ pub fn send_cli_new_pane_action_with_default_parameters() {
width: None, width: None,
height: None, height: None,
pinned: None, pinned: None,
stacked: false,
}; };
send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id); send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
@ -2640,6 +2631,7 @@ pub fn send_cli_new_pane_action_with_split_direction() {
width: None, width: None,
height: None, height: None,
pinned: None, pinned: None,
stacked: false,
}; };
send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id); send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
@ -2684,6 +2676,7 @@ pub fn send_cli_new_pane_action_with_command_and_cwd() {
width: None, width: None,
height: None, height: None,
pinned: None, pinned: None,
stacked: false,
}; };
send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id); send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
@ -2694,7 +2687,7 @@ pub fn send_cli_new_pane_action_with_command_and_cwd() {
.unwrap() .unwrap()
.iter() .iter()
.find(|instruction| match instruction { .find(|instruction| match instruction {
PtyInstruction::SpawnTerminalVertically(..) => true, PtyInstruction::SpawnTerminal(..) => true,
_ => false, _ => false,
}) })
.cloned(); .cloned();
@ -2739,6 +2732,7 @@ pub fn send_cli_new_pane_action_with_floating_pane_and_coordinates() {
width: Some("20%".to_owned()), width: Some("20%".to_owned()),
height: None, height: None,
pinned: None, pinned: None,
stacked: false,
}; };
send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id); send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be

View file

@ -2,4 +2,4 @@
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())" expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
--- ---
[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: None, cwd: Some("."), originating_plugin: None })), Some(false), Some("Editing: /file/to/edit"), None, false, ClientId(10)), Exit] [UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: None, cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled(None), false, ClientId(10)), Exit]

View file

@ -2,4 +2,4 @@
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())" expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
--- ---
[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: Some(100), cwd: Some("."), originating_plugin: None })), Some(false), Some("Editing: /file/to/edit"), None, false, ClientId(10)), Exit] [UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: Some(100), cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled(None), false, ClientId(10)), Exit]

View file

@ -2,4 +2,4 @@
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())" expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
--- ---
[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminalHorizontally(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: None, cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), 10), Exit] [UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(OpenFile(OpenFilePayload { path: "/file/to/edit", line_number: None, cwd: Some("."), originating_plugin: None })), Some("Editing: /file/to/edit"), Tiled(Some(Down)), false, ClientId(10)), Exit]

View file

@ -1,6 +1,5 @@
--- ---
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 2339
expression: "format!(\"{:?}\", new_pane_instruction)" expression: "format!(\"{:?}\", new_pane_instruction)"
--- ---
Some(SpawnTerminalVertically(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder"), hold_on_close: true, hold_on_start: false, originating_plugin: None })), None, 10)) Some(SpawnTerminal(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder"), hold_on_close: true, hold_on_start: false, originating_plugin: None })), None, Tiled(Some(Right)), false, ClientId(10)))

View file

@ -2,4 +2,4 @@
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())" expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
--- ---
[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(None, Some(false), None, None, false, ClientId(10)), Exit] [UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(None, None, Tiled(None), false, ClientId(10)), Exit]

View file

@ -2,4 +2,4 @@
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())" expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
--- ---
[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder"), hold_on_close: true, hold_on_start: false, originating_plugin: None })), Some(true), None, Some(FloatingPaneCoordinates { x: Some(Fixed(10)), y: None, width: Some(Percent(20)), height: None, pinned: None }), false, ClientId(10)), Exit] [UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(Some(RunCommand(RunCommand { command: "htop", args: [], cwd: Some("/some/folder"), hold_on_close: true, hold_on_start: false, originating_plugin: None })), None, Floating(Some(FloatingPaneCoordinates { x: Some(Fixed(10)), y: None, width: Some(Percent(20)), height: None, pinned: None })), false, ClientId(10)), Exit]

View file

@ -2,4 +2,4 @@
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())" expression: "format!(\"{:?}\", *received_pty_instructions.lock().unwrap())"
--- ---
[UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminalVertically(None, None, 10), Exit] [UpdateActivePane(Some(Terminal(0)), 1), UpdateActivePane(Some(Terminal(0)), 1), SpawnTerminal(None, None, Tiled(Some(Right)), false, ClientId(10)), Exit]

View file

@ -30,6 +30,7 @@ keybinds {
bind "n" { NewPane; SwitchToMode "Normal"; } bind "n" { NewPane; SwitchToMode "Normal"; }
bind "d" { NewPane "Down"; SwitchToMode "Normal"; } bind "d" { NewPane "Down"; SwitchToMode "Normal"; }
bind "r" { NewPane "Right"; SwitchToMode "Normal"; } bind "r" { NewPane "Right"; SwitchToMode "Normal"; }
bind "s" { NewPane "stacked"; SwitchToMode "Normal"; }
bind "x" { CloseFocus; SwitchToMode "Normal"; } bind "x" { CloseFocus; SwitchToMode "Normal"; }
bind "f" { ToggleFocusFullscreen; SwitchToMode "Normal"; } bind "f" { ToggleFocusFullscreen; SwitchToMode "Normal"; }
bind "z" { TogglePaneFrames; SwitchToMode "Normal"; } bind "z" { TogglePaneFrames; SwitchToMode "Normal"; }

View file

@ -475,6 +475,7 @@ pub enum ActionName {
MouseEvent = 86, MouseEvent = 86,
TogglePaneInGroup = 87, TogglePaneInGroup = 87,
ToggleGroupMarking = 88, ToggleGroupMarking = 88,
NewStackedPane = 89,
} }
impl ActionName { impl ActionName {
/// String value of the enum field names used in the ProtoBuf definition. /// String value of the enum field names used in the ProtoBuf definition.
@ -569,6 +570,7 @@ impl ActionName {
ActionName::MouseEvent => "MouseEvent", ActionName::MouseEvent => "MouseEvent",
ActionName::TogglePaneInGroup => "TogglePaneInGroup", ActionName::TogglePaneInGroup => "TogglePaneInGroup",
ActionName::ToggleGroupMarking => "ToggleGroupMarking", ActionName::ToggleGroupMarking => "ToggleGroupMarking",
ActionName::NewStackedPane => "NewStackedPane",
} }
} }
/// Creates an enum from field names used in the ProtoBuf definition. /// Creates an enum from field names used in the ProtoBuf definition.
@ -660,6 +662,7 @@ impl ActionName {
"MouseEvent" => Some(Self::MouseEvent), "MouseEvent" => Some(Self::MouseEvent),
"TogglePaneInGroup" => Some(Self::TogglePaneInGroup), "TogglePaneInGroup" => Some(Self::TogglePaneInGroup),
"ToggleGroupMarking" => Some(Self::ToggleGroupMarking), "ToggleGroupMarking" => Some(Self::ToggleGroupMarking),
"NewStackedPane" => Some(Self::NewStackedPane),
_ => None, _ => None,
} }
} }

View file

@ -313,6 +313,15 @@ pub enum Sessions {
/// Whether to pin a floating pane so that it is always on top /// Whether to pin a floating pane so that it is always on top
#[clap(long, requires("floating"))] #[clap(long, requires("floating"))]
pinned: Option<bool>, pinned: Option<bool>,
#[clap(
long,
conflicts_with("floating"),
conflicts_with("direction"),
value_parser,
default_value("false"),
takes_value(false)
)]
stacked: bool,
}, },
/// Load a plugin /// Load a plugin
#[clap(visible_alias = "p")] #[clap(visible_alias = "p")]
@ -602,6 +611,15 @@ pub enum CliAction {
/// Whether to pin a floating pane so that it is always on top /// Whether to pin a floating pane so that it is always on top
#[clap(long, requires("floating"))] #[clap(long, requires("floating"))]
pinned: Option<bool>, pinned: Option<bool>,
#[clap(
long,
conflicts_with("floating"),
conflicts_with("direction"),
value_parser,
default_value("false"),
takes_value(false)
)]
stacked: bool,
}, },
/// Open the specified file in a new zellij pane with your default EDITOR /// Open the specified file in a new zellij pane with your default EDITOR
Edit { Edit {

View file

@ -180,6 +180,8 @@ pub enum Action {
/// Open a new pane in place of the focused one, suppressing it instead /// Open a new pane in place of the focused one, suppressing it instead
NewInPlacePane(Option<RunCommandAction>, Option<String>), // String is an NewInPlacePane(Option<RunCommandAction>, Option<String>), // String is an
// optional pane // optional pane
NewStackedPane(Option<RunCommandAction>, Option<String>), // String is an
// optional pane
// name // name
/// Embed focused pane in tab if floating or float focused pane if embedded /// Embed focused pane in tab if floating or float focused pane if embedded
TogglePaneEmbedOrFloating, TogglePaneEmbedOrFloating,
@ -362,6 +364,7 @@ impl Action {
width, width,
height, height,
pinned, pinned,
stacked,
} => { } => {
let current_dir = get_current_dir(); let current_dir = get_current_dir();
// cwd should only be specified in a plugin alias if it was explicitly given to us, // cwd should only be specified in a plugin alias if it was explicitly given to us,
@ -443,6 +446,8 @@ impl Action {
)]) )])
} else if in_place { } else if in_place {
Ok(vec![Action::NewInPlacePane(Some(run_command_action), name)]) Ok(vec![Action::NewInPlacePane(Some(run_command_action), name)])
} else if stacked {
Ok(vec![Action::NewStackedPane(Some(run_command_action), name)])
} else { } else {
Ok(vec![Action::NewTiledPane( Ok(vec![Action::NewTiledPane(
direction, direction,
@ -459,6 +464,8 @@ impl Action {
)]) )])
} else if in_place { } else if in_place {
Ok(vec![Action::NewInPlacePane(None, name)]) Ok(vec![Action::NewInPlacePane(None, name)])
} else if stacked {
Ok(vec![Action::NewStackedPane(None, name)])
} else { } else {
Ok(vec![Action::NewTiledPane(direction, None, name)]) Ok(vec![Action::NewTiledPane(direction, None, name)])
} }

View file

@ -534,6 +534,8 @@ impl Action {
"NewPane" => { "NewPane" => {
if string.is_empty() { if string.is_empty() {
return Ok(Action::NewPane(None, None, false)); return Ok(Action::NewPane(None, None, false));
} else if string == "stacked" {
return Ok(Action::NewStackedPane(None, None));
} else { } else {
let direction = Direction::from_str(string.as_str()).map_err(|_| { let direction = Direction::from_str(string.as_str()).map_err(|_| {
ConfigError::new_kdl_error( ConfigError::new_kdl_error(
@ -911,6 +913,48 @@ impl Action {
} }
Some(node) Some(node)
}, },
Action::NewStackedPane(run_command_action, name) => match run_command_action {
Some(run_command_action) => {
let mut node = KdlNode::new("Run");
let mut node_children = KdlDocument::new();
node.push(run_command_action.command.display().to_string());
for arg in &run_command_action.args {
node.push(arg.clone());
}
let mut stacked_node = KdlNode::new("stacked");
stacked_node.push(KdlValue::Bool(true));
node_children.nodes_mut().push(stacked_node);
if let Some(cwd) = &run_command_action.cwd {
let mut cwd_node = KdlNode::new("cwd");
cwd_node.push(cwd.display().to_string());
node_children.nodes_mut().push(cwd_node);
}
if run_command_action.hold_on_start {
let mut hos_node = KdlNode::new("hold_on_start");
hos_node.push(KdlValue::Bool(true));
node_children.nodes_mut().push(hos_node);
}
if !run_command_action.hold_on_close {
let mut hoc_node = KdlNode::new("hold_on_close");
hoc_node.push(KdlValue::Bool(false));
node_children.nodes_mut().push(hoc_node);
}
if let Some(name) = name {
let mut name_node = KdlNode::new("name");
name_node.push(name.clone());
node_children.nodes_mut().push(name_node);
}
if !node_children.nodes().is_empty() {
node.set_children(node_children);
}
Some(node)
},
None => {
let mut node = KdlNode::new("NewPane");
node.push("stacked");
Some(node)
},
},
Action::Detach => Some(KdlNode::new("Detach")), Action::Detach => Some(KdlNode::new("Detach")),
Action::LaunchOrFocusPlugin( Action::LaunchOrFocusPlugin(
run_plugin_or_alias, run_plugin_or_alias,
@ -1557,6 +1601,9 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
let in_place = command_metadata let in_place = command_metadata
.and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "in_place")) .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "in_place"))
.unwrap_or(false); .unwrap_or(false);
let stacked = command_metadata
.and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "stacked"))
.unwrap_or(false);
let run_command_action = RunCommandAction { let run_command_action = RunCommandAction {
command: PathBuf::from(command), command: PathBuf::from(command),
args, args,
@ -1588,6 +1635,8 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
)) ))
} else if in_place { } else if in_place {
Ok(Action::NewInPlacePane(Some(run_command_action), name)) Ok(Action::NewInPlacePane(Some(run_command_action), name))
} else if stacked {
Ok(Action::NewStackedPane(Some(run_command_action), name))
} else { } else {
Ok(Action::NewTiledPane( Ok(Action::NewTiledPane(
direction, direction,

View file

@ -24,6 +24,7 @@ keybinds clear-defaults=true {
bind "p" { SwitchFocus; } bind "p" { SwitchFocus; }
bind "Ctrl p" { SwitchToMode "normal"; } bind "Ctrl p" { SwitchToMode "normal"; }
bind "r" { NewPane "right"; SwitchToMode "normal"; } bind "r" { NewPane "right"; SwitchToMode "normal"; }
bind "s" { NewPane "stacked"; SwitchToMode "normal"; }
bind "w" { ToggleFloatingPanes; SwitchToMode "normal"; } bind "w" { ToggleFloatingPanes; SwitchToMode "normal"; }
bind "z" { TogglePaneFrames; SwitchToMode "normal"; } bind "z" { TogglePaneFrames; SwitchToMode "normal"; }
} }

View file

@ -24,6 +24,7 @@ keybinds clear-defaults=true {
bind "p" { SwitchFocus; } bind "p" { SwitchFocus; }
bind "Ctrl p" { SwitchToMode "normal"; } bind "Ctrl p" { SwitchToMode "normal"; }
bind "r" { NewPane "right"; SwitchToMode "normal"; } bind "r" { NewPane "right"; SwitchToMode "normal"; }
bind "s" { NewPane "stacked"; SwitchToMode "normal"; }
bind "w" { ToggleFloatingPanes; SwitchToMode "normal"; } bind "w" { ToggleFloatingPanes; SwitchToMode "normal"; }
bind "z" { TogglePaneFrames; SwitchToMode "normal"; } bind "z" { TogglePaneFrames; SwitchToMode "normal"; }
} }

View file

@ -243,6 +243,7 @@ enum ActionName {
MouseEvent = 86; MouseEvent = 86;
TogglePaneInGroup = 87; TogglePaneInGroup = 87;
ToggleGroupMarking = 88; ToggleGroupMarking = 88;
NewStackedPane = 89;
} }
message Position { message Position {

View file

@ -720,6 +720,10 @@ impl TryFrom<ProtobufAction> for Action {
plugin_id: None, plugin_id: None,
}), }),
}, },
Some(ProtobufActionName::NewStackedPane) => match protobuf_action.optional_payload {
Some(_) => Err("NewStackedPane should not have a payload"),
None => Ok(Action::NewStackedPane(None, None)),
},
_ => Err("Unknown Action"), _ => Err("Unknown Action"),
} }
} }
@ -1254,6 +1258,10 @@ impl TryFrom<Action> for ProtobufAction {
name: ProtobufActionName::ToggleGroupMarking as i32, name: ProtobufActionName::ToggleGroupMarking as i32,
optional_payload: None, optional_payload: None,
}), }),
Action::NewStackedPane(..) => Ok(ProtobufAction {
name: ProtobufActionName::NewStackedPane as i32,
optional_payload: None,
}),
Action::NoOp Action::NoOp
| Action::Confirm | Action::Confirm
| Action::NewInPlacePane(..) | Action::NewInPlacePane(..)

View file

@ -1339,6 +1339,20 @@ Config {
Normal, Normal,
), ),
], ],
KeyWithModifier {
bare_key: Char(
's',
),
key_modifiers: {},
}: [
NewStackedPane(
None,
None,
),
SwitchToMode(
Normal,
),
],
KeyWithModifier { KeyWithModifier {
bare_key: Char( bare_key: Char(
's', 's',

View file

@ -1339,6 +1339,20 @@ Config {
Normal, Normal,
), ),
], ],
KeyWithModifier {
bare_key: Char(
's',
),
key_modifiers: {},
}: [
NewStackedPane(
None,
None,
),
SwitchToMode(
Normal,
),
],
KeyWithModifier { KeyWithModifier {
bare_key: Char( bare_key: Char(
's', 's',

View file

@ -1339,6 +1339,20 @@ Config {
Normal, Normal,
), ),
], ],
KeyWithModifier {
bare_key: Char(
's',
),
key_modifiers: {},
}: [
NewStackedPane(
None,
None,
),
SwitchToMode(
Normal,
),
],
KeyWithModifier { KeyWithModifier {
bare_key: Char( bare_key: Char(
's', 's',

View file

@ -1339,6 +1339,20 @@ Config {
Normal, Normal,
), ),
], ],
KeyWithModifier {
bare_key: Char(
's',
),
key_modifiers: {},
}: [
NewStackedPane(
None,
None,
),
SwitchToMode(
Normal,
),
],
KeyWithModifier { KeyWithModifier {
bare_key: Char( bare_key: Char(
's', 's',