1use anyhow::Result;
2use clap::Parser;
3
4use crate::cli::GardenOptions;
5use crate::{constants, display, model, query};
6
7#[derive(Parser, Clone, Debug)]
9#[command(author, about, long_about)]
10pub struct ListOptions {
11 #[arg(short, long, default_value_t = false)]
13 all: bool,
14 #[arg(long, short = 'C', default_value_t = false)]
16 no_commands: bool,
17 #[arg(long = "commands", short = 'c', action = clap::ArgAction::Count)]
19 commands: u8,
20 #[arg(long, short = 'N', default_value_t = false)]
22 no_gardens: bool,
23 #[arg(long, short = 'G', default_value_t = false)]
25 no_groups: bool,
26 #[arg(long, short = 'R', default_value_t = false)]
28 no_remotes: bool,
29 #[arg(short, long, default_value_t = false)]
31 reverse: bool,
32 #[arg(
34 long,
35 short,
36 require_equals = true,
37 num_args = 0..=1,
38 default_value_t = model::TreeSortMode::None,
39 default_missing_value = "name",
40 value_name = "MODE",
41 value_parser = model::TreeSortMode::parse_from_str,
42 )]
43 sort: model::TreeSortMode,
44 #[arg(short, long, action = clap::ArgAction::Count)]
46 verbose: u8,
47 #[arg(short, long, default_value_t = false)]
49 worktrees: bool,
50 #[arg(long, short, default_value = "*")]
52 trees: String,
53 queries: Vec<String>,
55}
56
57pub fn main(app_context: &model::ApplicationContext, options: &mut ListOptions) -> Result<()> {
59 if options.queries.is_empty() {
60 options.queries.push(string!("@*"));
61 }
62 list(app_context, options)
63}
64
65fn list(app_context: &model::ApplicationContext, options: &ListOptions) -> Result<()> {
67 let config = app_context.get_root_config();
68 let display_all = options.all;
69 let worktrees = options.worktrees;
70 let remotes = !options.no_remotes;
71 let show_commands = !options.no_commands;
72 let only_commands = options.commands > 0;
73 let show_gardens = !only_commands && !options.no_gardens;
74 let show_groups = !only_commands && !options.no_groups;
75 let verbose = app_context.options.verbose + options.verbose;
76 let mut needs_newline = false;
77 let mut display_options = display::DisplayOptions {
78 remotes,
79 verbose,
80 worktrees,
81 ..std::default::Default::default()
82 };
83
84 if app_context.options.debug_level(constants::DEBUG_LEVEL_LIST) > 0 {
85 debug!("queries: {:?}", options.queries);
86 }
87
88 for query in &options.queries {
89 let mut contexts =
91 query::resolve_and_filter_trees(app_context, config, query, &options.trees);
92 match options.sort {
93 model::TreeSortMode::None => (),
94 model::TreeSortMode::Name => {
95 contexts.sort_by(|context_a, context_b| context_a.tree.cmp(&context_b.tree));
96 }
97 model::TreeSortMode::Time => {
98 contexts.sort_by(|context_a, context_b| {
99 let config_a = match context_a.config {
100 Some(config_id) => app_context.get_config(config_id),
101 None => config,
102 };
103 let config_b = match context_b.config {
104 Some(config_id) => app_context.get_config(config_id),
105 None => config,
106 };
107 match (
108 config_a
109 .trees
110 .get(&context_a.tree)
111 .and_then(|tree| tree.pathbuf())
112 .and_then(|pathbuf| pathbuf.metadata().ok())
113 .and_then(|metadata| metadata.modified().ok()),
114 config_b
115 .trees
116 .get(&context_b.tree)
117 .and_then(|tree| tree.pathbuf())
118 .and_then(|pathbuf| pathbuf.metadata().ok())
119 .and_then(|metadata| metadata.modified().ok()),
120 ) {
121 (Some(a), Some(b)) => a.cmp(&b),
122 (None, Some(_)) => std::cmp::Ordering::Less,
123 (Some(_), None) => std::cmp::Ordering::Greater,
124 (None, None) => std::cmp::Ordering::Equal,
125 }
126 });
127 }
128 }
129 if options.reverse {
130 contexts.reverse();
131 }
132 for (idx, context) in contexts.iter().enumerate() {
134 let config = match context.config {
135 Some(config_id) => app_context.get_config(config_id),
136 None => config,
137 };
138 let tree = match config.trees.get(&context.tree) {
139 Some(tree) => tree,
140 None => continue,
141 };
142 let path = match tree.path_as_ref() {
143 Ok(path) => path,
144 Err(_) => continue,
145 };
146 if !std::path::PathBuf::from(&path).exists() {
148 if needs_newline {
149 println!();
150 }
151 display::print_missing_tree(tree, path, verbose);
152 if display_all {
153 if !only_commands {
154 display::print_tree_extended_details(
155 app_context,
156 context,
157 tree,
158 &display_options,
159 );
160 }
161 if show_commands && !tree.commands.is_empty() {
162 display::print_commands(
163 app_context,
164 context,
165 &tree.commands,
166 options.commands > 1,
167 );
168 }
169 }
170 needs_newline = display_all;
171 continue;
172 }
173
174 if tree.is_symlink {
175 if needs_newline {
176 println!();
177 }
178 display::print_symlink_tree_entry(tree, path, verbose);
179 needs_newline = false;
180 continue;
181 }
182
183 if idx > 0 {
184 println!();
185 }
186 display_options.branches = config.tree_branches;
187 display::print_tree(tree, &display_options);
188 if !only_commands {
189 display::print_tree_extended_details(app_context, context, tree, &display_options);
190 }
191 if show_commands && !tree.commands.is_empty() {
192 display::print_commands(app_context, context, &tree.commands, options.commands > 1);
193 }
194 needs_newline = true;
195 }
196 }
197
198 if show_groups && !config.groups.is_empty() {
199 println!();
200 display::print_groups(&config.groups);
201 }
202
203 if show_gardens && !config.gardens.is_empty() {
204 println!();
205 display::print_gardens(&config.gardens);
206 }
207
208 if show_commands && !config.commands.is_empty() {
209 println!();
210 let context = model::TreeContext {
211 tree: "".to_string(),
212 config: config.get_id(),
213 garden: None,
214 group: None,
215 };
216 display::print_commands(
217 app_context,
218 &context,
219 &config.commands,
220 options.commands > 1,
221 );
222 }
223
224 Ok(())
225}