feat(plugins): more plugin api methods (#2550)

* feat(plugins): close, focus, rename pane, rename tab and show_self api methods

* style(fmt): rustfmt
This commit is contained in:
Aram Drevekenin 2023-06-17 14:41:49 +02:00 committed by GitHub
parent 044519f537
commit 63e3a1eae2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 994 additions and 1 deletions

View file

@ -183,6 +183,33 @@ impl ZellijPlugin for State {
Key::Ctrl('p') => { Key::Ctrl('p') => {
hide_self(); 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) => { Event::CustomMessage(message, payload) => {

View file

@ -571,6 +571,10 @@ impl Pane for PluginPane {
self.pane_name.to_owned() 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 { impl PluginPane {

View file

@ -745,6 +745,10 @@ impl Pane for TerminalPane {
None => false, 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 { impl TerminalPane {

View file

@ -3608,3 +3608,483 @@ pub fn hide_self_plugin_command() {
.clone(); .clone();
assert_snapshot!(format!("{:#?}", new_tab_event)); 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));
}

View file

@ -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,
),
)

View file

@ -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,
),
)

View file

@ -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,
),
)

View file

@ -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,
),
)

View file

@ -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,
],
),
)

View file

@ -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,
],
),
)

View file

@ -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,
],
),
)

View file

@ -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,
),
)

View file

