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