1use crate::{
2 PagerConfig, run_pager,
3 util::{create_lscolors, create_map},
4};
5use nu_ansi_term::{Color, Style};
6use nu_color_config::{StyleComputer, get_color_map};
7use nu_engine::command_prelude::*;
8use nu_protocol::Config;
9
10#[derive(Clone)]
12pub struct Explore;
13
14impl Command for Explore {
15 fn name(&self) -> &str {
16 "explore"
17 }
18
19 fn description(&self) -> &str {
20 "Explore acts as a table pager, just like `less` does for text."
21 }
22
23 fn signature(&self) -> nu_protocol::Signature {
24 Signature::build("explore")
28 .input_output_types(vec![(Type::Any, Type::Any)])
29 .named(
30 "head",
31 SyntaxShape::Boolean,
32 "Show or hide column headers (default true)",
33 None,
34 )
35 .switch("index", "Show row indexes when viewing a list", Some('i'))
36 .switch(
37 "tail",
38 "Start with the viewport scrolled to the bottom",
39 Some('t'),
40 )
41 .switch(
42 "peek",
43 "When quitting, output the value of the cell the cursor was on",
44 Some('p'),
45 )
46 .category(Category::Viewers)
47 }
48
49 fn extra_description(&self) -> &str {
50 r#"Press `:` then `h` to get a help menu."#
51 }
52
53 fn run(
54 &self,
55 engine_state: &EngineState,
56 stack: &mut Stack,
57 call: &Call,
58 input: PipelineData,
59 ) -> Result<PipelineData, ShellError> {
60 let show_head: bool = call.get_flag(engine_state, stack, "head")?.unwrap_or(true);
61 let show_index: bool = call.has_flag(engine_state, stack, "index")?;
62 let tail: bool = call.has_flag(engine_state, stack, "tail")?;
63 let peek_value: bool = call.has_flag(engine_state, stack, "peek")?;
64
65 let nu_config = stack.get_config(engine_state);
66 let style_computer = StyleComputer::from_config(engine_state, stack);
67
68 let mut explore_config = ExploreConfig::from_nu_config(&nu_config);
69 explore_config.table.show_header = show_head;
70 explore_config.table.show_index = show_index;
71 explore_config.table.separator_style = lookup_color(&style_computer, "separator");
72
73 let lscolors = create_lscolors(engine_state, stack);
74 let cwd = engine_state.cwd(Some(stack)).map_or(String::new(), |path| {
75 path.to_str().unwrap_or("").to_string()
76 });
77
78 let config = PagerConfig::new(
79 &nu_config,
80 &explore_config,
81 &style_computer,
82 &lscolors,
83 peek_value,
84 tail,
85 &cwd,
86 );
87
88 let result = run_pager(engine_state, &mut stack.clone(), input, config);
89
90 match result {
91 Ok(Some(value)) => Ok(PipelineData::value(value, None)),
92 Ok(None) => Ok(PipelineData::value(Value::default(), None)),
93 Err(err) => {
94 let shell_error = match err.downcast::<ShellError>() {
95 Ok(e) => e,
96 Err(e) => ShellError::GenericError {
97 error: e.to_string(),
98 msg: "".into(),
99 span: None,
100 help: None,
101 inner: vec![],
102 },
103 };
104
105 Ok(PipelineData::value(
106 Value::error(shell_error, call.head),
107 None,
108 ))
109 }
110 }
111 }
112
113 fn examples(&self) -> Vec<Example<'_>> {
114 vec![
115 Example {
116 description: "Explore the system host information record",
117 example: r#"sys host | explore"#,
118 result: None,
119 },
120 Example {
121 description: "Explore the output of `ls` without column names",
122 example: r#"ls | explore --head false"#,
123 result: None,
124 },
125 Example {
126 description: "Explore a list of Markdown files' contents, with row indexes",
127 example: r#"glob *.md | each {|| open } | explore --index"#,
128 result: None,
129 },
130 Example {
131 description: "Explore a JSON file, then save the last visited sub-structure to a file",
132 example: r#"open file.json | explore --peek | to json | save part.json"#,
133 result: None,
134 },
135 ]
136 }
137}
138
139#[derive(Debug, Clone)]
140pub struct ExploreConfig {
141 pub table: TableConfig,
142 pub selected_cell: Style,
143 pub status_info: Style,
144 pub status_success: Style,
145 pub status_warn: Style,
146 pub status_error: Style,
147 pub status_bar_background: Style,
148 pub status_bar_text: Style,
149 pub cmd_bar_text: Style,
150 pub cmd_bar_background: Style,
151 pub highlight: Style,
152 pub try_reactive: bool,
154}
155
156impl Default for ExploreConfig {
157 fn default() -> Self {
158 Self {
159 table: TableConfig::default(),
160 selected_cell: color(None, Some(Color::LightBlue)),
161 status_info: color(None, None),
162 status_success: color(Some(Color::Black), Some(Color::Green)),
163 status_warn: color(None, None),
164 status_error: color(Some(Color::White), Some(Color::Red)),
165 status_bar_background: color(
166 Some(Color::Rgb(29, 31, 33)),
167 Some(Color::Rgb(196, 201, 198)),
168 ),
169 status_bar_text: color(None, None),
170 cmd_bar_text: color(Some(Color::Rgb(196, 201, 198)), None),
171 cmd_bar_background: color(None, None),
172 highlight: color(Some(Color::Black), Some(Color::Yellow)),
173 try_reactive: false,
174 }
175 }
176}
177impl ExploreConfig {
178 pub fn from_nu_config(config: &Config) -> Self {
180 let mut ret = Self::default();
181
182 ret.table.column_padding_left = config.table.padding.left;
183 ret.table.column_padding_right = config.table.padding.right;
184
185 let explore_cfg_hash_map = config.explore.clone();
186 let colors = get_color_map(&explore_cfg_hash_map);
187
188 if let Some(s) = colors.get("status_bar_text") {
189 ret.status_bar_text = *s;
190 }
191
192 if let Some(s) = colors.get("status_bar_background") {
193 ret.status_bar_background = *s;
194 }
195
196 if let Some(s) = colors.get("command_bar_text") {
197 ret.cmd_bar_text = *s;
198 }
199
200 if let Some(s) = colors.get("command_bar_background") {
201 ret.cmd_bar_background = *s;
202 }
203
204 if let Some(s) = colors.get("command_bar_background") {
205 ret.cmd_bar_background = *s;
206 }
207
208 if let Some(s) = colors.get("selected_cell") {
209 ret.selected_cell = *s;
210 }
211
212 if let Some(hm) = explore_cfg_hash_map.get("status").and_then(create_map) {
213 let colors = get_color_map(&hm);
214
215 if let Some(s) = colors.get("info") {
216 ret.status_info = *s;
217 }
218
219 if let Some(s) = colors.get("success") {
220 ret.status_success = *s;
221 }
222
223 if let Some(s) = colors.get("warn") {
224 ret.status_warn = *s;
225 }
226
227 if let Some(s) = colors.get("error") {
228 ret.status_error = *s;
229 }
230 }
231
232 if let Some(hm) = explore_cfg_hash_map.get("try").and_then(create_map)
233 && let Some(reactive) = hm.get("reactive")
234 && let Ok(b) = reactive.as_bool()
235 {
236 ret.try_reactive = b;
237 }
238
239 ret
240 }
241}
242
243#[derive(Debug, Default, Clone, Copy)]
244pub struct TableConfig {
245 pub separator_style: Style,
246 pub show_index: bool,
247 pub show_header: bool,
248 pub column_padding_left: usize,
249 pub column_padding_right: usize,
250}
251
252const fn color(foreground: Option<Color>, background: Option<Color>) -> Style {
253 Style {
254 background,
255 foreground,
256 is_blink: false,
257 is_bold: false,
258 is_dimmed: false,
259 is_hidden: false,
260 is_italic: false,
261 is_reverse: false,
262 is_strikethrough: false,
263 is_underline: false,
264 prefix_with_reset: false,
265 }
266}
267
268fn lookup_color(style_computer: &StyleComputer, key: &str) -> nu_ansi_term::Style {
269 style_computer.compute(key, &Value::nothing(Span::unknown()))
270}