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