mirror of
https://github.com/zellij-org/zellij.git
synced 2024-11-25 23:33:08 +03:00
feat(cli): list clients, their focused pane_id and the running command (#3314)
* feat(cli): list clients * style(fmt): rustfmt
This commit is contained in:
parent
158260799b
commit
64ce7a7d75
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 =
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -736,4 +736,5 @@ tail -f /tmp/my-live-logfile | zellij action pipe --name logs --plugin https://e
|
||||
#[clap(short('t'), long, value_parser, display_order(10))]
|
||||
plugin_title: Option<String>,
|
||||
},
|
||||
ListClients,
|
||||
}
|
||||
|
@ -352,6 +352,7 @@ pub enum ScreenContext {
|
||||
DumpLayoutToHd,
|
||||
RenameSession,
|
||||
DumpLayoutToPlugin,
|
||||
ListClientsMetadata,
|
||||
}
|
||||
|
||||
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s.
|
||||
@ -373,6 +374,7 @@ pub enum PtyContext {
|
||||
LogLayoutToHd,
|
||||
FillPluginCwd,
|
||||
DumpLayoutToPlugin,
|
||||
ListClientsMetadata,
|
||||
Exit,
|
||||
}
|
||||
|
||||
@ -405,6 +407,7 @@ pub enum PluginContext {
|
||||
WatchFilesystem,
|
||||
KeybindPipe,
|
||||
DumpLayoutToPlugin,
|
||||
ListClientsMetadata,
|
||||
}
|
||||
|
||||
/// Stack call representations corresponding to the different types of [`ClientInstruction`]s.
|
||||
|
@ -297,6 +297,7 @@ pub enum Action {
|
||||
cwd: Option<PathBuf>,
|
||||
pane_title: Option<String>,
|
||||
},
|
||||
ListClients,
|
||||
}
|
||||
|
||||
impl Action {
|
||||
@ -689,6 +690,7 @@ impl Action {
|
||||
skip_cache,
|
||||
}])
|
||||
},
|
||||
CliAction::ListClients => Ok(vec![Action::ListClients]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1293,6 +1293,7 @@ impl TryFrom<Action> for ProtobufAction {
|
||||
| Action::Copy
|
||||
| Action::DumpLayout
|
||||
| Action::CliPipe { .. }
|
||||
| Action::ListClients
|
||||
| Action::SkipConfirm(..) => Err("Unsupported action"),
|
||||
}
|
||||
}
|
||||
|
@ -216,7 +216,7 @@ fn kdl_string_from_tiled_pane(
|
||||
kdl_string
|
||||
}
|
||||
|
||||
fn extract_command_and_args(layout_run: &Option<Run>) -> (Option<String>, Vec<String>) {
|
||||
pub fn extract_command_and_args(layout_run: &Option<Run>) -> (Option<String>, Vec<String>) {
|
||||
match layout_run {
|
||||
Some(Run::Command(run_command)) => (
|
||||
Some(run_command.command.display().to_string()),
|
||||
@ -225,7 +225,7 @@ fn extract_command_and_args(layout_run: &Option<Run>) -> (Option<String>, Vec<St
|
||||
_ => (None, vec![]),
|
||||
}
|
||||
}
|
||||
fn extract_plugin_and_config(
|
||||
pub fn extract_plugin_and_config(
|
||||
layout_run: &Option<Run>,
|
||||
) -> (Option<String>, Option<PluginUserConfiguration>) {
|
||||
match &layout_run {
|
||||
@ -246,7 +246,7 @@ fn extract_plugin_and_config(
|
||||
_ => (None, None),
|
||||
}
|
||||
}
|
||||
fn extract_edit_and_line_number(layout_run: &Option<Run>) -> (Option<String>, Option<usize>) {
|
||||
pub fn extract_edit_and_line_number(layout_run: &Option<Run>) -> (Option<String>, Option<usize>) {
|
||||
match &layout_run {
|
||||
// TODO: line number in layouts?
|
||||
Some(Run::EditFile(path, line_number, _cwd)) => {
|
||||
|
Loading…
Reference in New Issue
Block a user