1#[cfg(not(target_family = "wasm"))]
2use crate::consts::ASSET_MAP;
3use crate::input::theme::Themes;
4#[allow(unused_imports)]
5use crate::{
6 cli::{CliArgs, Command, SessionCommand, Sessions},
7 consts::{FEATURES, VERSION, ZELLIJ_CACHE_DIR, ZELLIJ_DEFAULT_THEMES},
8 data::LayoutInfo,
9 errors::prelude::*,
10 home::*,
11 input::{
12 config::{Config, ConfigError},
13 layout::Layout,
14 options::Options,
15 },
16};
17use clap::{Args, CommandFactory};
18use clap_complete::Shell;
19use log::info;
20use serde::{Deserialize, Serialize};
21use std::{convert::TryFrom, fmt::Write as FmtWrite, fs, io::Write, path::PathBuf, process};
22
23const CONFIG_NAME: &str = "config.kdl";
24static ARROW_SEPARATOR: &str = "";
25
26#[cfg(not(test))]
27pub fn get_default_themes() -> Themes {
28 let mut themes = Themes::default();
29 for file in ZELLIJ_DEFAULT_THEMES.files() {
30 if let Some(content) = file.contents_utf8() {
31 let sourced_from_external_file = true;
32 match Themes::from_string(&content.to_string(), sourced_from_external_file) {
33 Ok(theme) => themes = themes.merge(theme),
34 Err(_) => {},
35 }
36 }
37 }
38 themes
39}
40
41#[cfg(test)]
42pub fn get_default_themes() -> Themes {
43 Themes::default()
44}
45
46pub fn dump_asset(asset: &[u8]) -> std::io::Result<()> {
47 std::io::stdout().write_all(asset)?;
48 Ok(())
49}
50
51pub const DEFAULT_CONFIG: &[u8] = include_bytes!(concat!(
52 env!("CARGO_MANIFEST_DIR"),
53 "/",
54 "assets/config/default.kdl"
55));
56
57pub const DEFAULT_LAYOUT: &[u8] = include_bytes!(concat!(
58 env!("CARGO_MANIFEST_DIR"),
59 "/",
60 "assets/layouts/default.kdl"
61));
62
63pub const DEFAULT_SWAP_LAYOUT: &[u8] = include_bytes!(concat!(
64 env!("CARGO_MANIFEST_DIR"),
65 "/",
66 "assets/layouts/default.swap.kdl"
67));
68
69pub const STRIDER_LAYOUT: &[u8] = include_bytes!(concat!(
70 env!("CARGO_MANIFEST_DIR"),
71 "/",
72 "assets/layouts/strider.kdl"
73));
74
75pub const STRIDER_SWAP_LAYOUT: &[u8] = include_bytes!(concat!(
76 env!("CARGO_MANIFEST_DIR"),
77 "/",
78 "assets/layouts/strider.swap.kdl"
79));
80
81pub const NO_STATUS_LAYOUT: &[u8] = include_bytes!(concat!(
82 env!("CARGO_MANIFEST_DIR"),
83 "/",
84 "assets/layouts/disable-status-bar.kdl"
85));
86
87pub const COMPACT_BAR_LAYOUT: &[u8] = include_bytes!(concat!(
88 env!("CARGO_MANIFEST_DIR"),
89 "/",
90 "assets/layouts/compact.kdl"
91));
92
93pub const COMPACT_BAR_SWAP_LAYOUT: &[u8] = include_bytes!(concat!(
94 env!("CARGO_MANIFEST_DIR"),
95 "/",
96 "assets/layouts/compact.swap.kdl"
97));
98
99pub const CLASSIC_LAYOUT: &[u8] = include_bytes!(concat!(
100 env!("CARGO_MANIFEST_DIR"),
101 "/",
102 "assets/layouts/classic.kdl"
103));
104
105pub const CLASSIC_SWAP_LAYOUT: &[u8] = include_bytes!(concat!(
106 env!("CARGO_MANIFEST_DIR"),
107 "/",
108 "assets/layouts/classic.swap.kdl"
109));
110
111pub const WELCOME_LAYOUT: &[u8] = include_bytes!(concat!(
112 env!("CARGO_MANIFEST_DIR"),
113 "/",
114 "assets/layouts/welcome.kdl"
115));
116
117pub const FISH_EXTRA_COMPLETION: &[u8] = include_bytes!(concat!(
118 env!("CARGO_MANIFEST_DIR"),
119 "/",
120 "assets/completions/comp.fish"
121));
122
123pub const BASH_EXTRA_COMPLETION: &[u8] = include_bytes!(concat!(
124 env!("CARGO_MANIFEST_DIR"),
125 "/",
126 "assets/completions/comp.bash"
127));
128
129pub const ZSH_EXTRA_COMPLETION: &[u8] = include_bytes!(concat!(
130 env!("CARGO_MANIFEST_DIR"),
131 "/",
132 "assets/completions/comp.zsh"
133));
134
135pub const BASH_AUTO_START_SCRIPT: &[u8] = include_bytes!(concat!(
136 env!("CARGO_MANIFEST_DIR"),
137 "/",
138 "assets/shell/auto-start.bash"
139));
140
141pub const FISH_AUTO_START_SCRIPT: &[u8] = include_bytes!(concat!(
142 env!("CARGO_MANIFEST_DIR"),
143 "/",
144 "assets/shell/auto-start.fish"
145));
146
147pub const ZSH_AUTO_START_SCRIPT: &[u8] = include_bytes!(concat!(
148 env!("CARGO_MANIFEST_DIR"),
149 "/",
150 "assets/shell/auto-start.zsh"
151));
152
153pub fn add_layout_ext(s: &str) -> String {
154 match s {
155 c if s.ends_with(".kdl") => c.to_owned(),
156 _ => {
157 let mut s = s.to_owned();
158 s.push_str(".kdl");
159 s
160 },
161 }
162}
163
164pub fn dump_default_config() -> std::io::Result<()> {
165 dump_asset(DEFAULT_CONFIG)
166}
167
168pub fn dump_specified_layout(layout: &str) -> std::io::Result<()> {
169 match layout {
170 "strider" => dump_asset(STRIDER_LAYOUT),
171 "default" => dump_asset(DEFAULT_LAYOUT),
172 "compact" => dump_asset(COMPACT_BAR_LAYOUT),
173 "disable-status" => dump_asset(NO_STATUS_LAYOUT),
174 "classic" => dump_asset(CLASSIC_LAYOUT),
175 custom => {
176 info!("Dump {custom} layout");
177 let custom = add_layout_ext(custom);
178 let home = default_layout_dir();
179 let path = home.map(|h| h.join(&custom));
180 let layout_exists = path.as_ref().map(|p| p.exists()).unwrap_or_default();
181
182 match (path, layout_exists) {
183 (Some(path), true) => {
184 let content = fs::read_to_string(path)?;
185 std::io::stdout().write_all(content.as_bytes())
186 },
187 _ => {
188 log::error!("No layout named {custom} found");
189 return Ok(());
190 },
191 }
192 },
193 }
194}
195
196pub fn dump_specified_swap_layout(swap_layout: &str) -> std::io::Result<()> {
197 match swap_layout {
198 "strider" => dump_asset(STRIDER_SWAP_LAYOUT),
199 "default" => dump_asset(DEFAULT_SWAP_LAYOUT),
200 "compact" => dump_asset(COMPACT_BAR_SWAP_LAYOUT),
201 "classic" => dump_asset(CLASSIC_SWAP_LAYOUT),
202 not_found => Err(std::io::Error::new(
203 std::io::ErrorKind::Other,
204 format!("Swap Layout not found for: {}", not_found),
205 )),
206 }
207}
208
209#[cfg(not(target_family = "wasm"))]
210pub fn dump_builtin_plugins(path: &PathBuf) -> Result<()> {
211 for (asset_path, bytes) in ASSET_MAP.iter() {
212 let plugin_path = path.join(asset_path);
213 plugin_path
214 .parent()
215 .with_context(|| {
216 format!(
217 "failed to acquire parent path of '{}'",
218 plugin_path.display()
219 )
220 })
221 .and_then(|parent_path| {
222 std::fs::create_dir_all(parent_path).context("failed to create parent path")
223 })
224 .with_context(|| {
225 format!(
226 "failed to create folder '{}' to dump plugin '{}' to",
227 path.display(),
228 plugin_path.display()
229 )
230 })?;
231
232 std::fs::write(plugin_path, bytes)
233 .with_context(|| format!("failed to dump builtin plugin '{}'", asset_path.display()))?;
234 }
235
236 Ok(())
237}
238
239#[cfg(target_family = "wasm")]
240pub fn dump_builtin_plugins(_path: &PathBuf) -> Result<()> {
241 Ok(())
242}
243
244#[derive(Debug, Default, Clone, Args, Serialize, Deserialize)]
245pub struct Setup {
246 #[clap(long, value_parser)]
248 pub dump_config: bool,
249
250 #[clap(long, value_parser)]
253 pub clean: bool,
254
255 #[clap(long, value_parser)]
258 pub check: bool,
259
260 #[clap(long, value_parser)]
262 pub dump_layout: Option<String>,
263
264 #[clap(long, value_parser)]
266 pub dump_swap_layout: Option<String>,
267
268 #[clap(
270 long,
271 value_name = "DIR",
272 value_parser,
273 exclusive = true,
274 num_args(0..=1)
275 )]
276 pub dump_plugins: Option<Option<PathBuf>>,
277
278 #[clap(long, value_name = "SHELL", value_parser)]
280 pub generate_completion: Option<String>,
281
282 #[clap(long, value_name = "SHELL", value_parser)]
284 pub generate_auto_start: Option<String>,
285}
286
287impl Setup {
288 pub fn from_cli_args(
297 cli_args: &CliArgs,
298 ) -> Result<(Config, Option<LayoutInfo>, Options, Config, Options), ConfigError> {
299 Setup::handle_setup_commands(cli_args);
301 let config = Config::try_from(cli_args)?;
302 let cli_config_options: Option<Options> =
303 if let Some(Command::Options(options)) = cli_args.command.clone() {
304 Some(options.into())
305 } else {
306 None
307 };
308
309 let cli_config_options = merge_attach_command_options(cli_config_options, &cli_args);
312
313 let mut config_without_layout = config.clone();
314 let (layout_info, mut config) =
315 Setup::parse_layout_and_override_config(cli_config_options.as_ref(), config, cli_args)?;
316
317 let config_options =
318 apply_themes_to_config(&mut config, cli_config_options.clone(), cli_args)?;
319 let config_options_without_layout =
320 apply_themes_to_config(&mut config_without_layout, cli_config_options, cli_args)?;
321 fn apply_themes_to_config(
322 config: &mut Config,
323 cli_config_options: Option<Options>,
324 cli_args: &CliArgs,
325 ) -> Result<Options, ConfigError> {
326 let config_options = match cli_config_options {
327 Some(cli_config_options) => config.options.merge(cli_config_options),
328 None => config.options.clone(),
329 };
330
331 config.themes = config.themes.merge(get_default_themes());
332
333 let user_theme_dir = config_options.theme_dir.clone().or_else(|| {
334 get_theme_dir(cli_args.config_dir.clone().or_else(find_default_config_dir))
335 .filter(|dir| dir.exists())
336 });
337 if let Some(user_theme_dir) = user_theme_dir {
338 config.themes = config.themes.merge(Themes::from_dir(user_theme_dir)?);
339 }
340 Ok(config_options)
341 }
342
343 if let Some(Command::Setup(ref setup)) = &cli_args.command {
344 setup
345 .from_cli_with_options(cli_args, &config_options)
346 .map_or_else(
347 |e| {
348 eprintln!("{:?}", e);
349 process::exit(1);
350 },
351 |_| {},
352 );
353 };
354 Ok((
355 config,
356 layout_info,
357 config_options,
358 config_without_layout,
359 config_options_without_layout,
360 ))
361 }
362
363 pub fn from_cli(&self) -> Result<()> {
365 if self.clean {
366 return Ok(());
367 }
368
369 if self.dump_config {
370 dump_default_config()?;
371 std::process::exit(0);
372 }
373
374 if let Some(shell) = &self.generate_completion {
375 Self::generate_completion(shell);
376 std::process::exit(0);
377 }
378
379 if let Some(shell) = &self.generate_auto_start {
380 Self::generate_auto_start(shell);
381 std::process::exit(0);
382 }
383
384 if let Some(layout) = &self.dump_layout {
385 dump_specified_layout(&layout)?;
386 std::process::exit(0);
387 }
388
389 if let Some(swap_layout) = &self.dump_swap_layout {
390 dump_specified_swap_layout(swap_layout)?;
391 std::process::exit(0);
392 }
393
394 Ok(())
395 }
396
397 pub fn from_cli_with_options(&self, opts: &CliArgs, config_options: &Options) -> Result<()> {
399 if self.check {
400 Setup::check_defaults_config(opts, config_options)?;
401 std::process::exit(0);
402 }
403
404 if let Some(maybe_path) = &self.dump_plugins {
405 if cfg!(feature = "disable_automatic_asset_installation") {
406 return Err(anyhow!(
407 "This zellij was built without bundled plugins (feature \
408 'disable_automatic_asset_installation'). Builtin plugins are provided by the \
409 distributor of this build and must be placed in the plugin directory, \
410 visible in the output of `zellij setup --check`."
411 ))
412 .context("failed to dump plugins");
413 }
414 let data_dir = &opts.data_dir.clone().unwrap_or_else(get_default_data_dir);
415 let dir = match maybe_path {
416 Some(path) => path,
417 None => data_dir,
418 };
419
420 println!("Dumping plugins to '{}'", dir.display());
421 dump_builtin_plugins(&dir)?;
422 std::process::exit(0);
423 }
424
425 Ok(())
426 }
427
428 pub fn check_defaults_config(opts: &CliArgs, config_options: &Options) -> std::io::Result<()> {
429 let data_dir = opts.data_dir.clone().unwrap_or_else(get_default_data_dir);
430 let config_dir = opts.config_dir.clone().or_else(find_default_config_dir);
431 let plugin_dir = data_dir.join("plugins");
432 let layout_dir = config_options
433 .layout_dir
434 .clone()
435 .or_else(|| get_layout_dir(config_dir.clone()));
436 let system_data_dir = system_data_dir();
437 let config_file = opts
438 .config
439 .clone()
440 .or_else(|| config_dir.clone().map(|p| p.join(CONFIG_NAME)));
441
442 let hyperlink_start = "\u{1b}]8;;";
445 let hyperlink_mid = "\u{1b}\\";
446 let hyperlink_end = "\u{1b}]8;;\u{1b}\\";
447
448 let mut message = String::new();
449
450 writeln!(&mut message, "[Version]: {:?}", VERSION).unwrap();
451 if let Some(config_dir) = config_dir {
452 writeln!(&mut message, "[CONFIG DIR]: \"{}\"", config_dir.display()).unwrap();
453 } else {
454 message.push_str("[CONFIG DIR]: Not Found\n");
455 let mut default_config_dirs = default_config_dirs()
456 .iter()
457 .filter_map(|p| p.clone())
458 .collect::<Vec<PathBuf>>();
459 default_config_dirs.dedup();
460 message.push_str(
461 " On your system zellij looks in the following config directories by default:\n",
462 );
463 for dir in default_config_dirs {
464 writeln!(&mut message, " \"{}\"", dir.display()).unwrap();
465 }
466 }
467 if let Some(config_file) = config_file {
468 writeln!(
469 &mut message,
470 "[LOOKING FOR CONFIG FILE FROM]: \"{}\"",
471 config_file.display()
472 )
473 .unwrap();
474 match Config::from_path(&config_file, None) {
475 Ok(_) => message.push_str("[CONFIG FILE]: Well defined.\n"),
476 Err(e) => writeln!(
477 &mut message,
478 "[CONFIG ERROR]: {}. \n By default, zellij loads default configuration",
479 e
480 )
481 .unwrap(),
482 }
483 } else {
484 message.push_str("[CONFIG FILE]: Not Found\n");
485 writeln!(
486 &mut message,
487 " By default zellij looks for a file called [{}] in the configuration directory",
488 CONFIG_NAME
489 )
490 .unwrap();
491 }
492 writeln!(&mut message, "[CACHE DIR]: {}", ZELLIJ_CACHE_DIR.display()).unwrap();
493 writeln!(&mut message, "[DATA DIR]: \"{}\"", data_dir.display()).unwrap();
494 writeln!(&mut message, "[PLUGIN DIR]: \"{}\"", plugin_dir.display()).unwrap();
495 if !cfg!(feature = "disable_automatic_asset_installation") {
496 writeln!(
497 &mut message,
498 " Builtin, default plugins will not be loaded from disk."
499 )
500 .unwrap();
501 writeln!(
502 &mut message,
503 " Create a custom layout if you require this behavior."
504 )
505 .unwrap();
506 } else {
507 writeln!(
508 &mut message,
509 " This zellij was built without bundled plugins."
510 )
511 .unwrap();
512 writeln!(
513 &mut message,
514 " Builtin plugins are loaded from the 'PLUGIN DIR' above, or from '{}'.",
515 system_data_dir.join("plugins").display()
516 )
517 .unwrap();
518 }
519 if let Some(layout_dir) = layout_dir {
520 writeln!(&mut message, "[LAYOUT DIR]: \"{}\"", layout_dir.display()).unwrap();
521 } else {
522 message.push_str("[LAYOUT DIR]: Not Found\n");
523 }
524 writeln!(
525 &mut message,
526 "[SYSTEM DATA DIR]: \"{}\"",
527 system_data_dir.display()
528 )
529 .unwrap();
530
531 writeln!(&mut message, "[ARROW SEPARATOR]: {}", ARROW_SEPARATOR).unwrap();
532 message.push_str(" Is the [ARROW_SEPARATOR] displayed correctly?\n");
533 message.push_str(" If not you may want to either start zellij with a compatible mode: 'zellij options --simplified-ui true'\n");
534 let mut hyperlink_compat = String::new();
535 hyperlink_compat.push_str(hyperlink_start);
536 hyperlink_compat.push_str("https://zellij.dev/documentation/compatibility.html#the-status-bar-fonts-dont-render-correctly");
537 hyperlink_compat.push_str(hyperlink_mid);
538 hyperlink_compat.push_str("https://zellij.dev/documentation/compatibility.html#the-status-bar-fonts-dont-render-correctly");
539 hyperlink_compat.push_str(hyperlink_end);
540 write!(
541 &mut message,
542 " Or check the font that is in use:\n {}\n",
543 hyperlink_compat
544 )
545 .unwrap();
546 message.push_str("[MOUSE INTERACTION]: \n");
547 message.push_str(" Can be temporarily disabled through pressing the [SHIFT] key.\n");
548 message.push_str(" If that doesn't fix any issues consider to disable the mouse handling of zellij: 'zellij options --disable-mouse-mode'\n");
549
550 let default_editor = std::env::var("EDITOR")
551 .or_else(|_| std::env::var("VISUAL"))
552 .unwrap_or_else(|_| String::from("Not set, checked $EDITOR and $VISUAL"));
553 writeln!(&mut message, "[DEFAULT EDITOR]: {}", default_editor).unwrap();
554 writeln!(&mut message, "[FEATURES]: {:?}", FEATURES).unwrap();
555 let mut hyperlink = String::new();
556 hyperlink.push_str(hyperlink_start);
557 hyperlink.push_str("https://www.zellij.dev/documentation/");
558 hyperlink.push_str(hyperlink_mid);
559 hyperlink.push_str("zellij.dev/documentation");
560 hyperlink.push_str(hyperlink_end);
561 writeln!(&mut message, "[DOCUMENTATION]: {}", hyperlink).unwrap();
562 std::io::stdout().write_all(message.as_bytes())?;
565
566 Ok(())
567 }
568 fn generate_completion(shell: &str) {
569 let shell: Shell = match shell.to_lowercase().parse() {
570 Ok(shell) => shell,
571 _ => {
572 eprintln!("Unsupported shell: {}", shell);
573 std::process::exit(1);
574 },
575 };
576 let mut out = std::io::stdout();
577 clap_complete::generate(shell, &mut CliArgs::command(), "zellij", &mut out);
578 match shell {
580 Shell::Bash => {
581 let _ = out.write_all(BASH_EXTRA_COMPLETION);
582 },
583 Shell::Elvish => {},
584 Shell::Fish => {
585 let _ = out.write_all(FISH_EXTRA_COMPLETION);
586 },
587 Shell::PowerShell => {},
588 Shell::Zsh => {
589 let _ = out.write_all(ZSH_EXTRA_COMPLETION);
590 },
591 _ => {},
592 };
593 }
594
595 fn generate_auto_start(shell: &str) {
596 let shell: Shell = match shell.to_lowercase().parse() {
597 Ok(shell) => shell,
598 _ => {
599 eprintln!("Unsupported shell: {}", shell);
600 std::process::exit(1);
601 },
602 };
603
604 let mut out = std::io::stdout();
605 match shell {
606 Shell::Bash => {
607 let _ = out.write_all(BASH_AUTO_START_SCRIPT);
608 },
609 Shell::Fish => {
610 let _ = out.write_all(FISH_AUTO_START_SCRIPT);
611 },
612 Shell::Zsh => {
613 let _ = out.write_all(ZSH_AUTO_START_SCRIPT);
614 },
615 _ => {},
616 }
617 }
618 fn parse_layout_and_override_config(
619 cli_config_options: Option<&Options>,
620 config: Config,
621 cli_args: &CliArgs,
622 ) -> Result<(Option<LayoutInfo>, Config), ConfigError> {
623 let layout_dir = cli_config_options
625 .as_ref()
626 .and_then(|cli_options| cli_options.layout_dir.clone())
627 .or_else(|| config.options.layout_dir.clone())
628 .or_else(|| get_layout_dir(cli_args.config_dir.clone()))
629 .or_else(|| get_layout_dir(find_default_config_dir()))
630 .map(|d| d.canonicalize().unwrap_or(d));
632 let (layout_info, chosen_layout) = if let Some(ref layout_string) = cli_args.layout_string {
636 (Some(LayoutInfo::Stringified(layout_string.clone())), None)
637 } else if let Some(chosen_layout) = cli_args.layout.clone() {
638 let layout_info = LayoutInfo::from_cli(
639 &layout_dir,
640 &Some(chosen_layout.clone()),
641 std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
642 );
643 (layout_info, Some(chosen_layout))
644 } else {
645 let chosen_layout = cli_config_options
646 .as_ref()
647 .and_then(|cli_options| cli_options.default_layout.clone())
648 .or_else(|| config.options.default_layout.clone());
649 let layout_info = LayoutInfo::from_config(&layout_dir, &chosen_layout);
650 (layout_info, chosen_layout)
651 };
652 match layout_info {
653 Some(LayoutInfo::Url(ref layout_url)) => {
654 Layout::from_url(layout_url, config).map(|(_layout, config)| (layout_info, config))
655 },
656 Some(LayoutInfo::Stringified(ref raw_layout)) => {
657 Layout::from_stringified_layout(raw_layout, config)
658 .map(|(_layout, config)| (layout_info, config))
659 },
660 _ => Layout::from_path_or_default(chosen_layout.as_ref(), layout_dir.clone(), config)
661 .map(|(_layout, config)| (layout_info, config)),
662 }
663 }
664 fn handle_setup_commands(cli_args: &CliArgs) {
665 if let Some(Command::Setup(ref setup)) = &cli_args.command {
666 setup.from_cli().map_or_else(
667 |e| {
668 eprintln!("{:?}", e);
669 process::exit(1);
670 },
671 |_| {},
672 );
673 };
674 }
675}
676
677fn merge_attach_command_options(
678 cli_config_options: Option<Options>,
679 cli_args: &CliArgs,
680) -> Option<Options> {
681 let cli_config_options = if let Some(Command::Sessions(Sessions::Attach { options, .. })) =
682 cli_args.command.clone()
683 {
684 match options.clone().as_deref() {
685 Some(SessionCommand::Options(options)) => match cli_config_options {
686 Some(cli_config_options) => {
687 Some(cli_config_options.merge_from_cli(options.to_owned().into()))
688 },
689 None => Some(options.to_owned().into()),
690 },
691 _ => cli_config_options,
692 }
693 } else {
694 cli_config_options
695 };
696 cli_config_options
697}
698
699#[cfg(test)]
700mod setup_test {
701 use super::Setup;
702 use crate::cli::{CliArgs, Command};
703 use crate::data::LayoutInfo;
704 use crate::input::options::Options;
705 use insta::assert_snapshot;
706 use std::path::PathBuf;
707
708 #[test]
709 fn default_config_with_no_cli_arguments() {
710 let cli_args = CliArgs::default();
711 let (config, layout_info, options, _, _) = Setup::from_cli_args(&cli_args).unwrap();
712 assert_snapshot!(format!("{:#?}", config));
713 assert_snapshot!(format!("{:#?}", layout_info));
714 assert_snapshot!(format!("{:#?}", options));
715 }
716 #[test]
717 fn cli_arguments_override_config_options() {
718 let mut cli_args = CliArgs::default();
719 cli_args.command = Some(Command::Options(Options {
720 simplified_ui: Some(true),
721 ..Default::default()
722 }));
723 let (_config, _layout_info, options, _, _) = Setup::from_cli_args(&cli_args).unwrap();
724 assert_snapshot!(format!("{:#?}", options));
725 }
726 #[test]
727 fn layout_options_override_config_options() {
728 let mut cli_args = CliArgs::default();
729 cli_args.layout = Some(PathBuf::from(format!(
730 "{}/src/test-fixtures/layout-with-options.kdl",
731 env!("CARGO_MANIFEST_DIR")
732 )));
733 let (_config, layout_info, options, _, _) = Setup::from_cli_args(&cli_args).unwrap();
734 assert_snapshot!(format!("{:#?}", options));
735 let Some(LayoutInfo::File(layout_path, _)) = layout_info else {
736 panic!("layout info doesn't have expected format");
737 };
738 assert_eq!(
739 layout_path,
740 format!(
741 "{}/src/test-fixtures/layout-with-options.kdl",
742 env!("CARGO_MANIFEST_DIR")
743 )
744 );
745 }
746 #[test]
747 fn cli_arguments_override_layout_options() {
748 let mut cli_args = CliArgs::default();
749 cli_args.layout = Some(PathBuf::from(format!(
750 "{}/src/test-fixtures/layout-with-options.kdl",
751 env!("CARGO_MANIFEST_DIR")
752 )));
753 cli_args.command = Some(Command::Options(Options {
754 pane_frames: Some(true),
755 ..Default::default()
756 }));
757 let (_config, layout_info, options, _, _) = Setup::from_cli_args(&cli_args).unwrap();
758 assert_snapshot!(format!("{:#?}", options));
759 let Some(LayoutInfo::File(layout_path, _)) = layout_info else {
760 panic!("layout info doesn't have expected format");
761 };
762 assert_eq!(
763 layout_path,
764 format!(
765 "{}/src/test-fixtures/layout-with-options.kdl",
766 env!("CARGO_MANIFEST_DIR")
767 )
768 );
769 }
770 #[test]
771 fn layout_env_vars_override_config_env_vars() {
772 let mut cli_args = CliArgs::default();
773 cli_args.config = Some(PathBuf::from(format!(
774 "{}/src/test-fixtures/config-with-env-vars.kdl",
775 env!("CARGO_MANIFEST_DIR")
776 )));
777 cli_args.layout = Some(PathBuf::from(format!(
778 "{}/src/test-fixtures/layout-with-env-vars.kdl",
779 env!("CARGO_MANIFEST_DIR")
780 )));
781 let (config, _layout_info, _options, _, _) = Setup::from_cli_args(&cli_args).unwrap();
782 assert_snapshot!(format!("{:#?}", config));
783 }
784 #[test]
785 fn layout_ui_config_overrides_config_ui_config() {
786 let mut cli_args = CliArgs::default();
787 cli_args.config = Some(PathBuf::from(format!(
788 "{}/src/test-fixtures/config-with-ui-config.kdl",
789 env!("CARGO_MANIFEST_DIR")
790 )));
791 cli_args.layout = Some(PathBuf::from(format!(
792 "{}/src/test-fixtures/layout-with-ui-config.kdl",
793 env!("CARGO_MANIFEST_DIR")
794 )));
795 let (config, _layout_info, _options, _, _) = Setup::from_cli_args(&cli_args).unwrap();
796 assert_snapshot!(format!("{:#?}", config));
797 }
798 #[test]
799 fn layout_themes_override_config_themes() {
800 let mut cli_args = CliArgs::default();
801 cli_args.config = Some(PathBuf::from(format!(
802 "{}/src/test-fixtures/config-with-themes-config.kdl",
803 env!("CARGO_MANIFEST_DIR")
804 )));
805 cli_args.layout = Some(PathBuf::from(format!(
806 "{}/src/test-fixtures/layout-with-themes-config.kdl",
807 env!("CARGO_MANIFEST_DIR")
808 )));
809 let (config, _layout_info, _options, _, _) = Setup::from_cli_args(&cli_args).unwrap();
810 assert_snapshot!(format!("{:#?}", config));
811 }
812 #[test]
813 fn layout_keybinds_override_config_keybinds() {
814 let mut cli_args = CliArgs::default();
815 cli_args.config = Some(PathBuf::from(format!(
816 "{}/src/test-fixtures/config-with-keybindings-config.kdl",
817 env!("CARGO_MANIFEST_DIR")
818 )));
819 cli_args.layout = Some(PathBuf::from(format!(
820 "{}/src/test-fixtures/layout-with-keybindings-config.kdl",
821 env!("CARGO_MANIFEST_DIR")
822 )));
823 let (config, _layout_info, _options, _, _) = Setup::from_cli_args(&cli_args).unwrap();
824 assert_snapshot!(format!("{:#?}", config));
825 }
826 #[test]
827 fn cli_config_dir_overrides_defaults() {
828 let config_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
829 .join("src")
830 .join("test-fixtures")
831 .join("config-dirs")
832 .join("layout-upside-down");
833 let cli_args = CliArgs {
834 config_dir: Some(config_dir.clone()),
835 ..Default::default()
836 };
837 let (_, layout_info, _, _, _) = Setup::from_cli_args(&cli_args).unwrap();
838 let Some(LayoutInfo::File(layout_path, _)) = layout_info else {
839 panic!("layout info has unexpected format");
840 };
841 let expected = config_dir
842 .join("layouts")
843 .join("upside-down.kdl")
844 .canonicalize()
845 .unwrap();
846 assert_eq!(layout_path, expected.display().to_string());
847 }
848 #[test]
849 fn cli_config_dir_finds_custom_default() {
850 let config_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
851 .join("src")
852 .join("test-fixtures")
853 .join("config-dirs")
854 .join("custom-default-layout");
855 let cli_args = CliArgs {
856 config_dir: Some(config_dir.clone()),
857 ..Default::default()
858 };
859 let (_, layout_info, _, _, _) = Setup::from_cli_args(&cli_args).unwrap();
860 let Some(LayoutInfo::File(layout_path, _)) = layout_info else {
861 panic!("layout info has unexpected format");
862 };
863 let expected = config_dir
864 .join("layouts")
865 .join("default.kdl")
866 .canonicalize()
867 .unwrap();
868 assert_eq!(layout_path, expected.display().to_string());
869 }
870
871 #[test]
872 fn cli_with_relative_layout_and_extension() {
873 let cwd = std::env::current_dir().unwrap();
876 assert_eq!(cwd, PathBuf::from(env!("CARGO_MANIFEST_DIR")));
877
878 let cli_args = CliArgs {
879 layout: Some(PathBuf::from("assets/layouts/compact.kdl")),
880 ..Default::default()
881 };
882 let (_, layout_info, _, _, _) = Setup::from_cli_args(&cli_args).unwrap();
883 let Some(LayoutInfo::File(layout_path, _)) = layout_info else {
884 panic!("layout info has unexpected format: {:?}", &layout_info);
885 };
886 let expected = cwd.join("assets/layouts/compact.kdl");
887 assert_eq!(layout_path, expected.display().to_string());
888 }
889
890 #[test]
891 fn cli_with_relative_layout_and_separator() {
892 let cwd = std::env::current_dir().unwrap();
895 assert_eq!(cwd, PathBuf::from(env!("CARGO_MANIFEST_DIR")));
896
897 let cli_args = CliArgs {
898 layout: Some(PathBuf::from("assets/layouts/compact")),
899 ..Default::default()
900 };
901 let (_, layout_info, _, _, _) = Setup::from_cli_args(&cli_args).unwrap();
902 let Some(LayoutInfo::File(layout_path, _)) = layout_info else {
903 panic!("layout info has unexpected format");
904 };
905 let expected = cwd.join("assets/layouts/compact");
906 assert_eq!(layout_path, expected.display().to_string());
907 }
908
909 #[test]
910 fn layout_string_cli_argument() {
911 let layout_kdl = "layout {\n pane\n pane\n}\n".to_string();
912 let cli_args = CliArgs {
913 layout_string: Some(layout_kdl.clone()),
914 ..Default::default()
915 };
916 let (_, layout_info, _, _, _) = Setup::from_cli_args(&cli_args).unwrap();
917 let Some(LayoutInfo::Stringified(content)) = layout_info else {
918 panic!(
919 "layout info should be Stringified variant, got: {:#?}",
920 layout_info
921 );
922 };
923 assert_eq!(content, layout_kdl);
924 }
925}