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, IntoApp};
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 min_values = 0,
275 max_values = 1
276 )]
277 pub dump_plugins: Option<Option<PathBuf>>,
278
279 #[clap(long, value_name = "SHELL", value_parser)]
281 pub generate_completion: Option<String>,
282
283 #[clap(long, value_name = "SHELL", value_parser)]
285 pub generate_auto_start: Option<String>,
286}
287
288impl Setup {
289 pub fn from_cli_args(
298 cli_args: &CliArgs,
299 ) -> Result<(Config, Option<LayoutInfo>, Options, Config, Options), ConfigError> {
300 Setup::handle_setup_commands(cli_args);
302 let config = Config::try_from(cli_args)?;
303 let cli_config_options: Option<Options> =
304 if let Some(Command::Options(options)) = cli_args.command.clone() {
305 Some(options.into())
306 } else {
307 None
308 };
309
310 let cli_config_options = merge_attach_command_options(cli_config_options, &cli_args);
313
314 let mut config_without_layout = config.clone();
315 let (layout_info, mut config) =
316 Setup::parse_layout_and_override_config(cli_config_options.as_ref(), config, cli_args)?;
317
318 let config_options =
319 apply_themes_to_config(&mut config, cli_config_options.clone(), cli_args)?;
320 let config_options_without_layout =
321 apply_themes_to_config(&mut config_without_layout, cli_config_options, cli_args)?;
322 fn apply_themes_to_config(
323 config: &mut Config,
324 cli_config_options: Option<Options>,
325 cli_args: &CliArgs,
326 ) -> Result<Options, ConfigError> {
327 let config_options = match cli_config_options {
328 Some(cli_config_options) => config.options.merge(cli_config_options),
329 None => config.options.clone(),
330 };
331
332 config.themes = config.themes.merge(get_default_themes());
333
334 let user_theme_dir = config_options.theme_dir.clone().or_else(|| {
335 get_theme_dir(cli_args.config_dir.clone().or_else(find_default_config_dir))
336 .filter(|dir| dir.exists())
337 });
338 if let Some(user_theme_dir) = user_theme_dir {
339 config.themes = config.themes.merge(Themes::from_dir(user_theme_dir)?);
340 }
341 Ok(config_options)
342 }
343
344 if let Some(Command::Setup(ref setup)) = &cli_args.command {
345 setup
346 .from_cli_with_options(cli_args, &config_options)
347 .map_or_else(
348 |e| {
349 eprintln!("{:?}", e);
350 process::exit(1);
351 },
352 |_| {},
353 );
354 };
355 Ok((
356 config,
357 layout_info,
358 config_options,
359 config_without_layout,
360 config_options_without_layout,
361 ))
362 }
363
364 pub fn from_cli(&self) -> Result<()> {
366 if self.clean {
367 return Ok(());
368 }
369
370 if self.dump_config {
371 dump_default_config()?;
372 std::process::exit(0);
373 }
374
375 if let Some(shell) = &self.generate_completion {
376 Self::generate_completion(shell);
377 std::process::exit(0);
378 }
379
380 if let Some(shell) = &self.generate_auto_start {
381 Self::generate_auto_start(shell);
382 std::process::exit(0);
383 }
384
385 if let Some(layout) = &self.dump_layout {
386 dump_specified_layout(&layout)?;
387 std::process::exit(0);
388 }
389
390 if let Some(swap_layout) = &self.dump_swap_layout {
391 dump_specified_swap_layout(swap_layout)?;
392 std::process::exit(0);
393 }
394
395 Ok(())
396 }
397
398 pub fn from_cli_with_options(&self, opts: &CliArgs, config_options: &Options) -> Result<()> {
400 if self.check {
401 Setup::check_defaults_config(opts, config_options)?;
402 std::process::exit(0);
403 }
404
405 if let Some(maybe_path) = &self.dump_plugins {
406 let data_dir = &opts.data_dir.clone().unwrap_or_else(get_default_data_dir);
407 let dir = match maybe_path {
408 Some(path) => path,
409 None => data_dir,
410 };
411
412 println!("Dumping plugins to '{}'", dir.display());
413 dump_builtin_plugins(&dir)?;
414 std::process::exit(0);
415 }
416
417 Ok(())
418 }
419
420 pub fn check_defaults_config(opts: &CliArgs, config_options: &Options) -> std::io::Result<()> {
421 let data_dir = opts.data_dir.clone().unwrap_or_else(get_default_data_dir);
422 let config_dir = opts.config_dir.clone().or_else(find_default_config_dir);
423 let plugin_dir = data_dir.join("plugins");
424 let layout_dir = config_options
425 .layout_dir
426 .clone()
427 .or_else(|| get_layout_dir(config_dir.clone()));
428 let system_data_dir = system_data_dir();
429 let config_file = opts
430 .config
431 .clone()
432 .or_else(|| config_dir.clone().map(|p| p.join(CONFIG_NAME)));
433
434 let hyperlink_start = "\u{1b}]8;;";
437 let hyperlink_mid = "\u{1b}\\";
438 let hyperlink_end = "\u{1b}]8;;\u{1b}\\";
439
440 let mut message = String::new();
441
442 writeln!(&mut message, "[Version]: {:?}", VERSION).unwrap();
443 if let Some(config_dir) = config_dir {
444 writeln!(&mut message, "[CONFIG DIR]: \"{}\"", config_dir.display()).unwrap();
445 } else {
446 message.push_str("[CONFIG DIR]: Not Found\n");
447 let mut default_config_dirs = default_config_dirs()
448 .iter()
449 .filter_map(|p| p.clone())
450 .collect::<Vec<PathBuf>>();
451 default_config_dirs.dedup();
452 message.push_str(
453 " On your system zellij looks in the following config directories by default:\n",
454 );
455 for dir in default_config_dirs {
456 writeln!(&mut message, " \"{}\"", dir.display()).unwrap();
457 }
458 }
459 if let Some(config_file) = config_file {
460 writeln!(
461 &mut message,
462 "[LOOKING FOR CONFIG FILE FROM]: \"{}\"",
463 config_file.display()
464 )
465 .unwrap();
466 match Config::from_path(&config_file, None) {
467 Ok(_) => message.push_str("[CONFIG FILE]: Well defined.\n"),
468 Err(e) => writeln!(
469 &mut message,
470 "[CONFIG ERROR]: {}. \n By default, zellij loads default configuration",
471 e
472 )
473 .unwrap(),
474 }
475 } else {
476 message.push_str("[CONFIG FILE]: Not Found\n");
477 writeln!(
478 &mut message,
479 " By default zellij looks for a file called [{}] in the configuration directory",
480 CONFIG_NAME
481 )
482 .unwrap();
483 }
484 writeln!(&mut message, "[CACHE DIR]: {}", ZELLIJ_CACHE_DIR.display()).unwrap();
485 writeln!(&mut message, "[DATA DIR]: \"{}\"", data_dir.display()).unwrap();
486 writeln!(&mut message, "[PLUGIN DIR]: \"{}\"", plugin_dir.display()).unwrap();
487 if !cfg!(feature = "disable_automatic_asset_installation") {
488 writeln!(
489 &mut message,
490 " Builtin, default plugins will not be loaded from disk."
491 )
492 .unwrap();
493 writeln!(
494 &mut message,
495 " Create a custom layout if you require this behavior."
496 )
497 .unwrap();
498 }
499 if let Some(layout_dir) = layout_dir {
500 writeln!(&mut message, "[LAYOUT DIR]: \"{}\"", layout_dir.display()).unwrap();
501 } else {
502 message.push_str("[LAYOUT DIR]: Not Found\n");
503 }
504 writeln!(
505 &mut message,
506 "[SYSTEM DATA DIR]: \"{}\"",
507 system_data_dir.display()
508 )
509 .unwrap();
510
511 writeln!(&mut message, "[ARROW SEPARATOR]: {}", ARROW_SEPARATOR).unwrap();
512 message.push_str(" Is the [ARROW_SEPARATOR] displayed correctly?\n");
513 message.push_str(" If not you may want to either start zellij with a compatible mode: 'zellij options --simplified-ui true'\n");
514 let mut hyperlink_compat = String::new();
515 hyperlink_compat.push_str(hyperlink_start);
516 hyperlink_compat.push_str("https://zellij.dev/documentation/compatibility.html#the-status-bar-fonts-dont-render-correctly");
517 hyperlink_compat.push_str(hyperlink_mid);
518 hyperlink_compat.push_str("https://zellij.dev/documentation/compatibility.html#the-status-bar-fonts-dont-render-correctly");
519 hyperlink_compat.push_str(hyperlink_end);
520 write!(
521 &mut message,
522 " Or check the font that is in use:\n {}\n",
523 hyperlink_compat
524 )
525 .unwrap();
526 message.push_str("[MOUSE INTERACTION]: \n");
527 message.push_str(" Can be temporarily disabled through pressing the [SHIFT] key.\n");
528 message.push_str(" If that doesn't fix any issues consider to disable the mouse handling of zellij: 'zellij options --disable-mouse-mode'\n");
529
530 let default_editor = std::env::var("EDITOR")
531 .or_else(|_| std::env::var("VISUAL"))
532 .unwrap_or_else(|_| String::from("Not set, checked $EDITOR and $VISUAL"));
533 writeln!(&mut message, "[DEFAULT EDITOR]: {}", default_editor).unwrap();
534 writeln!(&mut message, "[FEATURES]: {:?}", FEATURES).unwrap();
535 let mut hyperlink = String::new();
536 hyperlink.push_str(hyperlink_start);
537 hyperlink.push_str("https://www.zellij.dev/documentation/");
538 hyperlink.push_str(hyperlink_mid);
539 hyperlink.push_str("zellij.dev/documentation");
540 hyperlink.push_str(hyperlink_end);
541 writeln!(&mut message, "[DOCUMENTATION]: {}", hyperlink).unwrap();
542 std::io::stdout().write_all(message.as_bytes())?;
545
546 Ok(())
547 }
548 fn generate_completion(shell: &str) {
549 let shell: Shell = match shell.to_lowercase().parse() {
550 Ok(shell) => shell,
551 _ => {
552 eprintln!("Unsupported shell: {}", shell);
553 std::process::exit(1);
554 },
555 };
556 let mut out = std::io::stdout();
557 clap_complete::generate(shell, &mut CliArgs::command(), "zellij", &mut out);
558 match shell {
560 Shell::Bash => {
561 let _ = out.write_all(BASH_EXTRA_COMPLETION);
562 },
563 Shell::Elvish => {},
564 Shell::Fish => {
565 let _ = out.write_all(FISH_EXTRA_COMPLETION);
566 },
567 Shell::PowerShell => {},
568 Shell::Zsh => {
569 let _ = out.write_all(ZSH_EXTRA_COMPLETION);
570 },
571 _ => {},
572 };
573 }
574
575 fn generate_auto_start(shell: &str) {
576 let shell: Shell = match shell.to_lowercase().parse() {
577 Ok(shell) => shell,
578 _ => {
579 eprintln!("Unsupported shell: {}", shell);
580 std::process::exit(1);
581 },
582 };
583
584 let mut out = std::io::stdout();
585 match shell {
586 Shell::Bash => {
587 let _ = out.write_all(BASH_AUTO_START_SCRIPT);
588 },
589 Shell::Fish => {
590 let _ = out.write_all(FISH_AUTO_START_SCRIPT);
591 },
592 Shell::Zsh => {
593 let _ = out.write_all(ZSH_AUTO_START_SCRIPT);
594 },
595 _ => {},
596 }
597 }
598 fn parse_layout_and_override_config(
599 cli_config_options: Option<&Options>,
600 config: Config,
601 cli_args: &CliArgs,
602 ) -> Result<(Option<LayoutInfo>, Config), ConfigError> {
603 let layout_dir = cli_config_options
605 .as_ref()
606 .and_then(|cli_options| cli_options.layout_dir.clone())
607 .or_else(|| config.options.layout_dir.clone())
608 .or_else(|| get_layout_dir(cli_args.config_dir.clone()))
609 .or_else(|| get_layout_dir(find_default_config_dir()))
610 .map(|d| d.canonicalize().unwrap_or(d));
612 let (layout_info, chosen_layout) = if let Some(ref layout_string) = cli_args.layout_string {
616 (Some(LayoutInfo::Stringified(layout_string.clone())), None)
617 } else if let Some(chosen_layout) = cli_args.layout.clone() {
618 let layout_info = LayoutInfo::from_cli(
619 &layout_dir,
620 &Some(chosen_layout.clone()),
621 std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
622 );
623 (layout_info, Some(chosen_layout))
624 } else {
625 let chosen_layout = cli_config_options
626 .as_ref()
627 .and_then(|cli_options| cli_options.default_layout.clone())
628 .or_else(|| config.options.default_layout.clone());
629 let layout_info = LayoutInfo::from_config(&layout_dir, &chosen_layout);
630 (layout_info, chosen_layout)
631 };
632 match layout_info {
633 Some(LayoutInfo::Url(ref layout_url)) => {
634 Layout::from_url(layout_url, config).map(|(_layout, config)| (layout_info, config))
635 },
636 Some(LayoutInfo::Stringified(ref raw_layout)) => {
637 Layout::from_stringified_layout(raw_layout, config)
638 .map(|(_layout, config)| (layout_info, config))
639 },
640 _ => Layout::from_path_or_default(chosen_layout.as_ref(), layout_dir.clone(), config)
641 .map(|(_layout, config)| (layout_info, config)),
642 }
643 }
644 fn handle_setup_commands(cli_args: &CliArgs) {
645 if let Some(Command::Setup(ref setup)) = &cli_args.command {
646 setup.from_cli().map_or_else(
647 |e| {
648 eprintln!("{:?}", e);
649 process::exit(1);
650 },
651 |_| {},
652 );
653 };
654 }
655}
656
657fn merge_attach_command_options(
658 cli_config_options: Option<Options>,
659 cli_args: &CliArgs,
660) -> Option<Options> {
661 let cli_config_options = if let Some(Command::Sessions(Sessions::Attach { options, .. })) =
662 cli_args.command.clone()
663 {
664 match options.clone().as_deref() {
665 Some(SessionCommand::Options(options)) => match cli_config_options {
666 Some(cli_config_options) => {
667 Some(cli_config_options.merge_from_cli(options.to_owned().into()))
668 },
669 None => Some(options.to_owned().into()),
670 },
671 _ => cli_config_options,
672 }
673 } else {
674 cli_config_options
675 };
676 cli_config_options
677}
678
679#[cfg(test)]
680mod setup_test {
681 use super::Setup;
682 use crate::cli::{CliArgs, Command};
683 use crate::data::LayoutInfo;
684 use crate::input::options::Options;
685 use insta::assert_snapshot;
686 use std::path::PathBuf;
687
688 #[test]
689 fn default_config_with_no_cli_arguments() {
690 let cli_args = CliArgs::default();
691 let (config, layout_info, options, _, _) = Setup::from_cli_args(&cli_args).unwrap();
692 assert_snapshot!(format!("{:#?}", config));
693 assert_snapshot!(format!("{:#?}", layout_info));
694 assert_snapshot!(format!("{:#?}", options));
695 }
696 #[test]
697 fn cli_arguments_override_config_options() {
698 let mut cli_args = CliArgs::default();
699 cli_args.command = Some(Command::Options(Options {
700 simplified_ui: Some(true),
701 ..Default::default()
702 }));
703 let (_config, _layout_info, options, _, _) = Setup::from_cli_args(&cli_args).unwrap();
704 assert_snapshot!(format!("{:#?}", options));
705 }
706 #[test]
707 fn layout_options_override_config_options() {
708 let mut cli_args = CliArgs::default();
709 cli_args.layout = Some(PathBuf::from(format!(
710 "{}/src/test-fixtures/layout-with-options.kdl",
711 env!("CARGO_MANIFEST_DIR")
712 )));
713 let (_config, layout_info, options, _, _) = Setup::from_cli_args(&cli_args).unwrap();
714 assert_snapshot!(format!("{:#?}", options));
715 let Some(LayoutInfo::File(layout_path, _)) = layout_info else {
716 panic!("layout info doesn't have expected format");
717 };
718 assert_eq!(
719 layout_path,
720 format!(
721 "{}/src/test-fixtures/layout-with-options.kdl",
722 env!("CARGO_MANIFEST_DIR")
723 )
724 );
725 }
726 #[test]
727 fn cli_arguments_override_layout_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 cli_args.command = Some(Command::Options(Options {
734 pane_frames: Some(true),
735 ..Default::default()
736 }));
737 let (_config, layout_info, options, _, _) = Setup::from_cli_args(&cli_args).unwrap();
738 assert_snapshot!(format!("{:#?}", options));
739 let Some(LayoutInfo::File(layout_path, _)) = layout_info else {
740 panic!("layout info doesn't have expected format");
741 };
742 assert_eq!(
743 layout_path,
744 format!(
745 "{}/src/test-fixtures/layout-with-options.kdl",
746 env!("CARGO_MANIFEST_DIR")
747 )
748 );
749 }
750 #[test]
751 fn layout_env_vars_override_config_env_vars() {
752 let mut cli_args = CliArgs::default();
753 cli_args.config = Some(PathBuf::from(format!(
754 "{}/src/test-fixtures/config-with-env-vars.kdl",
755 env!("CARGO_MANIFEST_DIR")
756 )));
757 cli_args.layout = Some(PathBuf::from(format!(
758 "{}/src/test-fixtures/layout-with-env-vars.kdl",
759 env!("CARGO_MANIFEST_DIR")
760 )));
761 let (config, _layout_info, _options, _, _) = Setup::from_cli_args(&cli_args).unwrap();
762 assert_snapshot!(format!("{:#?}", config));
763 }
764 #[test]
765 fn layout_ui_config_overrides_config_ui_config() {
766 let mut cli_args = CliArgs::default();
767 cli_args.config = Some(PathBuf::from(format!(
768 "{}/src/test-fixtures/config-with-ui-config.kdl",
769 env!("CARGO_MANIFEST_DIR")
770 )));
771 cli_args.layout = Some(PathBuf::from(format!(
772 "{}/src/test-fixtures/layout-with-ui-config.kdl",
773 env!("CARGO_MANIFEST_DIR")
774 )));
775 let (config, _layout_info, _options, _, _) = Setup::from_cli_args(&cli_args).unwrap();
776 assert_snapshot!(format!("{:#?}", config));
777 }
778 #[test]
779 fn layout_themes_override_config_themes() {
780 let mut cli_args = CliArgs::default();
781 cli_args.config = Some(PathBuf::from(format!(
782 "{}/src/test-fixtures/config-with-themes-config.kdl",
783 env!("CARGO_MANIFEST_DIR")
784 )));
785 cli_args.layout = Some(PathBuf::from(format!(
786 "{}/src/test-fixtures/layout-with-themes-config.kdl",
787 env!("CARGO_MANIFEST_DIR")
788 )));
789 let (config, _layout_info, _options, _, _) = Setup::from_cli_args(&cli_args).unwrap();
790 assert_snapshot!(format!("{:#?}", config));
791 }
792 #[test]
793 fn layout_keybinds_override_config_keybinds() {
794 let mut cli_args = CliArgs::default();
795 cli_args.config = Some(PathBuf::from(format!(
796 "{}/src/test-fixtures/config-with-keybindings-config.kdl",
797 env!("CARGO_MANIFEST_DIR")
798 )));
799 cli_args.layout = Some(PathBuf::from(format!(
800 "{}/src/test-fixtures/layout-with-keybindings-config.kdl",
801 env!("CARGO_MANIFEST_DIR")
802 )));
803 let (config, _layout_info, _options, _, _) = Setup::from_cli_args(&cli_args).unwrap();
804 assert_snapshot!(format!("{:#?}", config));
805 }
806 #[test]
807 fn cli_config_dir_overrides_defaults() {
808 let config_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
809 .join("src")
810 .join("test-fixtures")
811 .join("config-dirs")
812 .join("layout-upside-down");
813 let cli_args = CliArgs {
814 config_dir: Some(config_dir.clone()),
815 ..Default::default()
816 };
817 let (_, layout_info, _, _, _) = Setup::from_cli_args(&cli_args).unwrap();
818 let Some(LayoutInfo::File(layout_path, _)) = layout_info else {
819 panic!("layout info has unexpected format");
820 };
821 let expected = config_dir
822 .join("layouts")
823 .join("upside-down.kdl")
824 .canonicalize()
825 .unwrap();
826 assert_eq!(layout_path, expected.display().to_string());
827 }
828 #[test]
829 fn cli_config_dir_finds_custom_default() {
830 let config_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
831 .join("src")
832 .join("test-fixtures")
833 .join("config-dirs")
834 .join("custom-default-layout");
835 let cli_args = CliArgs {
836 config_dir: Some(config_dir.clone()),
837 ..Default::default()
838 };
839 let (_, layout_info, _, _, _) = Setup::from_cli_args(&cli_args).unwrap();
840 let Some(LayoutInfo::File(layout_path, _)) = layout_info else {
841 panic!("layout info has unexpected format");
842 };
843 let expected = config_dir
844 .join("layouts")
845 .join("default.kdl")
846 .canonicalize()
847 .unwrap();
848 assert_eq!(layout_path, expected.display().to_string());
849 }
850
851 #[test]
852 fn cli_with_relative_layout_and_extension() {
853 let cwd = std::env::current_dir().unwrap();
856 assert_eq!(cwd, PathBuf::from(env!("CARGO_MANIFEST_DIR")));
857
858 let cli_args = CliArgs {
859 layout: Some(PathBuf::from("assets/layouts/compact.kdl")),
860 ..Default::default()
861 };
862 let (_, layout_info, _, _, _) = Setup::from_cli_args(&cli_args).unwrap();
863 let Some(LayoutInfo::File(layout_path, _)) = layout_info else {
864 panic!("layout info has unexpected format: {:?}", &layout_info);
865 };
866 let expected = cwd.join("assets/layouts/compact.kdl");
867 assert_eq!(layout_path, expected.display().to_string());
868 }
869
870 #[test]
871 fn cli_with_relative_layout_and_separator() {
872 let cwd = std::env::current_dir().unwrap();
875 assert_eq!(cwd, PathBuf::from(env!("CARGO_MANIFEST_DIR")));
876
877 let cli_args = CliArgs {
878 layout: Some(PathBuf::from("assets/layouts/compact")),
879 ..Default::default()
880 };
881 let (_, layout_info, _, _, _) = Setup::from_cli_args(&cli_args).unwrap();
882 let Some(LayoutInfo::File(layout_path, _)) = layout_info else {
883 panic!("layout info has unexpected format");
884 };
885 let expected = cwd.join("assets/layouts/compact");
886 assert_eq!(layout_path, expected.display().to_string());
887 }
888
889 #[test]
890 fn layout_string_cli_argument() {
891 let layout_kdl = "layout {\n pane\n pane\n}\n".to_string();
892 let cli_args = CliArgs {
893 layout_string: Some(layout_kdl.clone()),
894 ..Default::default()
895 };
896 let (_, layout_info, _, _, _) = Setup::from_cli_args(&cli_args).unwrap();
897 let Some(LayoutInfo::Stringified(content)) = layout_info else {
898 panic!(
899 "layout info should be Stringified variant, got: {:#?}",
900 layout_info
901 );
902 };
903 assert_eq!(content, layout_kdl);
904 }
905}