Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugins): add API to list clients, their focused panes and running commands/plugins #3687

Merged
merged 3 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions default-plugins/fixture-plugin-for-tests/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,9 @@ impl ZellijPlugin for State {
];
rebind_keys(keys_to_unbind, keys_to_rebind, write_to_disk);
},
BareKey::Char('z') if key.has_modifiers(&[KeyModifier::Alt]) => {
list_clients();
},
_ => {},
},
Event::CustomMessage(message, payload) => {
Expand Down
33 changes: 32 additions & 1 deletion zellij-server/src/plugins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use wasm_bridge::WasmBridge;
use zellij_utils::{
async_std::{channel, future::timeout, task},
data::{
Event, EventType, InputMode, MessageToPlugin, PermissionStatus, PermissionType,
ClientInfo, Event, EventType, InputMode, MessageToPlugin, PermissionStatus, PermissionType,
PipeMessage, PipeSource, PluginCapabilities,
},
errors::{prelude::*, ContextType, PluginContext},
Expand Down Expand Up @@ -158,6 +158,7 @@ pub enum PluginInstruction {
file_path: Option<PathBuf>,
},
WatchFilesystem,
ListClientsToPlugin(SessionLayoutMetadata, PluginId, ClientId),
Exit,
}

Expand Down Expand Up @@ -203,6 +204,7 @@ impl From<&PluginInstruction> for PluginContext {
PluginInstruction::FailedToWriteConfigToDisk { .. } => {
PluginContext::FailedToWriteConfigToDisk
},
PluginInstruction::ListClientsToPlugin(..) => PluginContext::ListClientsToPlugin,
}
}
}
Expand Down Expand Up @@ -607,6 +609,35 @@ pub(crate) fn plugin_thread_main(
},
}
},
PluginInstruction::ListClientsToPlugin(
mut session_layout_metadata,
plugin_id,
client_id,
) => {
populate_session_layout_metadata(
&mut session_layout_metadata,
&wasm_bridge,
&plugin_aliases,
);
let mut clients_metadata = session_layout_metadata.all_clients_metadata();
let mut client_list_for_plugin = vec![];
let default_editor = session_layout_metadata.default_editor.clone();
for (client_metadata_id, client_metadata) in clients_metadata.iter_mut() {
let is_current_client = client_metadata_id == &client_id;
client_list_for_plugin.push(ClientInfo::new(
*client_metadata_id,
client_metadata.get_pane_id().into(),
client_metadata.stringify_command(&default_editor),
is_current_client,
));
}
let updates = vec![(
Some(plugin_id),
Some(client_id),
Event::ListClients(client_list_for_plugin),
)];
wasm_bridge.update_plugins(updates, shutdown_send.clone())?;
},
PluginInstruction::LogLayoutToHd(mut session_layout_metadata) => {
populate_session_layout_metadata(
&mut session_layout_metadata,
Expand Down
71 changes: 71 additions & 0 deletions zellij-server/src/plugins/unit/plugin_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8551,3 +8551,74 @@ pub fn rebind_keys_plugin_command() {
.clone();
assert_snapshot!(format!("{:#?}", rebind_event));
}

#[test]
#[ignore]
pub fn list_clients_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, screen_receiver, 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 = 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!(
received_screen_instructions,
ScreenInstruction::ListClientsToPlugin,
screen_receiver,
1,
&PermissionType::ChangeApplicationState,
cache_path,
plugin_thread_sender,
client_id
);

let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
let _ = plugin_thread_sender.send(PluginInstruction::Load(
plugin_should_float,
false,
plugin_title,
run_plugin,
Some(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(KeyWithModifier::new(BareKey::Char('z')).with_alt_modifier()), // this triggers the enent in the fixture plugin
)]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown();
let list_clients_instruction = received_screen_instructions
.lock()
.unwrap()
.iter()
.find_map(|i| {
if let ScreenInstruction::ListClientsToPlugin(..) = i {
Some(i.clone())
} else {
None
}
})
.unwrap();
assert_snapshot!(format!("{:#?}", list_clients_instruction));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 8623
expression: "format!(\"{:#?}\", list_clients_instruction)"
---
ListClientsToPlugin(
0,
1,
)
14 changes: 13 additions & 1 deletion zellij-server/src/plugins/zellij_exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ fn host_run_plugin_command(caller: Caller<'_, PluginEnv>) {
keys_to_unbind,
write_config_to_disk,
} => rebind_keys(env, keys_to_rebind, keys_to_unbind, write_config_to_disk)?,
PluginCommand::ListClients => list_clients(env),
},
(PermissionStatus::Denied, permission) => {
log::error!(
Expand Down Expand Up @@ -1474,6 +1475,15 @@ fn dump_session_layout(env: &PluginEnv) {
.map(|sender| sender.send(ScreenInstruction::DumpLayoutToPlugin(env.plugin_id)));
}

fn list_clients(env: &PluginEnv) {
let _ = env.senders.to_screen.as_ref().map(|sender| {
sender.send(ScreenInstruction::ListClientsToPlugin(
env.plugin_id,
env.client_id,
))
});
}

fn scan_host_folder(env: &PluginEnv, folder_to_scan: PathBuf) {
if !folder_to_scan.starts_with("/host") {
log::error!(
Expand Down Expand Up @@ -1897,7 +1907,9 @@ fn check_command_permission(
| PluginCommand::BlockCliPipeInput(..)
| PluginCommand::CliPipeOutput(..) => PermissionType::ReadCliPipes,
PluginCommand::MessageToPlugin(..) => PermissionType::MessageAndLaunchOtherPlugins,
PluginCommand::DumpSessionLayout => PermissionType::ReadApplicationState,
PluginCommand::ListClients | PluginCommand::DumpSessionLayout => {
PermissionType::ReadApplicationState
},
PluginCommand::RebindKeys { .. } | PluginCommand::Reconfigure(..) => {
PermissionType::Reconfigure
},
Expand Down
19 changes: 19 additions & 0 deletions zellij-server/src/pty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ pub enum PtyInstruction {
client_id: ClientId,
default_editor: Option<PathBuf>,
},
ListClientsToPlugin(SessionLayoutMetadata, PluginId, ClientId),
Exit,
}

Expand All @@ -125,6 +126,7 @@ impl From<&PtyInstruction> for PtyContext {
PtyInstruction::FillPluginCwd(..) => PtyContext::FillPluginCwd,
PtyInstruction::ListClientsMetadata(..) => PtyContext::ListClientsMetadata,
PtyInstruction::Reconfigure { .. } => PtyContext::Reconfigure,
PtyInstruction::ListClientsToPlugin(..) => PtyContext::ListClientsToPlugin,
PtyInstruction::Exit => PtyContext::Exit,
}
}
Expand Down Expand Up @@ -724,6 +726,23 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
.with_context(err_context)
.non_fatal();
},
PtyInstruction::ListClientsToPlugin(
mut session_layout_metadata,
plugin_id,
client_id,
) => {
let err_context = || format!("Failed to dump layout");
pty.populate_session_layout_metadata(&mut session_layout_metadata);
pty.bus
.senders
.send_to_plugin(PluginInstruction::ListClientsToPlugin(
session_layout_metadata,
plugin_id,
client_id,
))
.with_context(err_context)
.non_fatal();
},
PtyInstruction::LogLayoutToHd(mut session_layout_metadata) => {
let err_context = || format!("Failed to dump layout");
pty.populate_session_layout_metadata(&mut session_layout_metadata);
Expand Down
19 changes: 18 additions & 1 deletion zellij-server/src/screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ pub enum ScreenInstruction {
should_change_focus_to_new_tab: bool,
client_id: ClientId,
},
ListClientsToPlugin(PluginId, ClientId),
}

impl From<&ScreenInstruction> for ScreenContext {
Expand Down Expand Up @@ -621,6 +622,7 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::BreakPanesToTabWithIndex { .. } => {
ScreenContext::BreakPanesToTabWithIndex
},
ScreenInstruction::ListClientsToPlugin(..) => ScreenContext::ListClientsToPlugin,
}
}
}
Expand Down Expand Up @@ -2506,7 +2508,7 @@ impl Screen {
let active_tab_index =
first_client_id.and_then(|client_id| self.active_tab_indices.get(&client_id));

for (tab_index, tab) in self.tabs.values().enumerate() {
for (tab_index, tab) in self.tabs.iter() {
let tab_is_focused = active_tab_index == Some(&tab_index);
let hide_floating_panes = !tab.are_floating_panes_visible();
let mut suppressed_panes = HashMap::new();
Expand Down Expand Up @@ -3110,6 +3112,21 @@ pub(crate) fn screen_thread_main(
.with_context(err_context)
.non_fatal();
},
ScreenInstruction::ListClientsToPlugin(plugin_id, client_id) => {
let err_context = || format!("Failed to dump layout");
let session_layout_metadata =
screen.get_layout_metadata(screen.default_shell.clone());
screen
.bus
.senders
.send_to_pty(PtyInstruction::ListClientsToPlugin(
session_layout_metadata,
plugin_id,
client_id,
))
.with_context(err_context)
.non_fatal();
},
ScreenInstruction::EditScrollback(client_id) => {
active_tab_and_connected_client_id!(
screen,
Expand Down
27 changes: 26 additions & 1 deletion zellij-server/src/session_layout_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,28 @@ impl SessionLayoutMetadata {

ClientMetadata::render_many(clients_metadata, &self.default_editor)
}
pub fn all_clients_metadata(&self) -> BTreeMap<ClientId, ClientMetadata> {
let mut clients_metadata: BTreeMap<ClientId, ClientMetadata> = BTreeMap::new();
for tab in &self.tabs {
let panes = if tab.hide_floating_panes {
&tab.tiled_panes
} else {
&tab.floating_panes
};
for pane in panes {
for focused_client in &pane.focused_clients {
clients_metadata.insert(
*focused_client,
ClientMetadata {
pane_id: pane.id.clone(),
command: pane.run.clone(),
},
);
}
}
}
clients_metadata
}
pub fn is_dirty(&self) -> bool {
// here we check to see if the serialized layout would be different than the base one, and
// thus is "dirty". A layout is considered dirty if one of the following is true:
Expand Down Expand Up @@ -372,7 +394,7 @@ impl PaneLayoutMetadata {
}
}

struct ClientMetadata {
pub struct ClientMetadata {
pane_id: PaneId,
command: Option<Run>,
}
Expand Down Expand Up @@ -404,6 +426,9 @@ impl ClientMetadata {
};
stringified.unwrap_or("N/A".to_owned())
}
pub fn get_pane_id(&self) -> PaneId {
self.pane_id
}
pub fn render_many(
clients_metadata: BTreeMap<ClientId, ClientMetadata>,
default_editor: &Option<PathBuf>,
Expand Down
9 changes: 9 additions & 0 deletions zellij-tile/src/shim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,15 @@ pub fn dump_session_layout() {
unsafe { host_run_plugin_command() };
}

/// Get a list of clients, their focused pane and running command or focused plugin back as an
/// Event::ListClients (note: this event must be subscribed to)
pub fn list_clients() {
let plugin_command = PluginCommand::ListClients;
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
unsafe { host_run_plugin_command() };
}

/// Change configuration for the current user
pub fn reconfigure(new_config: String, save_configuration_file: bool) {
let plugin_command = PluginCommand::Reconfigure(new_config, save_configuration_file);
Expand Down
Loading
Loading