feat(plugins): allow specifying the cwd when switching sessions (#3172)

* feat(plugins): allow specifying the cwd when switching sessions

* style(fmt): rustfmt
This commit is contained in:
Aram Drevekenin 2024-02-29 16:26:25 +01:00 committed by GitHub
parent d5bedd0e83
commit 896b09aa6f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 160 additions and 5 deletions

View file

@ -299,12 +299,20 @@ impl ZellijPlugin for State {
switch_session_with_layout( switch_session_with_layout(
Some("my_other_new_session"), Some("my_other_new_session"),
LayoutInfo::BuiltIn("compact".to_owned()), LayoutInfo::BuiltIn("compact".to_owned()),
None,
); );
}, },
Key::Ctrl('8') => { Key::Ctrl('8') => {
let mut file = std::fs::File::create("/host/hi-from-plugin.txt").unwrap(); let mut file = std::fs::File::create("/host/hi-from-plugin.txt").unwrap();
file.write_all(b"Hi there!").unwrap(); file.write_all(b"Hi there!").unwrap();
}, },
Key::Ctrl('9') => {
switch_session_with_layout(
Some("my_other_new_session_with_cwd"),
LayoutInfo::BuiltIn("compact".to_owned()),
Some(std::path::PathBuf::from("/tmp")),
);
},
_ => {}, _ => {},
}, },
Event::CustomMessage(message, payload) => { Event::CustomMessage(message, payload) => {

View file

@ -104,7 +104,7 @@ impl NewSessionInfo {
if new_session_name != current_session_name.as_ref().map(|s| s.as_str()) { if new_session_name != current_session_name.as_ref().map(|s| s.as_str()) {
match new_session_layout { match new_session_layout {
Some(new_session_layout) => { Some(new_session_layout) => {
switch_session_with_layout(new_session_name, new_session_layout) switch_session_with_layout(new_session_name, new_session_layout, None)
}, },
None => { None => {
switch_session(new_session_name); switch_session(new_session_name);

View file

@ -450,6 +450,9 @@ pub(crate) fn start_client(opts: CliArgs) {
// not want it to mix with the config of this session // not want it to mix with the config of this session
let (new_layout, new_layout_config) = new_session_layout; let (new_layout, new_layout_config) = new_session_layout;
layout = new_layout; layout = new_layout;
if let Some(cwd) = reconnect_to_session.cwd.as_ref() {
layout.add_cwd_to_layout(cwd);
}
let mut new_config = config_without_layout.clone(); let mut new_config = config_without_layout.clone();
let _ = new_config.merge(new_layout_config.clone()); let _ = new_config.merge(new_layout_config.clone());
config = new_config; config = new_config;

View file

@ -6317,6 +6317,87 @@ pub fn switch_session_with_layout_plugin_command() {
assert_snapshot!(format!("{:#?}", switch_session_event)); assert_snapshot!(format!("{:#?}", switch_session_event));
} }
#[test]
#[ignore]
pub fn switch_session_with_layout_and_cwd_plugin_command() {
let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its
// destructor removes the directory
let plugin_host_folder = PathBuf::from(temp_folder.path());
let cache_path = plugin_host_folder.join("permissions_test.kdl");
let (plugin_thread_sender, server_receiver, screen_receiver, teardown) =
create_plugin_thread_with_server_receiver(Some(plugin_host_folder));
let plugin_should_float = Some(false);
let plugin_title = Some("test_plugin".to_owned());
let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
..Default::default()
});
let tab_index = 1;
let client_id = 1;
let size = Size {
cols: 121,
rows: 20,
};
let received_screen_instructions = Arc::new(Mutex::new(vec![]));
let _screen_thread = grant_permissions_and_log_actions_in_thread_naked_variant!(
received_screen_instructions,
ScreenInstruction::Exit,
screen_receiver,
1,
&PermissionType::ChangeApplicationState,
cache_path,
plugin_thread_sender,
client_id
);
let received_server_instruction = Arc::new(Mutex::new(vec![]));
let server_thread = log_actions_in_thread!(
received_server_instruction,
ServerInstruction::SwitchSession,
server_receiver,
1
);
let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
let _ = plugin_thread_sender.send(PluginInstruction::Load(
plugin_should_float,
false,
plugin_title,
run_plugin,
tab_index,
None,
client_id,
size,
None,
false,
));
std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
Event::Key(Key::Ctrl('9')), // this triggers the enent in the fixture plugin
)]));
std::thread::sleep(std::time::Duration::from_millis(500));
teardown();
server_thread.join().unwrap(); // this might take a while if the cache is cold
let switch_session_event = received_server_instruction
.lock()
.unwrap()
.iter()
.rev()
.find_map(|i| {
if let ServerInstruction::SwitchSession(..) = i {
Some(i.clone())
} else {
None
}
})
.clone();
assert_snapshot!(format!("{:#?}", switch_session_event));
}
#[test] #[test]
#[ignore] #[ignore]
pub fn disconnect_other_clients_plugins_command() { pub fn disconnect_other_clients_plugins_command() {

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/plugins/./unit/plugin_tests.rs source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 6051 assertion_line: 6236
expression: "format!(\"{:#?}\", switch_session_event)" expression: "format!(\"{:#?}\", switch_session_event)"
--- ---
Some( Some(
@ -12,6 +12,7 @@ Some(
tab_position: None, tab_position: None,
pane_id: None, pane_id: None,
layout: None, layout: None,
cwd: None,
}, },
1, 1,
), ),

View file

@ -0,0 +1,25 @@
---
source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 6398
expression: "format!(\"{:#?}\", switch_session_event)"
---
Some(
SwitchSession(
ConnectToSession {
name: Some(
"my_other_new_session_with_cwd",
),
tab_position: None,
pane_id: None,
layout: Some(
BuiltIn(
"compact",
),
),
cwd: Some(
"/tmp",
),
},
1,
),
)

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/plugins/./unit/plugin_tests.rs source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 6131 assertion_line: 6317
expression: "format!(\"{:#?}\", switch_session_event)" expression: "format!(\"{:#?}\", switch_session_event)"
--- ---
Some( Some(
@ -16,6 +16,7 @@ Some(
"compact", "compact",
), ),
), ),
cwd: None,
}, },
1, 1,
), ),

