aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2023-06-17 14:41:49 +0200
committerGitHub <noreply@github.com>2023-06-17 14:41:49 +0200
commit63e3a1eae2e727a808084d9fe9ff6eca7816ef7e (patch)
tree445840cba9b3193eca8d5fc73e9cffa610f510ca
parentdocs(changelog): debounced watcher (diff)
downloadsl0ppij-63e3a1eae2e727a808084d9fe9ff6eca7816ef7e.tar.gz
sl0ppij-63e3a1eae2e727a808084d9fe9ff6eca7816ef7e.tar.xz
sl0ppij-63e3a1eae2e727a808084d9fe9ff6eca7816ef7e.zip
feat(plugins): more plugin api methods (#2550)
* feat(plugins): close, focus, rename pane, rename tab and show_self api methods * style(fmt): rustfmt
-rw-r--r--default-plugins/fixture-plugin-for-tests/src/main.rs27
-rw-r--r--zellij-server/src/panes/plugin_pane.rs4
-rw-r--r--zellij-server/src/panes/terminal_pane.rs4
-rw-r--r--zellij-server/src/plugins/unit/plugin_tests.rs480
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__close_plugin_pane_plugin_command.snap13
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__close_terminal_pane_plugin_command.snap13
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__focus_plugin_pane_plugin_command.snap14
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__focus_terminal_pane_plugin_command.snap14
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__rename_plugin_pane_plugin_command.snap34
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__rename_tab_plugin_command.snap24
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__rename_terminal_pane_plugin_command.snap36
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__show_self_plugin_command.snap14
-rw-r--r--zellij-server/src/plugins/zellij_exports.rs97
-rw-r--r--zellij-server/src/route.rs61
-rw-r--r--zellij-server/src/screen.rs65
-rw-r--r--zellij-server/src/tab/mod.rs19
-rw-r--r--zellij-tile/src/shim.rs66
-rw-r--r--zellij-utils/src/errors.rs3
-rw-r--r--zellij-utils/src/input/actions.rs7
19 files changed, 994 insertions, 1 deletions
diff --git a/default-plugins/fixture-plugin-for-tests/src/main.rs b/default-plugins/fixture-plugin-for-tests/src/main.rs
index 2107137d..9ce45f8a 100644
--- a/default-plugins/fixture-plugin-for-tests/src/main.rs
+++ b/default-plugins/fixture-plugin-for-tests/src/main.rs
@@ -183,6 +183,33 @@ impl ZellijPlugin for State {
Key::Ctrl('p') => {
hide_self();
},
+ Key::Ctrl('q') => {
+ let should_float_if_hidden = false;
+ show_self(should_float_if_hidden);
+ },
+ Key::Ctrl('r') => {
+ close_terminal_pane(1);
+ },
+ Key::Ctrl('s') => {
+ close_plugin_pane(1);
+ },
+ Key::Ctrl('t') => {
+ let should_float_if_hidden = false;
+ focus_terminal_pane(1, should_float_if_hidden);
+ },
+ Key::Ctrl('u') => {
+ let should_float_if_hidden = false;
+ focus_plugin_pane(1, should_float_if_hidden);
+ },
+ Key::Ctrl('v') => {
+ rename_terminal_pane(1, "new terminal_pane_name");
+ },
+ Key::Ctrl('w') => {
+ rename_plugin_pane(1, "new plugin_pane_name");
+ },
+ Key::Ctrl('x') => {
+ rename_tab(1, "new tab name");
+ },
_ => {},
},
Event::CustomMessage(message, payload) => {
diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs
index 557769f0..46552960 100644
--- a/zellij-server/src/panes/plugin_pane.rs
+++ b/zellij-server/src/panes/plugin_pane.rs
@@ -571,6 +571,10 @@ impl Pane for PluginPane {
self.pane_name.to_owned()
}
}
+ fn rename(&mut self, buf: Vec<u8>) {
+ self.pane_name = String::from_utf8_lossy(&buf).to_string();
+ self.set_should_render(true);
+ }
}
impl PluginPane {
diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs
index df74afd6..a01d815b 100644
--- a/zellij-server/src/panes/terminal_pane.rs
+++ b/zellij-server/src/panes/terminal_pane.rs
@@ -745,6 +745,10 @@ impl Pane for TerminalPane {
None => false,
}
}
+ fn rename(&mut self, buf: Vec<u8>) {
+ self.pane_name = String::from_utf8_lossy(&buf).to_string();
+ self.set_should_render(true);
+ }
}
impl TerminalPane {
diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs
index 6084c9fe..3ec8485b 100644
--- a/zellij-server/src/plugins/unit/plugin_tests.rs
+++ b/zellij-server/src/plugins/unit/plugin_tests.rs
@@ -3608,3 +3608,483 @@ pub fn hide_self_plugin_command() {
.clone();
assert_snapshot!(format!("{:#?}", new_tab_event));
}
+
+#[test]
+#[ignore]
+pub fn show_self_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 (plugin_thread_sender, screen_receiver, mut teardown) =
+ create_plugin_thread(Some(plugin_host_folder));
+ let plugin_should_float = Some(false);
+ let plugin_title = Some("test_plugin".to_owned());
+ let run_plugin = RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
+ };
+ 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 = log_actions_in_thread!(
+ received_screen_instructions,
+ ScreenInstruction::FocusPaneWithId,
+ screen_receiver,
+ 1
+ );
+
+ let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
+ let _ = plugin_thread_sender.send(PluginInstruction::Load(
+ plugin_should_float,
+ plugin_title,
+ run_plugin,
+ tab_index,
+ client_id,
+ size,
+ ));
+ let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
+ None,
+ Some(client_id),
+ Event::Key(Key::Ctrl('q')), // this triggers the enent in the fixture plugin
+ )]));
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ screen_thread.join().unwrap(); // this might take a while if the cache is cold
+ teardown();
+ let new_tab_event = received_screen_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find_map(|i| {
+ if let ScreenInstruction::FocusPaneWithId(..) = i {
+ Some(i.clone())
+ } else {
+ None
+ }
+ })
+ .clone();
+ assert_snapshot!(format!("{:#?}", new_tab_event));
+}
+
+#[test]
+#[ignore]
+pub fn close_terminal_pane_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 (plugin_thread_sender, screen_receiver, mut teardown) =
+ create_plugin_thread(Some(plugin_host_folder));
+ let plugin_should_float = Some(false);
+ let plugin_title = Some("test_plugin".to_owned());
+ let run_plugin = RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
+ };
+ 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 = log_actions_in_thread!(
+ received_screen_instructions,
+ ScreenInstruction::ClosePane,
+ screen_receiver,
+ 1
+ );
+
+ let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
+ let _ = plugin_thread_sender.send(PluginInstruction::Load(
+ plugin_should_float,
+ plugin_title,
+ run_plugin,
+ tab_index,
+ client_id,
+ size,
+ ));
+ let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
+ None,
+ Some(client_id),
+ Event::Key(Key::Ctrl('r')), // this triggers the enent in the fixture plugin
+ )]));
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ screen_thread.join().unwrap(); // this might take a while if the cache is cold
+ teardown();
+ let new_tab_event = received_screen_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find_map(|i| {
+ if let ScreenInstruction::ClosePane(..) = i {
+ Some(i.clone())
+ } else {
+ None
+ }
+ })
+ .clone();
+ assert_snapshot!(format!("{:#?}", new_tab_event));
+}
+
+#[test]
+#[ignore]
+pub fn close_plugin_pane_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 (plugin_thread_sender, screen_receiver, mut teardown) =
+ create_plugin_thread(Some(plugin_host_folder));
+ let plugin_should_float = Some(false);
+ let plugin_title = Some("test_plugin".to_owned());
+ let run_plugin = RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
+ };
+ 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 = log_actions_in_thread!(
+ received_screen_instructions,
+ ScreenInstruction::ClosePane,
+ screen_receiver,
+ 1
+ );
+
+ let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
+ let _ = plugin_thread_sender.send(PluginInstruction::Load(
+ plugin_should_float,
+ plugin_title,
+ run_plugin,
+ tab_index,
+ client_id,
+ size,
+ ));
+ let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
+ None,
+ Some(client_id),
+ Event::Key(Key::Ctrl('s')), // this triggers the enent in the fixture plugin
+ )]));
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ screen_thread.join().unwrap(); // this might take a while if the cache is cold
+ teardown();
+ let new_tab_event = received_screen_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find_map(|i| {
+ if let ScreenInstruction::ClosePane(..) = i {
+ Some(i.clone())
+ } else {
+ None
+ }
+ })
+ .clone();
+ assert_snapshot!(format!("{:#?}", new_tab_event));
+}
+
+#[test]
+#[ignore]
+pub fn focus_terminal_pane_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 (plugin_thread_sender, screen_receiver, mut teardown) =
+ create_plugin_thread(Some(plugin_host_folder));
+ let plugin_should_float = Some(false);
+ let plugin_title = Some("test_plugin".to_owned());
+ let run_plugin = RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
+ };
+ 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 = log_actions_in_thread!(
+ received_screen_instructions,
+ ScreenInstruction::FocusPaneWithId,
+ screen_receiver,
+ 1
+ );
+
+ let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
+ let _ = plugin_thread_sender.send(PluginInstruction::Load(
+ plugin_should_float,
+ plugin_title,
+ run_plugin,
+ tab_index,
+ client_id,
+ size,
+ ));
+ let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
+ None,
+ Some(client_id),
+ Event::Key(Key::Ctrl('t')), // this triggers the enent in the fixture plugin
+ )]));
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ screen_thread.join().unwrap(); // this might take a while if the cache is cold
+ teardown();
+ let new_tab_event = received_screen_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find_map(|i| {
+ if let ScreenInstruction::FocusPaneWithId(..) = i {
+ Some(i.clone())
+ } else {
+ None
+ }
+ })
+ .clone();
+ assert_snapshot!(format!("{:#?}", new_tab_event));
+}
+
+#[test]
+#[ignore]
+pub fn focus_plugin_pane_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 (plugin_thread_sender, screen_receiver, mut teardown) =
+ create_plugin_thread(Some(plugin_host_folder));
+ let plugin_should_float = Some(false);
+ let plugin_title = Some("test_plugin".to_owned());
+ let run_plugin = RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
+ };
+ 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 = log_actions_in_thread!(
+ received_screen_instructions,
+ ScreenInstruction::FocusPaneWithId,
+ screen_receiver,
+ 1
+ );
+
+ let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
+ let _ = plugin_thread_sender.send(PluginInstruction::Load(
+ plugin_should_float,
+ plugin_title,
+ run_plugin,
+ tab_index,
+ client_id,
+ size,
+ ));
+ let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
+ None,
+ Some(client_id),
+ Event::Key(Key::Ctrl('u')), // this triggers the enent in the fixture plugin
+ )]));
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ screen_thread.join().unwrap(); // this might take a while if the cache is cold
+ teardown();
+ let new_tab_event = received_screen_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find_map(|i| {
+ if let ScreenInstruction::FocusPaneWithId(..) = i {
+ Some(i.clone())
+ } else {
+ None
+ }
+ })
+ .clone();
+ assert_snapshot!(format!("{:#?}", new_tab_event));
+}
+
+#[test]
+#[ignore]
+pub fn rename_terminal_pane_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 (plugin_thread_sender, screen_receiver, mut teardown) =
+ create_plugin_thread(Some(plugin_host_folder));
+ let plugin_should_float = Some(false);
+ let plugin_title = Some("test_plugin".to_owned());
+ let run_plugin = RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
+ };
+ 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 = log_actions_in_thread!(
+ received_screen_instructions,
+ ScreenInstruction::RenamePane,
+ screen_receiver,
+ 1
+ );
+
+ let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
+ let _ = plugin_thread_sender.send(PluginInstruction::Load(
+ plugin_should_float,
+ plugin_title,
+ run_plugin,
+ tab_index,
+ client_id,
+ size,
+ ));
+ let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
+ None,
+ Some(client_id),
+ Event::Key(Key::Ctrl('v')), // this triggers the enent in the fixture plugin
+ )]));
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ screen_thread.join().unwrap(); // this might take a while if the cache is cold
+ teardown();
+ let new_tab_event = received_screen_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find_map(|i| {
+ if let ScreenInstruction::RenamePane(..) = i {
+ Some(i.clone())
+ } else {
+ None
+ }
+ })
+ .clone();
+ assert_snapshot!(format!("{:#?}", new_tab_event));
+}
+
+#[test]
+#[ignore]
+pub fn rename_plugin_pane_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 (plugin_thread_sender, screen_receiver, mut teardown) =
+ create_plugin_thread(Some(plugin_host_folder));
+ let plugin_should_float = Some(false);
+ let plugin_title = Some("test_plugin".to_owned());
+ let run_plugin = RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
+ };
+ 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 = log_actions_in_thread!(
+ received_screen_instructions,
+ ScreenInstruction::RenamePane,
+ screen_receiver,
+ 1
+ );
+
+ let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
+ let _ = plugin_thread_sender.send(PluginInstruction::Load(
+ plugin_should_float,
+ plugin_title,
+ run_plugin,
+ tab_index,
+ client_id,
+ size,
+ ));
+ let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
+ None,
+ Some(client_id),
+ Event::Key(Key::Ctrl('w')), // this triggers the enent in the fixture plugin
+ )]));
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ screen_thread.join().unwrap(); // this might take a while if the cache is cold
+ teardown();
+ let new_tab_event = received_screen_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find_map(|i| {
+ if let ScreenInstruction::RenamePane(..) = i {
+ Some(i.clone())
+ } else {
+ None
+ }
+ })
+ .clone();
+ assert_snapshot!(format!("{:#?}", new_tab_event));
+}
+
+#[test]
+#[ignore]
+pub fn rename_tab_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 (plugin_thread_sender, screen_receiver, mut teardown) =
+ create_plugin_thread(Some(plugin_host_folder));
+ let plugin_should_float = Some(false);
+ let plugin_title = Some("test_plugin".to_owned());
+ let run_plugin = RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
+ };
+ 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 = log_actions_in_thread!(
+ received_screen_instructions,
+ ScreenInstruction::RenameTab,
+ screen_receiver,
+ 1
+ );
+
+ let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
+ let _ = plugin_thread_sender.send(PluginInstruction::Load(
+ plugin_should_float,
+ plugin_title,
+ run_plugin,
+ tab_index,
+ client_id,
+ size,
+ ));
+ let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
+ None,
+ Some(client_id),
+ Event::Key(Key::Ctrl('x')), // this triggers the enent in the fixture plugin
+ )]));
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ screen_thread.join().unwrap(); // this might take a while if the cache is cold
+ teardown();
+ let new_tab_event = received_screen_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find_map(|i| {
+ if let ScreenInstruction::RenameTab(..) = i {
+ Some(i.clone())
+ } else {
+ None
+ }
+ })
+ .clone();
+ assert_snapshot!(format!("{:#?}", new_tab_event));
+}
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__close_plugin_pane_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__close_plugin_pane_plugin_command.snap
new file mode 100644
index 00000000..269986f1
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__close_plugin_pane_plugin_command.snap
@@ -0,0 +1,13 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+assertion_line: 3789
+expression: "format!(\"{:#?}\", new_tab_event)"
+---
+Some(
+ ClosePane(
+ Plugin(
+ 1,
+ ),
+ None,
+ ),
+)
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__close_terminal_pane_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__close_terminal_pane_plugin_command.snap
new file mode 100644
index 00000000..778d07b9
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__close_terminal_pane_plugin_command.snap
@@ -0,0 +1,13 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+assertion_line: 3729
+expression: "format!(\"{:#?}\", new_tab_event)"
+---
+Some(
+ ClosePane(
+ Terminal(
+ 1,
+ ),
+ None,
+ ),
+)
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__focus_plugin_pane_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__focus_plugin_pane_plugin_command.snap
new file mode 100644
index 00000000..d830391e
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__focus_plugin_pane_plugin_command.snap
@@ -0,0 +1,14 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+assertion_line: 3909
+expression: "format!(\"{:#?}\", new_tab_event)"
+---
+Some(
+ FocusPaneWithId(
+ Plugin(
+ 1,
+ ),
+ false,
+ 1,
+ ),
+)
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__focus_terminal_pane_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__focus_terminal_pane_plugin_command.snap
new file mode 100644
index 00000000..38685b01
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__focus_terminal_pane_plugin_command.snap
@@ -0,0 +1,14 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+assertion_line: 3849
+expression: "format!(\"{:#?}\", new_tab_event)"
+---
+Some(
+ FocusPaneWithId(
+ Terminal(
+ 1,
+ ),
+ false,
+ 1,
+ ),
+)
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__rename_plugin_pane_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__rename_plugin_pane_plugin_command.snap
new file mode 100644
index 00000000..cda7d3c4
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__rename_plugin_pane_plugin_command.snap
@@ -0,0 +1,34 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+assertion_line: 4029
+expression: "format!(\"{:#?}\", new_tab_event)"
+---
+Some(
+ RenamePane(
+ Plugin(
+ 1,
+ ),
+ [
+ 110,
+ 101,
+ 119,
+ 32,
+ 112,
+ 108,
+ 117,
+ 103,
+ 105,
+ 110,
+ 95,
+ 112,
+ 97,
+ 110,
+ 101,
+ 95,
+ 110,
+ 97,
+ 109,
+ 101,
+ ],
+ ),
+)
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__rename_tab_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__rename_tab_plugin_command.snap
new file mode 100644
index 00000000..21fe9260
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__rename_tab_plugin_command.snap
@@ -0,0 +1,24 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+assertion_line: 4089
+expression: "format!(\"{:#?}\", new_tab_event)"
+---
+Some(
+ RenameTab(
+ 1,
+ [
+ 110,
+ 101,
+ 119,
+ 32,
+ 116,
+ 97,
+ 98,
+ 32,
+ 110,
+ 97,
+ 109,
+ 101,
+ ],
+ ),
+)
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__rename_terminal_pane_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__rename_terminal_pane_plugin_command.snap
new file mode 100644
index 00000000..64fdf4a9
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__rename_terminal_pane_plugin_command.snap
@@ -0,0 +1,36 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+assertion_line: 3969
+expression: "format!(\"{:#?}\", new_tab_event)"
+---
+Some(
+ RenamePane(
+ Terminal(
+ 1,
+ ),
+ [
+ 110,
+ 101,
+ 119,
+ 32,
+ 116,
+ 101,
+ 114,
+ 109,
+ 105,
+ 110,
+ 97,
+ 108,
+ 95,
+ 112,
+ 97,
+ 110,
+ 101,
+ 95,
+ 110,
+ 97,
+ 109,
+ 101,
+ ],
+ ),
+)
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__show_self_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__show_self_plugin_command.snap
new file mode 100644
index 00000000..b42e5db1
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__show_self_plugin_command.snap
@@ -0,0 +1,14 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+assertion_line: 3673
+expression: "format!(\"{:#?}\", new_tab_event)"
+---
+Some(
+ FocusPaneWithId(
+ Plugin(
+ 0,
+ ),
+ false,
+ 1,
+ ),
+)
diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs
index 08d54c84..403d8ced 100644
--- a/zellij-server/src/plugins/zellij_exports.rs
+++ b/zellij-server/src/plugins/zellij_exports.rs
@@ -85,6 +85,7 @@ pub fn zellij_exports(
host_post_message_to,
host_post_message_to_plugin,
host_hide_self,
+ host_show_self,
host_switch_to_mode,
host_new_tabs_with_layout,
host_new_tab,
@@ -125,6 +126,13 @@ pub fn zellij_exports(
host_focus_or_create_tab,
host_go_to_tab,
host_start_or_reload_plugin,
+ host_close_terminal_pane,
+ host_close_plugin_pane,
+ host_focus_terminal_pane,
+ host_focus_plugin_pane,
+ host_rename_terminal_pane,
+ host_rename_plugin_pane,
+ host_rename_tab,
}
}
@@ -541,6 +549,13 @@ fn host_hide_self(env: &ForeignFunctionEnv) {
.fatal();
}
+fn host_show_self(env: &ForeignFunctionEnv, should_float_if_hidden: i32) {
+ let should_float_if_hidden = should_float_if_hidden != 0;
+ let action = Action::FocusPluginPaneWithId(env.plugin_env.plugin_id, should_float_if_hidden);
+ let error_msg = || format!("Failed to show self for plugin");
+ apply_action!(action, error_msg, env);
+}
+
fn host_switch_to_mode(env: &ForeignFunctionEnv) {
wasi_read_object::<InputMode>(&env.plugin_env.wasi_env)
.and_then(|input_mode| {
@@ -966,6 +981,88 @@ fn host_start_or_reload_plugin(env: &ForeignFunctionEnv) {
.fatal();
}
+fn host_close_terminal_pane(env: &ForeignFunctionEnv, terminal_pane_id: i32) {
+ let error_msg = || {
+ format!(
+ "failed to change tab focus in plugin {}",
+ env.plugin_env.name()
+ )
+ };
+ let action = Action::CloseTerminalPane(terminal_pane_id as u32);
+ apply_action!(action, error_msg, env);
+}
+
+fn host_close_plugin_pane(env: &ForeignFunctionEnv, plugin_pane_id: i32) {
+ let error_msg = || {
+ format!(
+ "failed to change tab focus in plugin {}",
+ env.plugin_env.name()
+ )
+ };
+ let action = Action::ClosePluginPane(plugin_pane_id as u32);
+ apply_action!(action, error_msg, env);
+}
+
+fn host_focus_terminal_pane(
+ env: &ForeignFunctionEnv,
+ terminal_pane_id: i32,
+ should_float_if_hidden: i32,
+) {
+ let should_float_if_hidden = should_float_if_hidden != 0;
+ let action = Action::FocusTerminalPaneWithId(terminal_pane_id as u32, should_float_if_hidden);
+ let error_msg = || format!("Failed to focus terminal pane");
+ apply_action!(action, error_msg, env);
+}
+
+fn host_focus_plugin_pane(
+ env: &ForeignFunctionEnv,
+ plugin_pane_id: i32,
+ should_float_if_hidden: i32,
+) {
+ let should_float_if_hidden = should_float_if_hidden != 0;
+ let action = Action::FocusPluginPaneWithId(plugin_pane_id as u32, should_float_if_hidden);
+ let error_msg = || format!("Failed to focus plugin pane");
+ apply_action!(action, error_msg, env);
+}
+
+fn host_rename_terminal_pane(env: &ForeignFunctionEnv) {
+ let error_msg = || format!("Failed to rename terminal pane");
+ wasi_read_object::<(u32, String)>(&env.plugin_env.wasi_env)
+ .and_then(|(terminal_pane_id, new_name)| {
+ let rename_pane_action =
+ Action::RenameTerminalPane(terminal_pane_id, new_name.as_bytes().to_vec());
+ apply_action!(rename_pane_action, error_msg, env);
+ Ok(())
+ })
+ .with_context(error_msg)
+ .fatal();
+}
+
+fn host_rename_plugin_pane(env: &ForeignFunctionEnv) {
+ let error_msg = || format!("Failed to rename plugin pane");
+ wasi_read_object::<(u32, String)>(&env.plugin_env.wasi_env)
+ .and_then(|(plugin_pane_id, new_name)| {
+ let rename_pane_action =
+ Action::RenamePluginPane(plugin_pane_id, new_name.as_bytes().to_vec());
+ apply_action!(rename_pane_action, error_msg, env);
+ Ok(())
+ })
+ .with_context(error_msg)
+ .fatal();
+}
+
+fn host_rename_tab(env: &ForeignFunctionEnv) {
+ let error_msg = || format!("Failed to rename tab");
+ wasi_read_object::<(u32, String)>(&env.plugin_env.wasi_env)
+ .and_then(|(tab_index, new_name)| {
+ let rename_tab_action = Action::RenameTab(tab_index, new_name.as_bytes().to_vec());
+ apply_action!(rename_tab_action, error_msg, env);
+ Ok(())
+ })
+ .with_context(error_msg)
+ .fatal();
+}
+
// Custom panic handler for plugins.
//
// This is called when a panic occurs in a plugin. Since most panics will likely originate in the
diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs
index 7ff85946..e1575e7f 100644
--- a/zellij-server/src/route.rs
+++ b/zellij-server/src/route.rs
@@ -4,6 +4,7 @@ use std::sync::{Arc, RwLock};
use crate::thread_bus::ThreadSenders;
use crate::{
os_input_output::ServerOsApi,
+ panes::PaneId,
plugins::PluginInstruction,
pty::{ClientOrTabIndex, PtyInstruction},
screen::ScreenInstruction,
@@ -629,6 +630,66 @@ pub(crate) fn route_action(
))
.with_context(err_context)?;
},
+ Action::CloseTerminalPane(terminal_pane_id) => {
+ senders
+ .send_to_screen(ScreenInstruction::ClosePane(
+ PaneId::Terminal(terminal_pane_id),
+ None, // we send None here so that the terminal pane would be closed anywhere
+ // in the app, not just in the client's tab
+ ))
+ .with_context(err_context)?;
+ },
+ Action::ClosePluginPane(plugin_pane_id) => {
+ senders
+ .send_to_screen(ScreenInstruction::ClosePane(
+ PaneId::Plugin(plugin_pane_id),
+ None, // we send None here so that the terminal pane would be closed anywhere
+ // in the app, not just in the client's tab
+ ))
+ .with_context(err_context)?;
+ },
+ Action::FocusTerminalPaneWithId(pane_id, should_float_if_hidden) => {
+ senders
+ .send_to_screen(ScreenInstruction::FocusPaneWithId(
+ PaneId::Terminal(pane_id),
+ should_float_if_hidden,
+ client_id,
+ ))
+ .with_context(err_context)?;
+ },
+ Action::FocusPluginPaneWithId(pane_id, should_float_if_hidden) => {
+ senders
+ .send_to_screen(ScreenInstruction::FocusPaneWithId(
+ PaneId::Plugin(pane_id),
+ should_float_if_hidden,
+ client_id,
+ ))
+ .with_context(err_context)?;
+ },
+ Action::RenameTerminalPane(pane_id, name_bytes) => {
+ senders
+ .send_to_screen(ScreenInstruction::RenamePane(
+ PaneId::Terminal(pane_id),
+ name_bytes,
+ ))
+ .with_context(err_context)?;
+ },
+ Action::RenamePluginPane(pane_id, name_bytes) => {
+ senders
+ .send_to_screen(ScreenInstruction::RenamePane(
+ PaneId::Plugin(pane_id),
+ name_bytes,
+ ))
+ .with_context(err_context)?;
+ },
+ Action::RenameTab(tab_position, name_bytes) => {
+ senders
+ .send_to_screen(ScreenInstruction::RenameTab(
+ tab_position as usize,
+ name_bytes,
+ ))
+ .with_context(err_context)?;
+ },
}
Ok(should_break)
}
diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs
index ae62be37..2bb93841 100644
--- a/zellij-server/src/screen.rs
+++ b/zellij-server/src/screen.rs
@@ -274,7 +274,10 @@ pub enum ScreenInstruction {
ProgressPluginLoadingOffset(u32), // u32 - plugin id
RequestStateUpdateForPlugins,
LaunchOrFocusPlugin(RunPlugin, bool, ClientId), // bool is should_float
- SuppressPane(PaneId, ClientId),
+ SuppressPane(PaneId, ClientId), // bool is should_float
+ FocusPaneWithId(PaneId, bool, ClientId), // bool is should_float
+ RenamePane(PaneId, Vec<u8>),
+ RenameTab(usize, Vec<u8>),
}
impl From<&ScreenInstruction> for ScreenContext {
@@ -439,6 +442,9 @@ impl From<&ScreenInstruction> for ScreenContext {
},
ScreenInstruction::LaunchOrFocusPlugin(..) => ScreenContext::LaunchOrFocusPlugin,
ScreenInstruction::SuppressPane(..) => ScreenContext::SuppressPane,
+ ScreenInstruction::FocusPaneWithId(..) => ScreenContext::FocusPaneWithId,
+ ScreenInstruction::RenamePane(..) => ScreenContext::RenamePane,
+ ScreenInstruction::RenameTab(..) => ScreenContext::RenameTab,
}
}
}
@@ -1521,6 +1527,34 @@ impl Screen {
}
}
+ pub fn focus_pane_with_id(
+ &mut self,
+ pane_id: PaneId,
+ should_float_if_hidden: bool,
+ client_id: ClientId,
+ ) -> Result<()> {
+ let err_context = || format!("failed to focus_plugin_pane");
+ let tab_index = self
+ .tabs
+ .iter()
+ .find(|(_tab_index, tab)| tab.has_pane_with_pid(&pane_id))
+ .map(|(tab_index, _tab)| *tab_index);
+ match tab_index {
+ Some(tab_index) => {
+ self.go_to_tab(tab_index + 1, client_id)?;
+ self.tabs
+ .get_mut(&tab_index)
+ .with_context(err_context)?
+ .focus_pane_with_id(pane_id, should_float_if_hidden, client_id)
+ .context("failed to focus pane with id")?;
+ },
+ None => {
+ log::error!("Could not find pane with id: {:?}", pane_id);
+ },
+ };
+ Ok(())
+ }
+
fn unblock_input(&self) -> Result<()> {
self.bus
.senders
@@ -2753,6 +2787,35 @@ pub(crate) fn screen_thread_main(
}
screen.report_pane_state()?;
},
+ ScreenInstruction::FocusPaneWithId(pane_id, should_float_if_hidden, client_id) => {
+ screen.focus_pane_with_id(pane_id, should_float_if_hidden, client_id)?;
+ screen.report_pane_state()?;
+ screen.report_tab_state()?;
+ },
+ ScreenInstruction::RenamePane(pane_id, new_name) => {
+ let all_tabs = screen.get_tabs_mut();
+ for tab in all_tabs.values_mut() {
+ if tab.has_pane_with_pid(&pane_id) {
+ match tab.rename_pane(new_name, pane_id) {
+ Ok(()) => drop(screen.render()),
+ Err(e) => log::error!("Failed to rename pane: {:?}", e),
+ }
+ break;
+ }
+ }
+ screen.report_pane_state()?;
+ },
+ ScreenInstruction::RenameTab(tab_index, new_name) => {
+ match screen.tabs.get_mut(&tab_index.saturating_sub(1)) {
+ Some(tab) => {
+ tab.name = String::from_utf8_lossy(&new_name).to_string();
+ },
+ None => {
+ log::error!("Failed to find tab with index: {:?}", tab_index);
+ },
+ }
+ screen.report_tab_state()?;
+ },
}
}
Ok(())
diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs
index cf326b86..f1326c31 100644
--- a/zellij-server/src/tab/mod.rs
+++ b/zellij-server/src/tab/mod.rs
@@ -456,6 +456,7 @@ pub trait Pane {
fn exit_status(&self) -> Option<i32> {
None
}
+ fn rename(&mut self, _buf: Vec<u8>) {}
}
#[derive(Clone, Debug)]
@@ -3073,6 +3074,23 @@ impl Tab {
Ok(())
}
+ pub fn rename_pane(&mut self, buf: Vec<u8>, pane_id: PaneId) -> Result<()> {
+ let err_context = || {
+ format!(
+ "failed to update name of active pane to '{buf:?}' for pane_id {:?}",
+ pane_id
+ )
+ };
+ let pane = self
+ .floating_panes
+ .get_pane_mut(pane_id)
+ .or_else(|| self.tiled_panes.get_pane_mut(pane_id))
+ .or_else(|| self.suppressed_panes.get_mut(&pane_id))
+ .with_context(err_context)?;
+ pane.rename(buf);
+ Ok(())
+ }
+
pub fn undo_active_rename_pane(&mut self, client_id: ClientId) -> Result<()> {
if let Some(active_terminal_id) = self.get_active_terminal_id(client_id) {
let active_terminal = if self.are_floating_panes_visible() {
@@ -3282,6 +3300,7 @@ impl Tab {
should_float: bool,
client_id: ClientId,
) -> Result<()> {
+ // TODO: should error if pane is not selectable
self.tiled_panes
.focus_pane_if_exists(pane_id, client_id)
.or_else(|_| {
diff --git a/zellij-tile/src/shim.rs b/zellij-tile/src/shim.rs
index 0a3102a1..cfa02eaf 100644
--- a/zellij-tile/src/shim.rs
+++ b/zellij-tile/src/shim.rs
@@ -1,4 +1,5 @@
use serde::{de::DeserializeOwned, Serialize};
+use std::str::FromStr;
use std::{io, path::Path};
use zellij_utils::data::*;
use zellij_utils::errors::prelude::*;
@@ -90,6 +91,10 @@ pub fn hide_self() {
unsafe { host_hide_self() };
}
+pub fn show_self(should_float_if_hidden: bool) {
+ unsafe { host_show_self(should_float_if_hidden as i32) };
+}
+
pub fn switch_to_input_mode(mode: &InputMode) {
object_to_stdout(&mode);
unsafe { host_switch_to_mode() };
@@ -269,6 +274,58 @@ pub fn start_or_reload_plugin(url: &str) {
unsafe { host_start_or_reload_plugin() };
}
+pub fn close_terminal_pane(terminal_pane_id: i32) {
+ unsafe { host_close_terminal_pane(terminal_pane_id) };
+}
+
+pub fn close_plugin_pane(plugin_pane_id: i32) {
+ unsafe { host_close_plugin_pane(plugin_pane_id) };
+}
+
+pub fn focus_terminal_pane(terminal_pane_id: i32, should_float_if_hidden: bool) {
+ unsafe { host_focus_terminal_pane(terminal_pane_id, should_float_if_hidden as i32) };
+}
+
+pub fn focus_plugin_pane(plugin_pane_id: i32, should_float_if_hidden: bool) {
+ unsafe { host_focus_plugin_pane(plugin_pane_id, should_float_if_hidden as i32) };
+}
+
+pub fn rename_terminal_pane(terminal_pane_id: i32, new_name: &str) {
+ match String::from_str(new_name) {
+ Ok(new_name) => {
+ object_to_stdout(&(terminal_pane_id, new_name));
+ unsafe { host_rename_terminal_pane() };
+ },
+ Err(e) => {
+ eprintln!("Failed to rename terminal: {:?}", e)
+ },
+ }
+}
+
+pub fn rename_plugin_pane(plugin_pane_id: i32, new_name: &str) {
+ match String::from_str(new_name) {
+ Ok(new_name) => {
+ object_to_stdout(&(plugin_pane_id, new_name));
+ unsafe { host_rename_plugin_pane() };
+ },
+ Err(e) => {
+ eprintln!("Failed to rename plugin: {:?}", e)
+ },
+ }
+}
+
+pub fn rename_tab(tab_position: i32, new_name: &str) {
+ match String::from_str(new_name) {
+ Ok(new_name) => {
+ object_to_stdout(&(tab_position, new_name));
+ unsafe { host_rename_tab() };
+ },
+ Err(e) => {
+ eprintln!("Failed to rename tab: {:?}", e)
+ },
+ }
+}
+
// Internal Functions
#[doc(hidden)]
@@ -282,6 +339,7 @@ pub fn object_from_stdin<T: DeserializeOwned>() -> Result<T> {
#[doc(hidden)]
pub fn object_to_stdout(object: &impl Serialize) {
+ // TODO: no crashy
println!("{}", serde_json::to_string(object).unwrap());
}
@@ -325,6 +383,7 @@ extern "C" {
fn host_post_message_to();
fn host_post_message_to_plugin();
fn host_hide_self();
+ fn host_show_self(should_float_if_hidden: i32);
fn host_switch_to_mode();
fn host_new_tabs_with_layout();
fn host_new_tab();
@@ -365,4 +424,11 @@ extern "C" {
fn host_focus_or_create_tab();
fn host_go_to_tab(tab_index: i32);
fn host_start_or_reload_plugin();
+ fn host_close_terminal_pane(terminal_pane: i32);
+ fn host_close_plugin_pane(plugin_pane: i32);
+ fn host_focus_terminal_pane(terminal_pane: i32, should_float_if_hidden: i32);
+ fn host_focus_plugin_pane(plugin_pane: i32, should_float_if_hidden: i32);
+ fn host_rename_terminal_pane();
+ fn host_rename_plugin_pane();
+ fn host_rename_tab();
}
diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs
index 78468891..e434067e 100644
--- a/zellij-utils/src/errors.rs
+++ b/zellij-utils/src/errors.rs
@@ -335,6 +335,9 @@ pub enum ScreenContext {
RequestStateUpdateForPlugins,
LaunchOrFocusPlugin,
SuppressPane,
+ FocusPaneWithId,
+ RenamePane,
+ RenameTab,
}
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s.
diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs
index f6e080d8..c2071846 100644
--- a/zellij-utils/src/input/actions.rs
+++ b/zellij-utils/src/input/actions.rs
@@ -233,6 +233,13 @@ pub enum Action {
NewTiledPluginPane(RunPluginLocation, Option<String>), // String is an optional name
NewFloatingPluginPane(RunPluginLocation, Option<String>), // String is an optional name
StartOrReloadPlugin(RunPlugin),
+ CloseTerminalPane(u32),
+ ClosePluginPane(u32),
+ FocusTerminalPaneWithId(u32, bool), // bool is should_float_if_hidden
+ FocusPluginPaneWithId(u32, bool), // bool is should_float_if_hidden
+ RenameTerminalPane(u32, Vec<u8>),
+ RenamePluginPane(u32, Vec<u8>),
+ RenameTab(u32, Vec<u8>),
}
impl Action {