@ -85,6 +85,7 @@ pub fn zellij_exports(
host_post_message_to, host_post_message_to,
host_post_message_to_plugin, host_post_message_to_plugin,
host_hide_self, host_hide_self,
host_show_self,
host_switch_to_mode, host_switch_to_mode,
host_new_tabs_with_layout, host_new_tabs_with_layout,
host_new_tab, host_new_tab,
@ -125,6 +126,13 @@ pub fn zellij_exports(
host_focus_or_create_tab, host_focus_or_create_tab,
host_go_to_tab, host_go_to_tab,
host_start_or_reload_plugin, 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(); .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) { fn host_switch_to_mode(env: &ForeignFunctionEnv) {
wasi_read_object::<InputMode>(&env.plugin_env.wasi_env) wasi_read_object::<InputMode>(&env.plugin_env.wasi_env)
.and_then(|input_mode| { .and_then(|input_mode| {
@ -966,6 +981,88 @@ fn host_start_or_reload_plugin(env: &ForeignFunctionEnv) {
.fatal(); .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. // Custom panic handler for plugins.
// //
// This is called when a panic occurs in a plugin. Since most panics will likely originate in the // This is called when a panic occurs in a plugin. Since most panics will likely originate in the

View file

@ -4,6 +4,7 @@ use std::sync::{Arc, RwLock};
use crate::thread_bus::ThreadSenders; use crate::thread_bus::ThreadSenders;
use crate::{ use crate::{
os_input_output::ServerOsApi, os_input_output::ServerOsApi,
panes::PaneId,
plugins::PluginInstruction, plugins::PluginInstruction,
pty::{ClientOrTabIndex, PtyInstruction}, pty::{ClientOrTabIndex, PtyInstruction},
screen::ScreenInstruction, screen::ScreenInstruction,
@ -629,6 +630,66 @@ pub(crate) fn route_action(
)) ))
.with_context(err_context)?; .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) Ok(should_break)
} }

View file

@ -274,7 +274,10 @@ pub enum ScreenInstruction {
ProgressPluginLoadingOffset(u32), // u32 - plugin id ProgressPluginLoadingOffset(u32), // u32 - plugin id
RequestStateUpdateForPlugins, RequestStateUpdateForPlugins,
LaunchOrFocusPlugin(RunPlugin, bool, ClientId), // bool is should_float 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 { impl From<&ScreenInstruction> for ScreenContext {
@ -439,6 +442,9 @@ impl From<&ScreenInstruction> for ScreenContext {
}, },
ScreenInstruction::LaunchOrFocusPlugin(..) => ScreenContext::LaunchOrFocusPlugin, ScreenInstruction::LaunchOrFocusPlugin(..) => ScreenContext::LaunchOrFocusPlugin,
ScreenInstruction::SuppressPane(..) => ScreenContext::SuppressPane, 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<()> { fn unblock_input(&self) -> Result<()> {
self.bus self.bus
.senders .senders
@ -2753,6 +2787,35 @@ pub(crate) fn screen_thread_main(
} }
screen.report_pane_state()?; 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(()) Ok(())

View file

@ -456,6 +456,7 @@ pub trait Pane {
fn exit_status(&self) -> Option<i32> { fn exit_status(&self) -> Option<i32> {
None None
} }
fn rename(&mut self, _buf: Vec<u8>) {}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -3073,6 +3074,23 @@ impl Tab {
Ok(()) 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<()> { 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) { if let Some(active_terminal_id) = self.get_active_terminal_id(client_id) {
let active_terminal = if self.are_floating_panes_visible() { let active_terminal = if self.are_floating_panes_visible() {
@ -3282,6 +3300,7 @@ impl Tab {
should_float: bool, should_float: bool,
client_id: ClientId, client_id: ClientId,
) -> Result<()> { ) -> Result<()> {
// TODO: should error if pane is not selectable
self.tiled_panes self.tiled_panes
.focus_pane_if_exists(pane_id, client_id) .focus_pane_if_exists(pane_id, client_id)
.or_else(|_| { .or_else(|_| {

View file

@ -1,4 +1,5 @@
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
use std::str::FromStr;
use std::{io, path::Path}; use std::{io, path::Path};
use zellij_utils::data::*; use zellij_utils::data::*;
use zellij_utils::errors::prelude::*; use zellij_utils::errors::prelude::*;
@ -90,6 +91,10 @@ pub fn hide_self() {
unsafe { host_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) { pub fn switch_to_input_mode(mode: &InputMode) {
object_to_stdout(&mode); object_to_stdout(&mode);
unsafe { host_switch_to_mode() }; unsafe { host_switch_to_mode() };
@ -269,6 +274,58 @@ pub fn start_or_reload_plugin(url: &str) {
unsafe { host_start_or_reload_plugin() }; 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 // Internal Functions
#[doc(hidden)] #[doc(hidden)]
@ -282,6 +339,7 @@ pub fn object_from_stdin<T: DeserializeOwned>() -> Result<T> {
#[doc(hidden)] #[doc(hidden)]
pub fn object_to_stdout(object: &impl Serialize) { pub fn object_to_stdout(object: &impl Serialize) {
// TODO: no crashy
println!("{}", serde_json::to_string(object).unwrap()); println!("{}", serde_json::to_string(object).unwrap());
} }
@ -325,6 +383,7 @@ extern "C" {
fn host_post_message_to(); fn host_post_message_to();
fn host_post_message_to_plugin(); fn host_post_message_to_plugin();
fn host_hide_self(); fn host_hide_self();
fn host_show_self(should_float_if_hidden: i32);
fn host_switch_to_mode(); fn host_switch_to_mode();
fn host_new_tabs_with_layout(); fn host_new_tabs_with_layout();
fn host_new_tab(); fn host_new_tab();
@ -365,4 +424,11 @@ extern "C" {
fn host_focus_or_create_tab(); fn host_focus_or_create_tab();
fn host_go_to_tab(tab_index: i32); fn host_go_to_tab(tab_index: i32);
fn host_start_or_reload_plugin(); 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();
} }

View file

@ -335,6 +335,9 @@ pub enum ScreenContext {
RequestStateUpdateForPlugins, RequestStateUpdateForPlugins,
LaunchOrFocusPlugin, LaunchOrFocusPlugin,
SuppressPane, SuppressPane,
FocusPaneWithId,
RenamePane,
RenameTab,
} }
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s. /// Stack call representations corresponding to the different types of [`PtyInstruction`]s.

View file

@ -233,6 +233,13 @@ pub enum Action {
NewTiledPluginPane(RunPluginLocation, Option<String>), // String is an optional name NewTiledPluginPane(RunPluginLocation, Option<String>), // String is an optional name
NewFloatingPluginPane(RunPluginLocation, Option<String>), // String is an optional name NewFloatingPluginPane(RunPluginLocation, Option<String>), // String is an optional name
StartOrReloadPlugin(RunPlugin), 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 { impl Action {