aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-server/src
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2024-04-30 15:21:04 +0200
committerGitHub <noreply@github.com>2024-04-30 15:21:04 +0200
commit64ce7a7d751b3b03403af0e99bab6f1c64d6d56e (patch)
tree216050770c518e3be7507e0d08d659c41cf47de7 /zellij-server/src
parentdocs(changelog): layoutinfo newtabs plugin api (diff)
downloadsl0ppij-64ce7a7d751b3b03403af0e99bab6f1c64d6d56e.tar.gz
sl0ppij-64ce7a7d751b3b03403af0e99bab6f1c64d6d56e.tar.xz
sl0ppij-64ce7a7d751b3b03403af0e99bab6f1c64d6d56e.zip
feat(cli): list clients, their focused pane_id and the running command (#3314)
* feat(cli): list clients * style(fmt): rustfmt
Diffstat (limited to 'zellij-server/src')
-rw-r--r--zellij-server/src/plugins/mod.rs9
-rw-r--r--zellij-server/src/pty.rs18
-rw-r--r--zellij-server/src/route.rs12
-rw-r--r--zellij-server/src/screen.rs51
-rw-r--r--zellij-server/src/session_layout_metadata.rs99
5 files changed, 183 insertions, 6 deletions
diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs
index f6862ffd..6beec9c6 100644
--- a/zellij-server/src/plugins/mod.rs
+++ b/zellij-server/src/plugins/mod.rs
@@ -105,6 +105,7 @@ pub enum PluginInstruction {
Option<PathBuf>,
),
DumpLayout(SessionLayoutMetadata, ClientId),
+ ListClientsMetadata(SessionLayoutMetadata, ClientId),
DumpLayoutToPlugin(SessionLayoutMetadata, PluginId),
LogLayoutToHd(SessionLayoutMetadata),
CliPipe {
@@ -173,6 +174,7 @@ impl From<&PluginInstruction> for PluginContext {
PluginContext::PermissionRequestResult
},
PluginInstruction::DumpLayout(..) => PluginContext::DumpLayout,
+ PluginInstruction::ListClientsMetadata(..) => PluginContext::ListClientsMetadata,
PluginInstruction::LogLayoutToHd(..) => PluginContext::LogLayoutToHd,
PluginInstruction::CliPipe { .. } => PluginContext::CliPipe,
PluginInstruction::CachePluginEvents { .. } => PluginContext::CachePluginEvents,
@@ -489,6 +491,13 @@ pub(crate) fn plugin_thread_main(
client_id,
)));
},
+ PluginInstruction::ListClientsMetadata(mut session_layout_metadata, client_id) => {
+ populate_session_layout_metadata(&mut session_layout_metadata, &wasm_bridge);
+ drop(bus.senders.send_to_pty(PtyInstruction::ListClientsMetadata(
+ session_layout_metadata,
+ client_id,
+ )));
+ },
PluginInstruction::DumpLayoutToPlugin(mut session_layout_metadata, plugin_id) => {
populate_session_layout_metadata(&mut session_layout_metadata, &wasm_bridge);
match session_serialization::serialize_session_layout(
diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs
index 5877e56d..f8bd61d2 100644
--- a/zellij-server/src/pty.rs
+++ b/zellij-server/src/pty.rs
@@ -92,6 +92,7 @@ pub enum PtyInstruction {
Option<PathBuf>, // if Some, will not fill cwd but just forward the message
Option<FloatingPaneCoordinates>,
),
+ ListClientsMetadata(SessionLayoutMetadata, ClientId),
Exit,
}
@@ -114,6 +115,7 @@ impl From<&PtyInstruction> for PtyContext {
PtyInstruction::DumpLayoutToPlugin(..) => PtyContext::DumpLayoutToPlugin,
PtyInstruction::LogLayoutToHd(..) => PtyContext::LogLayoutToHd,
PtyInstruction::FillPluginCwd(..) => PtyContext::FillPluginCwd,
+ PtyInstruction::ListClientsMetadata(..) => PtyContext::ListClientsMetadata,
PtyInstruction::Exit => PtyContext::Exit,
}
}
@@ -632,6 +634,21 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
},
}
},
+ PtyInstruction::ListClientsMetadata(mut session_layout_metadata, client_id) => {
+ let err_context = || format!("Failed to dump layout");
+ pty.populate_session_layout_metadata(&mut session_layout_metadata);
+ pty.bus
+ .senders
+ .send_to_server(ServerInstruction::Log(
+ vec![format!(
+ "{}",
+ session_layout_metadata.list_clients_metadata()
+ )],
+ client_id,
+ ))
+ .with_context(err_context)
+ .non_fatal();
+ },
PtyInstruction::DumpLayoutToPlugin(mut session_layout_metadata, plugin_id) => {
let err_context = || format!("Failed to dump layout");
pty.populate_session_layout_metadata(&mut session_layout_metadata);
@@ -1342,6 +1359,7 @@ impl Pty {
session_layout_metadata.update_default_shell(get_default_shell());
session_layout_metadata.update_terminal_commands(terminal_ids_to_commands);
session_layout_metadata.update_terminal_cwds(terminal_ids_to_cwds);
+ session_layout_metadata.update_default_editor(&self.default_editor)
}
pub fn fill_plugin_cwd(
&self,
diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs
index fef5b754..fc50fd2c 100644
--- a/zellij-server/src/route.rs
+++ b/zellij-server/src/route.rs
@@ -926,6 +926,18 @@ pub(crate) fn route_action(
log::error!("Message must have a name");
}
},
+ Action::ListClients => {
+ let default_shell = match default_shell {
+ Some(TerminalAction::RunCommand(run_command)) => Some(run_command.command),
+ _ => None,
+ };
+ senders
+ .send_to_screen(ScreenInstruction::ListClientsMetadata(
+ default_shell,
+ client_id,
+ ))
+ .with_context(err_context)?;
+ },
}
Ok(should_break)
}
diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs
index cd471652..75d34562 100644
--- a/zellij-server/src/screen.rs
+++ b/zellij-server/src/screen.rs
@@ -358,6 +358,7 @@ pub enum ScreenInstruction {
),
DumpLayoutToHd,
RenameSession(String, ClientId), // String -> new name
+ ListClientsMetadata(Option<PathBuf>, ClientId), // Option<PathBuf> - default shell
}
impl From<&ScreenInstruction> for ScreenContext {
@@ -541,6 +542,7 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::NewInPlacePluginPane(..) => ScreenContext::NewInPlacePluginPane,
ScreenInstruction::DumpLayoutToHd => ScreenContext::DumpLayoutToHd,
ScreenInstruction::RenameSession(..) => ScreenContext::RenameSession,
+ ScreenInstruction::ListClientsMetadata(..) => ScreenContext::ListClientsMetadata,
}
}
}
@@ -2165,8 +2167,23 @@ impl Screen {
for (triggering_pane_id, p) in tab.get_suppressed_panes() {
suppressed_panes.insert(*triggering_pane_id, p);
}
- let active_pane_id =
- first_client_id.and_then(|client_id| tab.get_active_pane_id(client_id));
+
+ let all_connected_clients: Vec<ClientId> = self
+ .connected_clients
+ .borrow()
+ .iter()
+ .copied()
+ .filter(|c| self.active_tab_indices.get(&c) == Some(&tab_index))
+ .collect();
+
+ let mut active_pane_ids: HashMap<ClientId, Option<PaneId>> = HashMap::new();
+ for connected_client_id in &all_connected_clients {
+ active_pane_ids.insert(
+ *connected_client_id,
+ tab.get_active_pane_id(*connected_client_id),
+ );
+ }
+
let tiled_panes: Vec<PaneLayoutMetadata> = tab
.get_tiled_panes()
.map(|(pane_id, p)| {
@@ -2183,18 +2200,25 @@ impl Screen {
}
})
.map(|(pane_id, p)| {
+ let focused_clients: Vec<ClientId> = active_pane_ids
+ .iter()
+ .filter_map(|(c_id, p_id)| {
+ p_id.and_then(|p_id| if p_id == pane_id { Some(*c_id) } else { None })
+ })
+ .collect();
PaneLayoutMetadata::new(
pane_id,
p.position_and_size(),
p.borderless(),
p.invoked_with().clone(),
p.custom_title(),
- active_pane_id == Some(pane_id),
+ !focused_clients.is_empty(),
if self.serialize_pane_viewport {
p.serialize(self.scrollback_lines_to_serialize)
} else {
None
},
+ focused_clients,
)
})
.collect();
@@ -2214,18 +2238,25 @@ impl Screen {
}
})
.map(|(pane_id, p)| {
+ let focused_clients: Vec<ClientId> = active_pane_ids
+ .iter()
+ .filter_map(|(c_id, p_id)| {
+ p_id.and_then(|p_id| if p_id == pane_id { Some(*c_id) } else { None })
+ })
+ .collect();
PaneLayoutMetadata::new(
pane_id,
p.position_and_size(),
false, // floating panes are never borderless
p.invoked_with().clone(),
p.custom_title(),
- active_pane_id == Some(pane_id),
+ !focused_clients.is_empty(),
if self.serialize_pane_viewport {
p.serialize(self.scrollback_lines_to_serialize)
} else {
None
},
+ focused_clients,
)
})
.collect();
@@ -2660,6 +2691,18 @@ pub(crate) fn screen_thread_main(
))
.with_context(err_context)?;
},
+ ScreenInstruction::ListClientsMetadata(default_shell, client_id) => {
+ let err_context = || format!("Failed to dump layout");
+ let session_layout_metadata = screen.get_layout_metadata(default_shell);
+ screen
+ .bus
+ .senders
+ .send_to_plugin(PluginInstruction::ListClientsMetadata(
+ session_layout_metadata,
+ client_id,
+ ))
+ .with_context(err_context)?;
+ },
ScreenInstruction::DumpLayoutToPlugin(plugin_id) => {
let err_context = || format!("Failed to dump layout");
let session_layout_metadata =
diff --git a/zellij-server/src/session_layout_metadata.rs b/zellij-server/src/session_layout_metadata.rs
index da19bc90..0ca25804 100644
--- a/zellij-server/src/session_layout_metadata.rs
+++ b/zellij-server/src/session_layout_metadata.rs
@@ -1,12 +1,16 @@
use crate::panes::PaneId;
-use std::collections::HashMap;
+use crate::ClientId;
+use std::collections::{BTreeMap, HashMap};
use std::path::PathBuf;
use zellij_utils::common_path::common_path_all;
use zellij_utils::pane_size::PaneGeom;
use zellij_utils::{
input::command::RunCommand,
input::layout::{Layout, Run, RunPlugin, RunPluginOrAlias},
- session_serialization::{GlobalLayoutManifest, PaneLayoutManifest, TabLayoutManifest},
+ session_serialization::{
+ extract_command_and_args, extract_edit_and_line_number, extract_plugin_and_config,
+ GlobalLayoutManifest, PaneLayoutManifest, TabLayoutManifest,
+ },
};
#[derive(Default, Debug, Clone)]
@@ -14,6 +18,7 @@ pub struct SessionLayoutMetadata {
default_layout: Box<Layout>,
global_cwd: Option<PathBuf>,
pub default_shell: Option<PathBuf>,
+ pub default_editor: Option<PathBuf>,
tabs: Vec<TabLayoutMetadata>,
}
@@ -53,6 +58,29 @@ impl SessionLayoutMetadata {
}
}
}
+ pub fn list_clients_metadata(&self) -> String {
+ 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(),
+ },
+ );
+ }
+ }
+ }
+
+ ClientMetadata::render_many(clients_metadata, &self.default_editor)
+ }
fn is_default_shell(
default_shell: Option<&PathBuf>,
command_name: &String,
@@ -192,6 +220,15 @@ impl SessionLayoutMetadata {
}
}
}
+ pub fn update_default_editor(&mut self, default_editor: &Option<PathBuf>) {
+ let default_editor = default_editor.clone().unwrap_or_else(|| {
+ PathBuf::from(
+ std::env::var("EDITOR")
+ .unwrap_or_else(|_| std::env::var("VISUAL").unwrap_or_else(|_| "vi".into())),
+ )
+ });
+ self.default_editor = Some(default_editor);
+ }
}
impl Into<GlobalLayoutManifest> for SessionLayoutMetadata {
@@ -253,6 +290,7 @@ pub struct PaneLayoutMetadata {
title: Option<String>,
is_focused: bool,
pane_contents: Option<String>,
+ focused_clients: Vec<ClientId>,
}
impl PaneLayoutMetadata {
@@ -264,6 +302,7 @@ impl PaneLayoutMetadata {
title: Option<String>,
is_focused: bool,
pane_contents: Option<String>,
+ focused_clients: Vec<ClientId>,
) -> Self {
PaneLayoutMetadata {
id,
@@ -274,6 +313,62 @@ impl PaneLayoutMetadata {
title,
is_focused,
pane_contents,
+ focused_clients,
+ }
+ }
+}
+
+struct ClientMetadata {
+ pane_id: PaneId,
+ command: Option<Run>,
+}
+impl ClientMetadata {
+ pub fn stringify_pane_id(&self) -> String {
+ match self.pane_id {
+ PaneId::Terminal(terminal_id) => format!("terminal_{}", terminal_id),
+ PaneId::Plugin(plugin_id) => format!("plugin_{}", plugin_id),
+ }
+ }
+ pub fn stringify_command(&self, editor: &Option<PathBuf>) -> String {
+ let stringified = match &self.command {
+ Some(Run::Command(..)) => {
+ let (command, args) = extract_command_and_args(&self.command);
+ command.map(|c| format!("{} {}", c, args.join(" ")))
+ },
+ Some(Run::EditFile(..)) => {
+ let (file_to_edit, _line_number) = extract_edit_and_line_number(&self.command);
+ editor.as_ref().and_then(|editor| {
+ file_to_edit
+ .map(|file_to_edit| format!("{} {}", editor.display(), file_to_edit))
+ })
+ },
+ Some(Run::Plugin(..)) => {
+ let (plugin, _plugin_config) = extract_plugin_and_config(&self.command);
+ plugin.map(|p| format!("{}", p))
+ },
+ _ => None,
+ };
+ stringified.unwrap_or("N/A".to_owned())
+ }
+ pub fn render_many(
+ clients_metadata: BTreeMap<ClientId, ClientMetadata>,
+ default_editor: &Option<PathBuf>,
+ ) -> String {
+ let mut lines = vec![];
+ lines.push(String::from("CLIENT_ID ZELLIJ_PANE_ID RUNNING_COMMAND"));
+
+ for (client_id, client_metadata) in clients_metadata.iter() {
+ // 9 - CLIENT_ID, 14 - ZELLIJ_PANE_ID, 15 - RUNNING_COMMAND
+ lines.push(format!(
+ "{} {} {}",
+ format!("{0: <9}", client_id),
+ format!("{0: <14}", client_metadata.stringify_pane_id()),
+ format!(
+ "{0: <15}",
+ client_metadata.stringify_command(default_editor)
+ )
+ ));
}
+ lines.join("\n")
}
}