View file

@ -229,6 +229,7 @@ fn host_run_plugin_command(env: FunctionEnvMut<ForeignFunctionEnv>) {
connect_to_session.tab_position, connect_to_session.tab_position,
connect_to_session.pane_id, connect_to_session.pane_id,
connect_to_session.layout, connect_to_session.layout,
connect_to_session.cwd,
)?, )?,
PluginCommand::DeleteDeadSession(session_name) => { PluginCommand::DeleteDeadSession(session_name) => {
delete_dead_session(session_name)? delete_dead_session(session_name)?
@ -926,6 +927,7 @@ fn switch_session(
tab_position: Option<usize>, tab_position: Option<usize>,
pane_id: Option<(u32, bool)>, pane_id: Option<(u32, bool)>,
layout: Option<LayoutInfo>, layout: Option<LayoutInfo>,
cwd: Option<PathBuf>,
) -> Result<()> { ) -> Result<()> {
// pane_id is (id, is_plugin) // pane_id is (id, is_plugin)
let err_context = || format!("Failed to switch session"); let err_context = || format!("Failed to switch session");
@ -936,6 +938,7 @@ fn switch_session(
tab_position, tab_position,
pane_id, pane_id,
layout, layout,
cwd,
}; };
env.plugin_env env.plugin_env
.senders .senders

View file

@ -660,10 +660,11 @@ pub fn switch_session(name: Option<&str>) {
} }
/// Switch to a session with the given name, create one if no name is given /// Switch to a session with the given name, create one if no name is given
pub fn switch_session_with_layout(name: Option<&str>, layout: LayoutInfo) { pub fn switch_session_with_layout(name: Option<&str>, layout: LayoutInfo, cwd: Option<PathBuf>) {
let plugin_command = PluginCommand::SwitchSession(ConnectToSession { let plugin_command = PluginCommand::SwitchSession(ConnectToSession {
name: name.map(|n| n.to_string()), name: name.map(|n| n.to_string()),
layout: Some(layout), layout: Some(layout),
cwd,
..Default::default() ..Default::default()
}); });
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();

View file

@ -183,6 +183,8 @@ pub struct SwitchSessionPayload {
pub pane_id_is_plugin: ::core::option::Option<bool>, pub pane_id_is_plugin: ::core::option::Option<bool>,
#[prost(message, optional, tag = "5")] #[prost(message, optional, tag = "5")]
pub layout: ::core::option::Option<super::event::LayoutInfo>, pub layout: ::core::option::Option<super::event::LayoutInfo>,
#[prost(string, optional, tag = "6")]
pub cwd: ::core::option::Option<::prost::alloc::string::String>,
} }
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]

View file

@ -1095,6 +1095,7 @@ pub struct ConnectToSession {
pub tab_position: Option<usize>, pub tab_position: Option<usize>,
pub pane_id: Option<(u32, bool)>, // (id, is_plugin) pub pane_id: Option<(u32, bool)>, // (id, is_plugin)
pub layout: Option<LayoutInfo>, pub layout: Option<LayoutInfo>,
pub cwd: Option<PathBuf>,
} }
impl ConnectToSession { impl ConnectToSession {

View file

@ -211,6 +211,16 @@ impl RunPluginOrAlias {
} }
self self
} }
pub fn add_initial_cwd(&mut self, initial_cwd: &PathBuf) {
match self {
RunPluginOrAlias::RunPlugin(ref mut run_plugin) => {
run_plugin.initial_cwd = Some(initial_cwd.clone());
},
RunPluginOrAlias::Alias(ref mut alias) => {
alias.initial_cwd = Some(initial_cwd.clone());
},
}
}
} }
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
@ -305,7 +315,9 @@ impl Run {
Run::Cwd(path) => { Run::Cwd(path) => {
*path = cwd.join(&path); *path = cwd.join(&path);
}, },
_ => {}, // plugins aren't yet supported Run::Plugin(run_plugin_or_alias) => {
run_plugin_or_alias.add_initial_cwd(&cwd);
},
} }
} }
pub fn add_args(&mut self, args: Option<Vec<String>>) { pub fn add_args(&mut self, args: Option<Vec<String>>) {
@ -1365,6 +1377,20 @@ impl Layout {
} }
} }
} }
pub fn add_cwd_to_layout(&mut self, cwd: &PathBuf) {
for (_, tiled_pane_layout, floating_panes) in self.tabs.iter_mut() {
tiled_pane_layout.add_cwd_to_layout(&cwd);
for floating_pane in floating_panes {
floating_pane.add_cwd_to_layout(&cwd);
}
}
if let Some((tiled_pane_layout, floating_panes)) = self.template.as_mut() {
tiled_pane_layout.add_cwd_to_layout(&cwd);
for floating_pane in floating_panes {
floating_pane.add_cwd_to_layout(&cwd);
}
}
}
} }
fn split_space( fn split_space(

View file

@ -194,6 +194,7 @@ message SwitchSessionPayload {
optional uint32 pane_id = 3; optional uint32 pane_id = 3;
optional bool pane_id_is_plugin = 4; optional bool pane_id_is_plugin = 4;
optional event.LayoutInfo layout = 5; optional event.LayoutInfo layout = 5;
optional string cwd = 6;
} }
message RequestPluginPermissionPayload { message RequestPluginPermissionPayload {

View file

@ -689,6 +689,7 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
tab_position: payload.tab_position.map(|p| p as usize), tab_position: payload.tab_position.map(|p| p as usize),
pane_id, pane_id,
layout: payload.layout.and_then(|l| l.try_into().ok()), layout: payload.layout.and_then(|l| l.try_into().ok()),
cwd: payload.cwd.map(|c| PathBuf::from(c)),
})) }))
}, },
_ => Err("Mismatched payload for SwitchSession"), _ => Err("Mismatched payload for SwitchSession"),
@ -1222,6 +1223,7 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
pane_id: switch_to_session.pane_id.map(|p| p.0), pane_id: switch_to_session.pane_id.map(|p| p.0),
pane_id_is_plugin: switch_to_session.pane_id.map(|p| p.1), pane_id_is_plugin: switch_to_session.pane_id.map(|p| p.1),
layout: switch_to_session.layout.and_then(|l| l.try_into().ok()), layout: switch_to_session.layout.and_then(|l| l.try_into().ok()),
cwd: switch_to_session.cwd.map(|c| c.display().to_string()),
})), })),
}), }),
PluginCommand::OpenTerminalInPlace(cwd) => Ok(ProtobufPluginCommand { PluginCommand::OpenTerminalInPlace(cwd) => Ok(ProtobufPluginCommand {