1use crate::data::{Direction, InputMode, Resize, UnblockCondition};
2use crate::setup::Setup;
3use crate::{
4 consts::{ZELLIJ_CONFIG_DIR_ENV, ZELLIJ_CONFIG_FILE_ENV},
5 input::{
6 layout::PluginUserConfiguration,
7 options::{Options, PaneFrameStyle},
8 },
9};
10use clap::builder::styling::{AnsiColor, Color, Style, Styles};
11use clap::{Args, Parser, Subcommand, ValueEnum};
12use serde::{Deserialize, Serialize};
13use std::net::IpAddr;
14use std::path::PathBuf;
15use url::Url;
16
17const fn ansi(color: AnsiColor) -> Style {
18 Style::new().fg_color(Some(Color::Ansi(color)))
19}
20
21const CLI_STYLES: Styles = Styles::styled()
22 .header(ansi(AnsiColor::Yellow))
23 .usage(ansi(AnsiColor::Yellow))
24 .literal(ansi(AnsiColor::Green))
25 .placeholder(Style::new())
26 .error(ansi(AnsiColor::Red))
27 .valid(ansi(AnsiColor::Green))
28 .invalid(ansi(AnsiColor::Yellow));
29
30fn validate_session(name: &str) -> Result<String, String> {
31 #[cfg(unix)]
32 {
33 use crate::consts::ZELLIJ_SOCK_MAX_LENGTH;
34
35 let mut socket_path = crate::consts::ZELLIJ_SOCK_DIR.clone();
36 socket_path.push(name);
37
38 if socket_path.as_os_str().len() >= ZELLIJ_SOCK_MAX_LENGTH {
39 let available_length = ZELLIJ_SOCK_MAX_LENGTH
41 .saturating_sub(socket_path.as_os_str().len())
42 .saturating_sub(1);
43
44 return Err(format!(
45 "session name must be less than {} characters",
46 available_length
47 ));
48 };
49 };
50
51 Ok(name.to_owned())
52}
53
54#[derive(Parser, Default, Debug, Clone, Serialize, Deserialize)]
55#[clap(
56 version,
57 name = "zellij",
58 about = "A terminal workspace with batteries included",
59 styles = CLI_STYLES,
60 args_override_self = true
61)]
62pub struct CliArgs {
63 #[clap(long, value_parser)]
65 pub max_panes: Option<usize>,
66
67 #[clap(long, value_parser, overrides_with = "data_dir")]
69 pub data_dir: Option<PathBuf>,
70
71 #[clap(long, value_parser, hide = true, overrides_with = "server")]
73 pub server: Option<PathBuf>,
74
75 #[clap(long, short, overrides_with = "session", value_parser = validate_session)]
77 pub session: Option<String>,
78
79 #[clap(short, long, value_parser, overrides_with = "layout")]
83 pub layout: Option<PathBuf>,
84
85 #[clap(long, value_parser, conflicts_with_all = &["layout", "new_session_with_layout"])]
89 pub layout_string: Option<String>,
90
91 #[clap(short, long, value_parser, overrides_with = "new_session_with_layout")]
94 pub new_session_with_layout: Option<PathBuf>,
95
96 #[clap(short, long, overrides_with = "config", env = ZELLIJ_CONFIG_FILE_ENV, value_parser)]
98 pub config: Option<PathBuf>,
99
100 #[clap(long, overrides_with = "config_dir", env = ZELLIJ_CONFIG_DIR_ENV, value_parser)]
102 pub config_dir: Option<PathBuf>,
103
104 #[clap(subcommand)]
105 pub command: Option<Command>,
106
107 #[clap(short, long, value_parser)]
109 pub debug: bool,
110}
111
112impl CliArgs {
113 pub fn is_setup_clean(&self) -> bool {
114 if let Some(Command::Setup(ref setup)) = &self.command {
115 if setup.clean {
116 return true;
117 }
118 }
119 false
120 }
121 pub fn options(&self) -> Option<Options> {
122 if let Some(Command::Options(options)) = &self.command {
123 return Some(options.clone());
124 }
125 None
126 }
127}
128
129#[derive(Debug, Subcommand, Clone, Serialize, Deserialize)]
130pub enum Command {
131 #[clap(name = "options", value_parser)]
133 Options(Options),
134
135 #[clap(name = "setup", value_parser)]
137 Setup(Setup),
138
139 #[clap(name = "web", value_parser)]
141 Web(WebCli),
142
143 #[clap(visible_alias = "ac")]
145 #[clap(subcommand)]
146 Action(Box<CliAction>),
147
148 #[clap(flatten)]
150 Sessions(Sessions),
151
152 #[clap(override_usage(
154 "zellij [--session <OTHER SESSION NAME>] subscribe [OPTIONS] --pane-id..."
155 ))]
156 Subscribe(SubscribeCli),
157}
158
159#[derive(Debug, Parser, Clone, Serialize, Deserialize)]
160pub struct SubscribeCli {
161 #[clap(
163 short,
164 long,
165 required = true,
166 num_args(1..)
167 )]
168 pub pane_id: Vec<String>,
169
170 #[clap(
173 short,
174 long,
175 default_missing_value = "0",
176 num_args(0..=1)
177 )]
178 pub scrollback: Option<usize>,
179
180 #[clap(short, long, default_value = "raw", value_enum)]
182 pub format: SubscribeFormat,
183
184 #[clap(long)]
186 pub ansi: bool,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize, ValueEnum)]
190pub enum SubscribeFormat {
191 Raw,
192 Json,
193}
194
195#[derive(Debug, Clone, Args, Serialize, Deserialize)]
196pub struct WebCli {
197 #[clap(long, value_parser, display_order = 1)]
199 pub start: bool,
200
201 #[clap(long, value_parser, exclusive(true), display_order = 2)]
203 pub stop: bool,
204
205 #[clap(long, value_parser, conflicts_with("start"), display_order = 3)]
207 pub status: bool,
208
209 #[clap(long, value_parser, requires = "status", display_order = 4)]
211 pub timeout: Option<u64>,
212
213 #[clap(
215 short,
216 long,
217 value_parser,
218 conflicts_with_all(&["stop", "status", "create_token", "revoke_token", "revoke_all_tokens"]),
219 display_order = 5
220 )]
221 pub daemonize: bool,
222 #[clap(long, value_parser, display_order = 6)]
226 pub server_startup_timeout: Option<u64>,
227 #[clap(long, value_parser, exclusive(true), display_order = 7)]
230 pub create_token: bool,
231 #[clap(long, value_parser, value_name = "TOKEN_NAME", display_order = 8)]
233 pub token_name: Option<String>,
234 #[clap(long, value_parser, exclusive(true), display_order = 9)]
236 pub create_read_only_token: bool,
237 #[clap(
239 long,
240 value_parser,
241 exclusive(true),
242 value_name = "TOKEN NAME",
243 display_order = 10
244 )]
245 pub revoke_token: Option<String>,
246 #[clap(long, value_parser, exclusive(true), display_order = 11)]
248 pub revoke_all_tokens: bool,
249 #[clap(long, value_parser, exclusive(true), display_order = 12)]
251 pub list_tokens: bool,
252 #[clap(
254 long,
255 value_parser,
256 conflicts_with_all(&["stop", "create_token", "revoke_token", "revoke_all_tokens"]),
257 display_order = 13
258 )]
259 pub ip: Option<IpAddr>,
260 #[clap(
262 long,
263 value_parser,
264 conflicts_with_all(&["stop", "create_token", "revoke_token", "revoke_all_tokens"]),
265 display_order = 14
266 )]
267 pub port: Option<u16>,
268 #[clap(
270 long,
271 value_parser,
272 conflicts_with_all(&["stop", "status", "create_token", "revoke_token", "revoke_all_tokens"]),
273 display_order = 15
274 )]
275 pub cert: Option<PathBuf>,
276 #[clap(
278 long,
279 value_parser,
280 conflicts_with_all(&["stop", "status", "create_token", "revoke_token", "revoke_all_tokens"]),
281 display_order = 16
282 )]
283 pub key: Option<PathBuf>,
284}
285
286impl WebCli {
287 pub fn get_start(&self) -> bool {
288 self.start
289 || !(self.stop
290 || self.status
291 || self.create_token
292 || self.create_read_only_token
293 || self.revoke_token.is_some()
294 || self.revoke_all_tokens
295 || self.list_tokens)
296 }
297}
298
299#[derive(Debug, Subcommand, Clone, Serialize, Deserialize)]
300pub enum SessionCommand {
301 #[clap(name = "options")]
303 Options(Options),
304}
305
306#[derive(Debug, Subcommand, Clone, Serialize, Deserialize)]
307pub enum Sessions {
308 #[clap(visible_alias = "ls")]
310 ListSessions {
311 #[clap(short, long)]
313 no_formatting: bool,
314
315 #[clap(short, long)]
317 short: bool,
318
319 #[clap(short, long)]
321 reverse: bool,
322 },
323 #[clap(visible_alias = "la")]
325 ListAliases,
326 #[clap(visible_alias = "a")]
328 Attach {
329 #[clap(value_parser)]
331 session_name: Option<String>,
332
333 #[clap(short, long, value_parser)]
335 create: bool,
336
337 #[clap(short('b'), long, value_parser)]
339 create_background: bool,
340
341 #[clap(long, value_parser)]
343 index: Option<usize>,
344
345 #[clap(subcommand, name = "options")]
347 options: Option<Box<SessionCommand>>,
348
349 #[clap(short, long)]
351 force_run_commands: bool,
352
353 #[clap(short('t'), long, value_parser)]
355 token: Option<String>,
356
357 #[clap(short('r'), long, value_parser)]
359 remember: bool,
360
361 #[clap(long, value_parser)]
363 forget: bool,
364
365 #[clap(long, value_name = "FILE", value_parser)]
367 ca_cert: Option<PathBuf>,
368
369 #[clap(long, value_parser)]
371 insecure: bool,
372 },
373
374 #[clap(visible_alias = "w")]
376 Watch {
377 #[clap(value_parser)]
379 session_name: Option<String>,
380 },
381
382 #[clap(visible_alias = "k")]
384 KillSession {
385 #[clap(value_parser)]
387 target_session: Option<String>,
388 },
389
390 #[clap(visible_alias = "d")]
392 DeleteSession {
393 #[clap(value_parser)]
395 target_session: Option<String>,
396 #[clap(short, long)]
398 force: bool,
399 },
400
401 #[clap(visible_alias = "ka")]
403 KillAllSessions {
404 #[clap(short, long, value_parser)]
406 yes: bool,
407 },
408
409 #[clap(visible_alias = "da")]
411 DeleteAllSessions {
412 #[clap(short, long, value_parser)]
414 yes: bool,
415 #[clap(short, long)]
417 force: bool,
418 },
419
420 #[clap(visible_alias = "r")]
423 Run {
424 #[clap(last(true), required(true))]
426 command: Vec<String>,
427
428 #[clap(short, long, value_parser, conflicts_with("floating"))]
430 direction: Option<Direction>,
431
432 #[clap(long, value_parser)]
434 cwd: Option<PathBuf>,
435
436 #[clap(short, long)]
438 floating: bool,
439
440 #[clap(short, long, conflicts_with("floating"), conflicts_with("direction"))]
442 in_place: bool,
443
444 #[clap(long, requires("in_place"))]
446 close_replaced_pane: bool,
447
448 #[clap(short, long, value_parser)]
450 name: Option<String>,
451
452 #[clap(short, long)]
454 close_on_exit: bool,
455
456 #[clap(short, long)]
458 start_suspended: bool,
459
460 #[clap(short, long, requires("floating"))]
462 x: Option<String>,
463 #[clap(short, long, requires("floating"))]
465 y: Option<String>,
466 #[clap(long, requires("floating"))]
468 width: Option<String>,
469 #[clap(long, requires("floating"))]
471 height: Option<String>,
472 #[clap(long, requires("floating"))]
474 pinned: Option<bool>,
475 #[clap(long, conflicts_with("floating"), conflicts_with("direction"))]
476 stacked: bool,
477 #[clap(long)]
479 blocking: bool,
480
481 #[clap(
483 long,
484 conflicts_with("blocking"),
485 conflicts_with("block_until_exit_failure"),
486 conflicts_with("block_until_exit")
487 )]
488 block_until_exit_success: bool,
489
490 #[clap(
493 long,
494 conflicts_with("blocking"),
495 conflicts_with("block_until_exit_success"),
496 conflicts_with("block_until_exit")
497 )]
498 block_until_exit_failure: bool,
499
500 #[clap(
502 long,
503 conflicts_with("blocking"),
504 conflicts_with("block_until_exit_success"),
505 conflicts_with("block_until_exit_failure")
506 )]
507 block_until_exit: bool,
508 #[clap(long)]
510 near_current_pane: bool,
511 #[clap(
512 long,
513 help = "if set, will open the pane without changing the focus of any client, placing it relative to the pane the command was issued from"
514 )]
515 no_focus: bool,
516 #[clap(short, long, value_parser)]
519 borderless: Option<bool>,
520 #[clap(
522 long,
523 value_parser,
524 conflicts_with("near_current_pane"),
525 conflicts_with("in_place")
526 )]
527 tab_id: Option<usize>,
528 },
529 #[clap(visible_alias = "p")]
532 Plugin {
533 #[clap(last(true), required(true))]
535 url: String,
536
537 #[clap(short, long, value_parser)]
539 configuration: Option<PluginUserConfiguration>,
540
541 #[clap(short, long)]
543 floating: bool,
544
545 #[clap(short, long, conflicts_with("floating"))]
547 in_place: bool,
548
549 #[clap(long, requires("in_place"))]
551 close_replaced_pane: bool,
552
553 #[clap(short, long)]
555 skip_plugin_cache: bool,
556 #[clap(short, long, requires("floating"))]
558 x: Option<String>,
559 #[clap(short, long, requires("floating"))]
561 y: Option<String>,
562 #[clap(long, requires("floating"))]
564 width: Option<String>,
565 #[clap(long, requires("floating"))]
567 height: Option<String>,
568 #[clap(long, requires("floating"))]
570 pinned: Option<bool>,
571 #[clap(
572 long,
573 help = "if set, will open the plugin pane without changing the focus of any client, placing it relative to the pane the command was issued from"
574 )]
575 no_focus: bool,
576 #[clap(short, long, value_parser)]
579 borderless: Option<bool>,
580 #[clap(long, value_parser, conflicts_with("in_place"))]
582 tab_id: Option<usize>,
583 },
584 #[clap(visible_alias = "e")]
587 Edit {
588 file: PathBuf,
589
590 #[clap(short, long, value_parser)]
592 line_number: Option<usize>,
593
594 #[clap(short, long, value_parser, conflicts_with("floating"))]
596 direction: Option<Direction>,
597
598 #[clap(short, long, conflicts_with("floating"), conflicts_with("direction"))]
600 in_place: bool,
601
602 #[clap(long, requires("in_place"))]
604 close_replaced_pane: bool,
605
606 #[clap(short, long)]
608 floating: bool,
609
610 #[clap(long, value_parser)]
612 cwd: Option<PathBuf>,
613 #[clap(short, long, requires("floating"))]
615 x: Option<String>,
616 #[clap(short, long, requires("floating"))]
618 y: Option<String>,
619 #[clap(long, requires("floating"))]
621 width: Option<String>,
622 #[clap(long, requires("floating"))]
624 height: Option<String>,
625 #[clap(long, requires("floating"))]
627 pinned: Option<bool>,
628 #[clap(long)]
630 near_current_pane: bool,
631 #[clap(
632 long,
633 help = "if set, will open the pane without changing the focus of any client, placing it relative to the pane the command was issued from"
634 )]
635 no_focus: bool,
636 #[clap(short, long, value_parser)]
639 borderless: Option<bool>,
640 #[clap(
642 long,
643 value_parser,
644 conflicts_with("near_current_pane"),
645 conflicts_with("in_place")
646 )]
647 tab_id: Option<usize>,
648 },
649 #[clap(override_usage(
651r#"
652zellij pipe [OPTIONS] [--] <PAYLOAD>
653
654* Send data to a specific plugin:
655
656zellij pipe --plugin file:/path/to/my/plugin.wasm --name my_pipe_name -- my_arbitrary_data
657
658* To all running plugins (that are listening):
659
660zellij pipe --name my_pipe_name -- my_arbitrary_data
661
662* Pipe data into this command's STDIN and get output from the plugin on this command's STDOUT
663
664tail -f /tmp/my-live-logfile | zellij pipe --name logs --plugin https://example.com/my-plugin.wasm | wc -l
665"#))]
666 Pipe {
667 #[clap(short, long, value_parser, display_order(1))]
669 name: Option<String>,
670 payload: Option<String>,
672
673 #[clap(short, long, value_parser, display_order(2))]
674 args: Option<PluginUserConfiguration>, #[clap(short, long, value_parser, display_order(3))]
680 plugin: Option<String>,
681 #[clap(short('c'), long, value_parser, display_order(4))]
684 plugin_configuration: Option<PluginUserConfiguration>,
685 },
686}
687
688#[derive(Debug, Subcommand, Clone, Serialize, Deserialize)]
689pub enum CliAction {
690 Write {
692 bytes: Vec<u8>,
693 #[clap(short, long, value_parser)]
695 pane_id: Option<String>,
696 },
697 WriteChars {
699 chars: String,
700 #[clap(short, long, value_parser)]
702 pane_id: Option<String>,
703 },
704 Paste {
706 chars: String,
707 #[clap(short, long, value_parser)]
709 pane_id: Option<String>,
710 },
711 SendKeys {
713 #[clap(value_parser, required = true)]
715 keys: Vec<String>,
716
717 #[clap(short, long, value_parser)]
719 pane_id: Option<String>,
720 },
721 Resize {
723 resize: Resize,
724 direction: Option<Direction>,
725 #[clap(short, long, value_parser)]
727 pane_id: Option<String>,
728 },
729 FocusNextPane,
731 FocusPreviousPane,
733 FocusPaneId {
735 pane_id: String,
737 },
738 FocusLastPane,
740 MoveFocus {
742 direction: Direction,
743 },
744 MoveFocusOrTab {
747 direction: Direction,
748 },
749 MovePane {
752 direction: Option<Direction>,
753 #[clap(short, long, value_parser)]
755 pane_id: Option<String>,
756 },
757 MovePaneBackwards {
759 #[clap(short, long, value_parser)]
761 pane_id: Option<String>,
762 },
763 Clear {
765 #[clap(short, long, value_parser)]
767 pane_id: Option<String>,
768 },
769 DumpScreen {
771 #[clap(long, value_parser)]
773 path: Option<PathBuf>,
774
775 #[clap(short, long)]
777 full: bool,
778
779 #[clap(short, long, value_parser)]
781 pane_id: Option<String>,
782
783 #[clap(short, long)]
785 ansi: bool,
786 },
787 DumpLayout,
789 SaveSession,
791 EditScrollback {
793 #[clap(short, long, value_parser)]
795 pane_id: Option<String>,
796
797 #[clap(short, long)]
799 ansi: bool,
800 },
801 ScrollUp {
803 #[clap(short, long, value_parser)]
805 pane_id: Option<String>,
806 },
807 ScrollDown {
809 #[clap(short, long, value_parser)]
811 pane_id: Option<String>,
812 },
813 ScrollToBottom {
815 #[clap(short, long, value_parser)]
817 pane_id: Option<String>,
818 },
819 ScrollToTop {
821 #[clap(short, long, value_parser)]
823 pane_id: Option<String>,
824 },
825 PageScrollUp {
827 #[clap(short, long, value_parser)]
829 pane_id: Option<String>,
830 },
831 PageScrollDown {
833 #[clap(short, long, value_parser)]
835 pane_id: Option<String>,
836 },
837 HalfPageScrollUp {
839 #[clap(short, long, value_parser)]
841 pane_id: Option<String>,
842 },
843 HalfPageScrollDown {
845 #[clap(short, long, value_parser)]
847 pane_id: Option<String>,
848 },
849 ToggleFullscreen {
851 #[clap(short, long, value_parser)]
853 pane_id: Option<String>,
854 },
855 #[clap(
856 about = "Toggle between fullscreen over the entire display (including the UI bars) and normal layout"
857 )]
858 ToggleNoUiFullscreen {
859 #[clap(
860 short,
861 long,
862 value_parser,
863 help = "Target a specific pane by ID (eg. terminal_1, plugin_2, or 3)"
864 )]
865 pane_id: Option<String>,
866 },
867 TogglePaneFrames,
869 SetPaneFrameStyle {
870 #[clap(value_enum, value_parser)]
871 style: PaneFrameStyle,
872 },
873 ToggleActiveSyncTab {
875 #[clap(short, long, value_parser)]
877 tab_id: Option<usize>,
878 },
879 NewPane {
883 #[clap(short, long, value_parser, conflicts_with("floating"))]
885 direction: Option<Direction>,
886
887 #[clap(last(true))]
888 command: Vec<String>,
889
890 #[clap(short, long, conflicts_with("command"), conflicts_with("direction"))]
891 plugin: Option<String>,
892
893 #[clap(long, value_parser)]
895 cwd: Option<PathBuf>,
896
897 #[clap(short, long)]
899 floating: bool,
900
901 #[clap(short, long, conflicts_with("floating"), conflicts_with("direction"))]
903 in_place: bool,
904
905 #[clap(long, requires("in_place"))]
907 close_replaced_pane: bool,
908
909 #[clap(
912 long,
913 value_parser,
914 requires("in_place"),
915 conflicts_with("near_current_pane")
916 )]
917 pane_id: Option<String>,
918
919 #[clap(short, long, value_parser)]
921 name: Option<String>,
922
923 #[clap(short, long, requires("command"))]
925 close_on_exit: bool,
926 #[clap(short, long, requires("command"))]
928 start_suspended: bool,
929 #[clap(long, value_parser)]
930 configuration: Option<PluginUserConfiguration>,
931 #[clap(long, value_parser)]
932 skip_plugin_cache: bool,
933 #[clap(short, long, requires("floating"))]
935 x: Option<String>,
936 #[clap(short, long, requires("floating"))]
938 y: Option<String>,
939 #[clap(long, requires("floating"))]
941 width: Option<String>,
942 #[clap(long, requires("floating"))]
944 height: Option<String>,
945 #[clap(long, requires("floating"))]
947 pinned: Option<bool>,
948 #[clap(long, conflicts_with("floating"), conflicts_with("direction"))]
949 stacked: bool,
950 #[clap(short, long)]
952 blocking: bool,
953
954 #[clap(
956 long,
957 conflicts_with("blocking"),
958 conflicts_with("block_until_exit_failure"),
959 conflicts_with("block_until_exit")
960 )]
961 block_until_exit_success: bool,
962
963 #[clap(
966 long,
967 conflicts_with("blocking"),
968 conflicts_with("block_until_exit_success"),
969 conflicts_with("block_until_exit")
970 )]
971 block_until_exit_failure: bool,
972
973 #[clap(
975 long,
976 conflicts_with("blocking"),
977 conflicts_with("block_until_exit_success"),
978 conflicts_with("block_until_exit_failure")
979 )]
980 block_until_exit: bool,
981
982 #[clap(skip)]
983 unblock_condition: Option<UnblockCondition>,
984
985 #[clap(long)]
987 near_current_pane: bool,
988 #[clap(
989 long,
990 help = "if set, will open the pane without changing the focus of any client, placing it relative to the pane the command was issued from"
991 )]
992 no_focus: bool,
993 #[clap(long, value_parser)]
996 borderless: Option<bool>,
997 #[clap(
999 long,
1000 value_parser,
1001 conflicts_with("near_current_pane"),
1002 conflicts_with("in_place")
1003 )]
1004 tab_id: Option<usize>,
1005 },
1006 Edit {
1009 file: PathBuf,
1010
1011 #[clap(short, long, value_parser, conflicts_with("floating"))]
1013 direction: Option<Direction>,
1014
1015 #[clap(short, long, value_parser)]
1017 line_number: Option<usize>,
1018
1019 #[clap(short, long)]
1021 floating: bool,
1022
1023 #[clap(short, long, conflicts_with("floating"), conflicts_with("direction"))]
1025 in_place: bool,
1026
1027 #[clap(long, requires("in_place"))]
1029 close_replaced_pane: bool,
1030
1031 #[clap(long, value_parser)]
1033 cwd: Option<PathBuf>,
1034 #[clap(short, long, requires("floating"))]
1036 x: Option<String>,
1037 #[clap(short, long, requires("floating"))]
1039 y: Option<String>,
1040 #[clap(long, requires("floating"))]
1042 width: Option<String>,
1043 #[clap(long, requires("floating"))]
1045 height: Option<String>,
1046 #[clap(long, requires("floating"))]
1048 pinned: Option<bool>,
1049 #[clap(long)]
1051 near_current_pane: bool,
1052 #[clap(
1053 long,
1054 help = "if set, will open the pane without changing the focus of any client, placing it relative to the pane the command was issued from"
1055 )]
1056 no_focus: bool,
1057 #[clap(short, long, value_parser)]
1060 borderless: Option<bool>,
1061 #[clap(
1063 long,
1064 value_parser,
1065 conflicts_with("near_current_pane"),
1066 conflicts_with("in_place")
1067 )]
1068 tab_id: Option<usize>,
1069 },
1070 SwitchMode {
1072 input_mode: InputMode,
1073 },
1074 TogglePaneEmbedOrFloating {
1076 #[clap(short, long, value_parser)]
1078 pane_id: Option<String>,
1079 },
1080 ToggleFloatingPanes {
1082 #[clap(short, long, value_parser)]
1084 tab_id: Option<usize>,
1085 },
1086 ShowFloatingPanes {
1090 #[clap(short, long, value_parser)]
1091 tab_id: Option<usize>,
1092 },
1093 HideFloatingPanes {
1097 #[clap(short, long, value_parser)]
1098 tab_id: Option<usize>,
1099 },
1100 AreFloatingPanesVisible {
1105 #[clap(short, long, value_parser)]
1106 tab_id: Option<usize>,
1107 },
1108 ClosePane {
1110 #[clap(short, long, value_parser)]
1112 pane_id: Option<String>,
1113 },
1114 RenamePane {
1116 name: String,
1117 #[clap(short, long, value_parser)]
1119 pane_id: Option<String>,
1120 },
1121 UndoRenamePane {
1123 #[clap(short, long, value_parser)]
1125 pane_id: Option<String>,
1126 },
1127 GoToNextTab,
1129 GoToPreviousTab,
1131 CloseTab {
1133 #[clap(short, long, value_parser)]
1135 tab_id: Option<usize>,
1136 },
1137 GoToTab {
1139 index: u32,
1140 },
1141 GoToTabName {
1145 name: String,
1146 #[clap(short, long, value_parser)]
1148 create: bool,
1149 },
1150 RenameTab {
1152 name: String,
1153 #[clap(short, long, value_parser)]
1155 tab_id: Option<usize>,
1156 },
1157 UndoRenameTab {
1159 #[clap(short, long, value_parser)]
1161 tab_id: Option<usize>,
1162 },
1163 GoToTabById {
1165 id: u64,
1166 },
1167 CloseTabById {
1169 id: u64,
1170 },
1171 RenameTabById {
1173 id: u64,
1174 name: String,
1175 },
1176 NewTab {
1180 #[clap(short, long, value_parser, conflicts_with = "layout_string")]
1182 layout: Option<PathBuf>,
1183
1184 #[clap(long, value_parser, conflicts_with = "layout")]
1186 layout_string: Option<String>,
1187
1188 #[clap(long, value_parser, requires("layout"))]
1190 layout_dir: Option<PathBuf>,
1191
1192 #[clap(short, long, value_parser)]
1194 name: Option<String>,
1195
1196 #[clap(short, long, value_parser)]
1198 cwd: Option<PathBuf>,
1199
1200 #[clap(value_parser, conflicts_with("initial_plugin"), last(true))]
1202 initial_command: Vec<String>,
1203
1204 #[clap(long, value_parser, conflicts_with("initial_command"))]
1206 initial_plugin: Option<String>,
1207
1208 #[clap(long, requires("initial_command"))]
1210 close_on_exit: bool,
1211
1212 #[clap(long, requires("initial_command"))]
1214 start_suspended: bool,
1215
1216 #[clap(
1218 long,
1219 requires("initial_command"),
1220 conflicts_with("block_until_exit_failure"),
1221 conflicts_with("block_until_exit")
1222 )]
1223 block_until_exit_success: bool,
1224
1225 #[clap(
1227 long,
1228 requires("initial_command"),
1229 conflicts_with("block_until_exit_success"),
1230 conflicts_with("block_until_exit")
1231 )]
1232 block_until_exit_failure: bool,
1233
1234 #[clap(
1236 long,
1237 requires("initial_command"),
1238 conflicts_with("block_until_exit_success"),
1239 conflicts_with("block_until_exit_failure")
1240 )]
1241 block_until_exit: bool,
1242
1243 #[clap(
1244 long,
1245 help = "if set, will create the tab without changing the focus of any client"
1246 )]
1247 no_focus: bool,
1248 },
1249 MoveTab {
1251 direction: Direction,
1252 #[clap(short, long, value_parser)]
1254 tab_id: Option<usize>,
1255 },
1256 PreviousSwapLayout {
1257 #[clap(short, long, value_parser)]
1259 tab_id: Option<usize>,
1260 },
1261 NextSwapLayout {
1262 #[clap(short, long, value_parser)]
1264 tab_id: Option<usize>,
1265 },
1266 OverrideLayout {
1268 #[clap(
1270 value_parser,
1271 required_unless_present = "layout_string",
1272 conflicts_with = "layout_string"
1273 )]
1274 layout: Option<PathBuf>,
1275
1276 #[clap(long, value_parser, conflicts_with = "layout")]
1278 layout_string: Option<String>,
1279
1280 #[clap(long, value_parser)]
1282 layout_dir: Option<PathBuf>,
1283
1284 #[clap(long)]
1286 retain_existing_terminal_panes: bool,
1287
1288 #[clap(long)]
1290 retain_existing_plugin_panes: bool,
1291
1292 #[clap(long)]
1295 apply_only_to_active_tab: bool,
1296 },
1297 QueryTabNames,
1299 StartOrReloadPlugin {
1300 url: String,
1301 #[clap(short, long, value_parser)]
1302 configuration: Option<PluginUserConfiguration>,
1303 },
1304 LaunchOrFocusPlugin {
1306 #[clap(short, long, value_parser)]
1307 floating: bool,
1308 #[clap(short, long, value_parser)]
1309 in_place: bool,
1310 #[clap(long, requires("in_place"))]
1312 close_replaced_pane: bool,
1313 #[clap(short, long, value_parser)]
1314 move_to_focused_tab: bool,
1315 url: String,
1316 #[clap(short, long, value_parser)]
1317 configuration: Option<PluginUserConfiguration>,
1318 #[clap(short, long, value_parser)]
1319 skip_plugin_cache: bool,
1320 #[clap(long, value_parser, conflicts_with("in_place"))]
1322 tab_id: Option<usize>,
1323 },
1324 LaunchPlugin {
1326 #[clap(short, long, value_parser)]
1327 floating: bool,
1328 #[clap(short, long, value_parser)]
1329 in_place: bool,
1330 #[clap(long, requires("in_place"))]
1332 close_replaced_pane: bool,
1333 url: Url,
1334 #[clap(short, long, value_parser)]
1335 configuration: Option<PluginUserConfiguration>,
1336 #[clap(short, long, value_parser)]
1337 skip_plugin_cache: bool,
1338 #[clap(
1339 long,
1340 help = "if set, will open the plugin pane without changing the focus of any client"
1341 )]
1342 no_focus: bool,
1343 #[clap(long, value_parser, conflicts_with("in_place"))]
1345 tab_id: Option<usize>,
1346 },
1347 RenameSession {
1348 name: String,
1349 },
1350 #[clap(override_usage(
1352r#"
1353zellij action pipe [OPTIONS] [--] <PAYLOAD>
1354
1355* Send data to a specific plugin:
1356
1357zellij action pipe --plugin file:/path/to/my/plugin.wasm --name my_pipe_name -- my_arbitrary_data
1358
1359* To all running plugins (that are listening):
1360
1361zellij action pipe --name my_pipe_name -- my_arbitrary_data
1362
1363* Pipe data into this command's STDIN and get output from the plugin on this command's STDOUT
1364
1365tail -f /tmp/my-live-logfile | zellij action pipe --name logs --plugin https://example.com/my-plugin.wasm | wc -l
1366"#))]
1367 Pipe {
1368 #[clap(short, long, value_parser, display_order(1))]
1370 name: Option<String>,
1371 payload: Option<String>,
1373
1374 #[clap(short, long, value_parser, display_order(2))]
1375 args: Option<PluginUserConfiguration>, #[clap(short, long, value_parser, display_order(3))]
1381 plugin: Option<String>,
1382 #[clap(short('c'), long, value_parser, display_order(4))]
1385 plugin_configuration: Option<PluginUserConfiguration>,
1386 #[clap(short('l'), long, display_order(5))]
1388 force_launch_plugin: bool,
1389 #[clap(short('s'), long, display_order(6))]
1391 skip_plugin_cache: bool,
1392 #[clap(short('f'), long, value_parser, display_order(7))]
1394 floating_plugin: Option<bool>,
1395 #[clap(
1397 short('i'),
1398 long,
1399 value_parser,
1400 conflicts_with("floating_plugin"),
1401 display_order(8)
1402 )]
1403 in_place_plugin: Option<bool>,
1404 #[clap(short('w'), long, value_parser, display_order(9))]
1406 plugin_cwd: Option<PathBuf>,
1407 #[clap(short('t'), long, value_parser, display_order(10))]
1409 plugin_title: Option<String>,
1410 },
1411 ListClients,
1412 ListPanes {
1416 #[clap(short, long, value_parser)]
1418 tab: bool,
1419
1420 #[clap(short, long, value_parser)]
1422 command: bool,
1423
1424 #[clap(short, long, value_parser)]
1426 state: bool,
1427
1428 #[clap(short, long, value_parser)]
1430 geometry: bool,
1431
1432 #[clap(short, long, value_parser)]
1434 all: bool,
1435
1436 #[clap(short, long, value_parser)]
1438 json: bool,
1439 },
1440 ListTabs {
1444 #[clap(short, long, value_parser)]
1446 state: bool,
1447
1448 #[clap(short, long, value_parser)]
1450 dimensions: bool,
1451
1452 #[clap(short, long, value_parser)]
1454 panes: bool,
1455
1456 #[clap(short, long, value_parser)]
1458 layout: bool,
1459
1460 #[clap(short, long, value_parser)]
1462 all: bool,
1463
1464 #[clap(short, long, value_parser)]
1466 json: bool,
1467 },
1468 CurrentTabInfo {
1472 #[clap(short, long, value_parser)]
1474 json: bool,
1475 },
1476 TogglePanePinned {
1477 #[clap(short, long, value_parser)]
1479 pane_id: Option<String>,
1480 },
1481 StackPanes {
1489 #[clap(last(true), required(true))]
1490 pane_ids: Vec<String>,
1491 },
1492 ChangeFloatingPaneCoordinates {
1493 #[clap(short, long, value_parser)]
1496 pane_id: String,
1497 #[clap(short, long)]
1499 x: Option<String>,
1500 #[clap(short, long)]
1502 y: Option<String>,
1503 #[clap(long)]
1505 width: Option<String>,
1506 #[clap(long)]
1508 height: Option<String>,
1509 #[clap(long)]
1511 pinned: Option<bool>,
1512 #[clap(short, long, value_parser)]
1515 borderless: Option<bool>,
1516 },
1517 TogglePaneBorderless {
1518 #[clap(short, long, value_parser)]
1520 pane_id: String,
1521 },
1522 SetPaneBorderless {
1523 #[clap(short, long, value_parser)]
1525 pane_id: String,
1526 #[clap(short, long, value_parser)]
1528 borderless: bool,
1529 },
1530 Detach,
1532 SetDarkTheme,
1534 SetLightTheme,
1536 ToggleTheme,
1538 SwitchSession {
1540 name: String,
1542 #[clap(long)]
1544 tab_position: Option<usize>,
1545 #[clap(long)]
1547 pane_id: Option<String>,
1548 #[clap(short, long, value_parser, conflicts_with = "layout_string")]
1550 layout: Option<PathBuf>,
1551 #[clap(long, value_parser, conflicts_with = "layout")]
1553 layout_string: Option<String>,
1554 #[clap(long, value_parser, requires("layout"))]
1556 layout_dir: Option<PathBuf>,
1557 #[clap(short, long, value_parser)]
1559 cwd: Option<PathBuf>,
1560 },
1561 SetPaneColor {
1563 #[clap(short, long, value_parser)]
1566 pane_id: Option<String>,
1567 #[clap(long, value_parser)]
1569 fg: Option<String>,
1570 #[clap(long, value_parser)]
1572 bg: Option<String>,
1573 #[clap(long, value_parser, conflicts_with_all(&["fg", "bg"]))]
1575 reset: bool,
1576 },
1577}
1578
1579#[cfg(test)]
1580mod tests {
1581 use super::*;
1582 use clap::Parser;
1583
1584 fn parse_subscribe(args: &[&str]) -> SubscribeCli {
1585 let mut full_args = vec!["zellij"];
1586 full_args.extend_from_slice(args);
1587 let cli = CliArgs::try_parse_from(full_args).unwrap();
1588 match cli.command {
1589 Some(Command::Subscribe(s)) => s,
1590 other => panic!("Expected Subscribe, got {:?}", other),
1591 }
1592 }
1593
1594 #[test]
1595 fn subscribe_scrollback_bare_flag() {
1596 let s = parse_subscribe(&["subscribe", "--pane-id", "terminal_1", "--scrollback"]);
1597 assert_eq!(s.scrollback, Some(0));
1598 }
1599
1600 #[test]
1601 fn subscribe_scrollback_with_value() {
1602 let s = parse_subscribe(&[
1603 "subscribe",
1604 "--pane-id",
1605 "terminal_1",
1606 "--scrollback",
1607 "100",
1608 ]);
1609 assert_eq!(s.scrollback, Some(100));
1610 }
1611
1612 #[test]
1613 fn subscribe_scrollback_absent() {
1614 let s = parse_subscribe(&["subscribe", "--pane-id", "terminal_1"]);
1615 assert_eq!(s.scrollback, None);
1616 }
1617
1618 #[test]
1619 fn subscribe_format_json() {
1620 let s = parse_subscribe(&["subscribe", "--pane-id", "terminal_1", "--format", "json"]);
1621 assert!(matches!(s.format, SubscribeFormat::Json));
1622 }
1623
1624 #[test]
1625 fn subscribe_format_default_raw() {
1626 let s = parse_subscribe(&["subscribe", "--pane-id", "terminal_1"]);
1627 assert!(matches!(s.format, SubscribeFormat::Raw));
1628 }
1629
1630 #[test]
1631 fn subscribe_multiple_pane_ids() {
1632 let s = parse_subscribe(&[
1633 "subscribe",
1634 "--pane-id",
1635 "terminal_1",
1636 "--pane-id",
1637 "plugin_2",
1638 ]);
1639 assert_eq!(
1640 s.pane_id,
1641 vec!["terminal_1".to_string(), "plugin_2".to_string()]
1642 );
1643 }
1644
1645 #[test]
1646 fn subscribe_requires_pane_id() {
1647 let result = CliArgs::try_parse_from(["zellij", "subscribe"]);
1648 assert!(result.is_err());
1649 }
1650}