@@ -16,7 +16,7 @@ use errors::*;
1616use line_range::LineRange;
1717use style::{OutputComponent, OutputComponents, OutputWrap};
1818
19−#[derive(Debug, Clone, Copy)]
19+#[derive(Debug, Clone, Copy, PartialEq)]
2020pub enum PagingMode {
2121 Always,
2222 QuitIfOneScreen,
@@ -41,6 +41,10 @@ pub struct Config<'a> {
4141 /// The character width of the terminal
4242 pub term_width: usize,
4343
44+ /// The width of tab characters.
45+ /// Currently, a value of 0 will cause tabs to be passed through without expanding them.
46+ pub tab_width: usize,
47+
4448 /// Whether or not to simply loop through all input (`cat` mode)
4549 pub loop_through: bool,
4650
@@ -130,7 +134,7 @@ impl App {
130134 to read from standard input.",
131135 ).multiple(true)
132136 .empty_values(false),
133− )
137+ )
134138 .arg(
135139 Arg::with_name("language")
136140 .short("l")
@@ -144,195 +148,209 @@ impl App {
144148 language names and file extensions."
145149 ).takes_value(true),
146150 ).arg(
147− Arg::with_name("list-languages")
148− .long("list-languages")
149− .conflicts_with("list-themes")
150− .help("Display all supported languages.")
151− .long_help("Display a list of supported languages for syntax highlighting."),
152− ).arg(
153− Arg::with_name("theme")
154− .long("theme")
155− .overrides_with("theme")
156− .takes_value(true)
157− .help("Set the color theme for syntax highlighting.")
158− .long_help(
159− "Set the theme for syntax highlighting. Use '--list-themes' to \
151+ Arg::with_name("list-languages")
152+ .long("list-languages")
153+ .conflicts_with("list-themes")
154+ .help("Display all supported languages.")
155+ .long_help("Display a list of supported languages for syntax highlighting."),
156+ ).arg(
157+ Arg::with_name("theme")
158+ .long("theme")
159+ .overrides_with("theme")
160+ .takes_value(true)
161+ .help("Set the color theme for syntax highlighting.")
162+ .long_help(
163+ "Set the theme for syntax highlighting. Use '--list-themes' to \
160164 see all available themes. To set a default theme, export the \
161165 BAT_THEME environment variable (e.g.: export \
162166 BAT_THEME=\"TwoDark\").",
163− ),
164− ).arg(
165− Arg::with_name("list-themes")
166− .long("list-themes")
167− .help("Display all supported highlighting themes.")
168− .long_help("Display a list of supported themes for syntax highlighting."),
169− ).arg(
170− Arg::with_name("style")
171− .long("style")
172− .value_name("style-components")
173− .use_delimiter(true)
174− .takes_value(true)
175− .possible_values(&[
176− "auto", "full", "plain", "changes", "header", "grid", "numbers",
177− ]).default_value("auto")
178− .help("Comma-separated list of style elements to display.")
179− .long_help(
180− "Configure which elements (line numbers, file headers, grid \
167+ ),
168+ ).arg(
169+ Arg::with_name("list-themes")
170+ .long("list-themes")
171+ .help("Display all supported highlighting themes.")
172+ .long_help("Display a list of supported themes for syntax highlighting."),
173+ ).arg(
174+ Arg::with_name("style")
175+ .long("style")
176+ .value_name("style-components")
177+ .use_delimiter(true)
178+ .takes_value(true)
179+ .possible_values(&[
180+ "auto", "full", "plain", "changes", "header", "grid", "numbers",
181+ ]).default_value("auto")
182+ .help("Comma-separated list of style elements to display.")
183+ .long_help(
184+ "Configure which elements (line numbers, file headers, grid \
181185 borders, Git modifications, ..) to display in addition to the \
182186 file contents. The argument is a comma-separated list of \
183187 components to display (e.g. 'numbers,changes,grid') or a \
184188 pre-defined style ('full')",
185− ),
186− ).arg(
187− Arg::with_name("plain")
188− .overrides_with("plain")
189− .short("p")
190− .long("plain")
191− .conflicts_with("style")
192− .conflicts_with("number")
193− .help("Show plain style (alias for '--style=plain').")
194− .long_help(
195− "Only show plain style, no decorations. This is an alias for \
189+ ),
190+ ).arg(
191+ Arg::with_name("plain")
192+ .overrides_with("plain")
193+ .short("p")
194+ .long("plain")
195+ .conflicts_with("style")
196+ .conflicts_with("number")
197+ .help("Show plain style (alias for '--style=plain').")
198+ .long_help(
199+ "Only show plain style, no decorations. This is an alias for \
196200 '--style=plain'",
197− ),
198− ).arg(
199− Arg::with_name("number")
200− .long("number")
201− .overrides_with("number")
202− .short("n")
203− .conflicts_with("style")
204− .help("Show line numbers (alias for '--style=numbers').")
205− .long_help(
206− "Only show line numbers, no other decorations. This is an alias for \
201+ ),
202+ ).arg(
203+ Arg::with_name("number")
204+ .long("number")
205+ .overrides_with("number")
206+ .short("n")
207+ .conflicts_with("style")
208+ .help("Show line numbers (alias for '--style=numbers').")
209+ .long_help(
210+ "Only show line numbers, no other decorations. This is an alias for \
207211 '--style=numbers'",
208− ),
209− ).arg(
210− Arg::with_name("line-range")
211− .long("line-range")
212− .overrides_with("line-range")
213− .takes_value(true)
214− .value_name("N:M")
215− .help("Only print the lines from N to M.")
216− .long_help(
217− "Only print the specified range of lines for each file. \
212+ ),
213+ ).arg(
214+ Arg::with_name("line-range")
215+ .long("line-range")
216+ .overrides_with("line-range")
217+ .takes_value(true)
218+ .value_name("N:M")
219+ .help("Only print the lines from N to M.")
220+ .long_help(
221+ "Only print the specified range of lines for each file. \
218222 For example:\n \
219223 '--line-range 30:40' prints lines 30 to 40\n \
220224 '--line-range :40' prints lines 1 to 40\n \
221225 '--line-range 40:' prints lines 40 to the end of the file",
222− ),
223− ).arg(
224− Arg::with_name("color")
225− .long("color")
226− .overrides_with("color")
227− .takes_value(true)
228− .value_name("when")
229− .possible_values(&["auto", "never", "always"])
230− .default_value("auto")
231− .help("When to use colors.")
232− .long_help("Specify when to use colored output. The automatic mode \
226+ ),
227+ ).arg(
228+ Arg::with_name("color")
229+ .long("color")
230+ .overrides_with("color")
231+ .takes_value(true)
232+ .value_name("when")
233+ .possible_values(&["auto", "never", "always"])
234+ .default_value("auto")
235+ .help("When to use colors.")
236+ .long_help("Specify when to use colored output. The automatic mode \
233237 only enables colors if an interactive terminal is detected."),
234− ).arg(
235− Arg::with_name("decorations")
236− .long("decorations")
237− .overrides_with("decorations")
238− .takes_value(true)
239− .value_name("when")
240− .possible_values(&["auto", "never", "always"])
241− .default_value("auto")
242− .help("When to show the decorations specified by '--style'.")
243− .long_help("Specify when to use the decorations that have been specified \
238+ ).arg(
239+ Arg::with_name("decorations")
240+ .long("decorations")
241+ .overrides_with("decorations")
242+ .takes_value(true)
243+ .value_name("when")
244+ .possible_values(&["auto", "never", "always"])
245+ .default_value("auto")
246+ .help("When to show the decorations specified by '--style'.")
247+ .long_help("Specify when to use the decorations that have been specified \
244248 via '--style'. The automatic mode only enables decorations if \
245249 an interactive terminal is detected."),
246− ).arg(
247− Arg::with_name("paging")
248− .long("paging")
249− .overrides_with("paging")
250− .takes_value(true)
251− .value_name("when")
252− .possible_values(&["auto", "never", "always"])
253− .default_value("auto")
254− .help("Specify when to use the pager.")
255− .long_help("Specify when to use the pager. To control which pager \
250+ ).arg(
251+ Arg::with_name("paging")
252+ .long("paging")
253+ .overrides_with("paging")
254+ .takes_value(true)
255+ .value_name("when")
256+ .possible_values(&["auto", "never", "always"])
257+ .default_value("auto")
258+ .help("Specify when to use the pager.")
259+ .long_help("Specify when to use the pager. To control which pager \
256260 is used, set the PAGER or BAT_PAGER environment \
257261 variables (the latter takes precedence). The default \
258262 pager is 'less'. To disable the pager permanently, set \
259263 BAT_PAGER to an empty string."),
264+ ).arg(
265+ Arg::with_name("wrap")
266+ .long("wrap")
267+ .overrides_with("wrap")
268+ .takes_value(true)
269+ .value_name("mode")
270+ .possible_values(&["auto", "never", "character"])
271+ .default_value("auto")
272+ .help("Specify the text-wrapping mode.")
273+ .long_help("Specify the text-wrapping mode."),
274+ ).arg(
275+ Arg::with_name("unbuffered")
276+ .short("u")
277+ .hidden_short_help(true)
278+ .long_help(
279+ "This option exists for POSIX-compliance reasons ('u' is for \
280+ 'unbuffered'). The output is always unbuffered - this option \
281+ is simply ignored.",
282+ ),
283+ ).arg(
284+ Arg::with_name("tabs")
285+ .long("tabs")
286+ .takes_value(true)
287+ .value_name("tabs")
288+ .validator(
289+ |t| t.parse::<u32>()
290+ .map_err(|_t| "must be a number")
291+ .map(|_t| ()) // Convert to Result<(), &str>
292+ .map_err(|e| e.to_string()) // Convert to Result<(), String>
293+ )
294+ .help("Sets the tab width.")
295+ .long_help("Sets the tab width. Use a width of 0 to pass tabs through \
296+ directly"),
297+ ).arg(
298+ Arg::with_name("terminal-width")
299+ .long("terminal-width")
300+ .takes_value(true)
301+ .value_name("width")
302+ .hidden(true)
303+ .help("Set the width of the terminal"),
304+ ).subcommand(
305+ SubCommand::with_name("cache")
306+ .about("Modify the syntax-definition and theme cache")
307+ .arg(
308+ Arg::with_name("init")
309+ .long("init")
310+ .short("i")
311+ .help("Initialize the syntax/theme cache.")
312+ .long_help(
313+ "Initialize the syntax/theme cache by loading from the \
314+ source directory (default: the configuration directory).",
315+ ),
316+ ).arg(
317+ Arg::with_name("clear")
318+ .long("clear")
319+ .short("c")
320+ .help("Remove the cached syntax definitions and themes."),
321+ ).arg(
322+ Arg::with_name("config-dir")
323+ .long("config-dir")
324+ .short("d")
325+ .help("Show bat's configuration directory."),
326+ ).group(
327+ ArgGroup::with_name("cache-actions")
328+ .args(&["init", "clear", "config-dir"])
329+ .required(arg_group_required),
260330 ).arg(
261− Arg::with_name("wrap")
262− .long("wrap")
263− .overrides_with("wrap")
331+ Arg::with_name("source")
332+ .long("source")
333+ .requires("init")
264334 .takes_value(true)
265− .value_name("mode")
266− .possible_values(&["auto", "never", "character"])
267− .default_value("auto")
268− .help("Specify the text-wrapping mode.")
269− .long_help("Specify the text-wrapping mode."),
335+ .value_name("dir")
336+ .help("Use a different directory to load syntaxes and themes from."),
270337 ).arg(
271− Arg::with_name("unbuffered")
272− .short("u")
273− .hidden_short_help(true)
274− .long_help(
275− "This option exists for POSIX-compliance reasons ('u' is for \
276− 'unbuffered'). The output is always unbuffered - this option \
277− is simply ignored.",
338+ Arg::with_name("target")
339+ .long("target")
340+ .requires("init")
341+ .takes_value(true)
342+ .value_name("dir")
343+ .help(
344+ "Use a different directory to store the cached syntax and theme set.",
278345 ),
279346 ).arg(
280− Arg::with_name("terminal-width")
281− .long("terminal-width")
282− .takes_value(true)
283− .value_name("width")
284− .hidden(true)
285− .help("Set the width of the terminal"),
286− ).subcommand(
287− SubCommand::with_name("cache")
288− .about("Modify the syntax-definition and theme cache")
289− .arg(
290− Arg::with_name("init")
291− .long("init")
292− .short("i")
293− .help("Initialize the syntax/theme cache.")
294− .long_help(
295− "Initialize the syntax/theme cache by loading from the \
296− source directory (default: the configuration directory).",
297− ),
298− ).arg(
299− Arg::with_name("clear")
300− .long("clear")
301− .short("c")
302− .help("Remove the cached syntax definitions and themes."),
303− ).arg(
304− Arg::with_name("config-dir")
305− .long("config-dir")
306− .short("d")
307− .help("Show bat's configuration directory."),
308− ).group(
309− ArgGroup::with_name("cache-actions")
310− .args(&["init", "clear", "config-dir"])
311− .required(arg_group_required),
312− ).arg(
313− Arg::with_name("source")
314− .long("source")
315− .requires("init")
316− .takes_value(true)
317− .value_name("dir")
318− .help("Use a different directory to load syntaxes and themes from."),
319− ).arg(
320− Arg::with_name("target")
321− .long("target")
322− .requires("init")
323− .takes_value(true)
324− .value_name("dir")
325− .help(
326− "Use a different directory to store the cached syntax and theme set.",
327− ),
328− ).arg(
329− Arg::with_name("blank")
330− .long("blank")
331− .requires("init")
332− .help("Create completely new syntax and theme sets \
347+ Arg::with_name("blank")
348+ .long("blank")
349+ .requires("init")
350+ .help("Create completely new syntax and theme sets \
333351 (instead of appending to the default sets).")
334− ),
335− ).help_message("Print this help message.")
352+ ),
353+ ).help_message("Print this help message.")
336354 .version_message("Show version information.")
337355 .get_matches()
338356 }
@@ -341,6 +359,27 @@ impl App {
341359 let files = self.files();
342360 let output_components = self.output_components()?;
343361
362+ let paging_mode = match self.matches.value_of("paging") {
363+ Some("always") => PagingMode::Always,
364+ Some("never") => PagingMode::Never,
365+ Some("auto") | _ => if files.contains(&InputFile::StdIn) {
366+ // If we are reading from stdin, only enable paging if we write to an
367+ // interactive terminal and if we do not *read* from an interactive
368+ // terminal.
369+ if self.interactive_output && !atty::is(Stream::Stdin) {
370+ PagingMode::QuitIfOneScreen
371+ } else {
372+ PagingMode::Never
373+ }
374+ } else {
375+ if self.interactive_output {
376+ PagingMode::QuitIfOneScreen
377+ } else {
378+ PagingMode::Never
379+ }
380+ },
381+ };
382+
344383 Ok(Config {
345384 true_color: is_truecolor_terminal(),
346385 language: self.matches.value_of("language"),
@@ -364,26 +403,7 @@ impl App {
364403 Some("never") => false,
365404 Some("auto") | _ => self.interactive_output,
366405 },
367− paging_mode: match self.matches.value_of("paging") {
368− Some("always") => PagingMode::Always,
369− Some("never") => PagingMode::Never,
370− Some("auto") | _ => if files.contains(&InputFile::StdIn) {
371− // If we are reading from stdin, only enable paging if we write to an
372− // interactive terminal and if we do not *read* from an interactive
373− // terminal.
374− if self.interactive_output && !atty::is(Stream::Stdin) {
375− PagingMode::QuitIfOneScreen
376− } else {
377− PagingMode::Never
378− }
379− } else {
380− if self.interactive_output {
381− PagingMode::QuitIfOneScreen
382− } else {
383− PagingMode::Never
384− }
385− },
386− },
406+ paging_mode,
387407 term_width: self
388408 .matches
389409 .value_of("terminal-width")
@@ -393,6 +413,19 @@ impl App {
393413 || self.matches.value_of("color") == Some("always")
394414 || self.matches.value_of("decorations") == Some("always")),
395415 files,
416+ tab_width: self
417+ .matches
418+ .value_of("tabs")
419+ .map(String::from)
420+ .or_else(|| env::var("BAT_TABS").ok())
421+ .and_then(|t| t.parse().ok())
422+ .unwrap_or(
423+ if output_components.plain() && paging_mode == PagingMode::Never {
424+ 0
425+ } else {
426+ 8
427+ },
428+ ),
396429 theme: self
397430 .matches
398431 .value_of("theme")