@@ -17,7 +17,7 @@ use errors::*;
17use line_range::LineRange;
17use line_range::LineRange;
18use style::{OutputComponent, OutputComponents, OutputWrap};
18use style::{OutputComponent, OutputComponents, OutputWrap};
20−#[derive(Debug, Clone, Copy)]
20+#[derive(Debug, Clone, Copy, PartialEq)]
21pub enum PagingMode {
21pub enum PagingMode {
23 QuitIfOneScreen,
23 QuitIfOneScreen,
@@ -42,6 +42,10 @@ pub struct Config<'a> {
42 /// The character width of the terminal
42 /// The character width of the terminal
43 pub term_width: usize,
43 pub term_width: usize,
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,
45 /// Whether or not to simply loop through all input (`cat` mode)
49 /// Whether or not to simply loop through all input (`cat` mode)
46 pub loop_through: bool,
50 pub loop_through: bool,
@@ -131,7 +135,7 @@ impl App {
131 to read from standard input.",
135 to read from standard input.",
132 ).multiple(true)
136 ).multiple(true)
133 .empty_values(false),
137 .empty_values(false),
136 Arg::with_name("language")
140 Arg::with_name("language")
137 .short("l")
141 .short("l")
@@ -145,196 +149,210 @@ impl App {
145 language names and file extensions."
149 language names and file extensions."
146 ).takes_value(true),
150 ).takes_value(true),
148− Arg::with_name("list-languages")
152+ Arg::with_name("list-languages")
149− .long("list-languages")
153+ .long("list-languages")
150− .conflicts_with("list-themes")
154+ .conflicts_with("list-themes")
151− .help("Display all supported languages.")
155+ .help("Display all supported languages.")
152− .long_help("Display a list of supported languages for syntax highlighting."),
156+ .long_help("Display a list of supported languages for syntax highlighting."),
154− Arg::with_name("theme")
158+ Arg::with_name("theme")
155− .long("theme")
159+ .long("theme")
156− .overrides_with("theme")
160+ .overrides_with("theme")
157− .takes_value(true)
161+ .takes_value(true)
158− .help("Set the color theme for syntax highlighting.")
162+ .help("Set the color theme for syntax highlighting.")
159− .long_help(
163+ .long_help(
160− "Set the theme for syntax highlighting. Use '--list-themes' to \
164+ "Set the theme for syntax highlighting. Use '--list-themes' to \
161 see all available themes. To set a default theme, export the \
165 see all available themes. To set a default theme, export the \
162 BAT_THEME environment variable (e.g.: export \
166 BAT_THEME environment variable (e.g.: export \
163 BAT_THEME=\"TwoDark\").",
167 BAT_THEME=\"TwoDark\").",
166− Arg::with_name("list-themes")
170+ Arg::with_name("list-themes")
167− .long("list-themes")
171+ .long("list-themes")
168− .help("Display all supported highlighting themes.")
172+ .help("Display all supported highlighting themes.")
169− .long_help("Display a list of supported themes for syntax highlighting."),
173+ .long_help("Display a list of supported themes for syntax highlighting."),
171− Arg::with_name("style")
175+ Arg::with_name("style")
172− .long("style")
176+ .long("style")
173− .value_name("style-components")
177+ .value_name("style-components")
174− .use_delimiter(true)
178+ .use_delimiter(true)
175− .takes_value(true)
179+ .takes_value(true)
176− .possible_values(&[
180+ .possible_values(&[
177− "auto", "full", "plain", "changes", "header", "grid", "numbers",
181+ "auto", "full", "plain", "changes", "header", "grid", "numbers",
179− .help("Comma-separated list of style elements to display.")
183+ .help("Comma-separated list of style elements to display.")
180− .long_help(
184+ .long_help(
181− "Configure which elements (line numbers, file headers, grid \
185+ "Configure which elements (line numbers, file headers, grid \
182 borders, Git modifications, ..) to display in addition to the \
186 borders, Git modifications, ..) to display in addition to the \
183 file contents. The argument is a comma-separated list of \
187 file contents. The argument is a comma-separated list of \
184 components to display (e.g. 'numbers,changes,grid') or a \
188 components to display (e.g. 'numbers,changes,grid') or a \
185 pre-defined style ('full'). To set a default theme, export the \
189 pre-defined style ('full'). To set a default theme, export the \
186 BAT_STYLE environment variable (e.g.: export BAT_STYLE=\"numbers\").",
190 BAT_STYLE environment variable (e.g.: export BAT_STYLE=\"numbers\").",
189− Arg::with_name("plain")
193+ Arg::with_name("plain")
190− .overrides_with("plain")
194+ .overrides_with("plain")
191− .short("p")
195+ .short("p")
192− .long("plain")
196+ .long("plain")
193− .conflicts_with("style")
197+ .conflicts_with("style")
194− .conflicts_with("number")
198+ .conflicts_with("number")
195− .help("Show plain style (alias for '--style=plain').")
199+ .help("Show plain style (alias for '--style=plain').")
196− .long_help(
200+ .long_help(
197− "Only show plain style, no decorations. This is an alias for \
201+ "Only show plain style, no decorations. This is an alias for \
198 '--style=plain'",
202 '--style=plain'",
201− Arg::with_name("number")
205+ Arg::with_name("number")
202− .long("number")
206+ .long("number")
203− .overrides_with("number")
207+ .overrides_with("number")
204− .short("n")
208+ .short("n")
205− .conflicts_with("style")
209+ .conflicts_with("style")
206− .help("Show line numbers (alias for '--style=numbers').")
210+ .help("Show line numbers (alias for '--style=numbers').")
207− .long_help(
211+ .long_help(
208− "Only show line numbers, no other decorations. This is an alias for \
212+ "Only show line numbers, no other decorations. This is an alias for \
209 '--style=numbers'",
213 '--style=numbers'",
212− Arg::with_name("line-range")
216+ Arg::with_name("line-range")
213− .long("line-range")
217+ .long("line-range")
214− .overrides_with("line-range")
218+ .overrides_with("line-range")
215− .takes_value(true)
219+ .takes_value(true)
216− .value_name("N:M")
220+ .value_name("N:M")
217− .help("Only print the lines from N to M.")
221+ .help("Only print the lines from N to M.")
218− .long_help(
222+ .long_help(
219− "Only print the specified range of lines for each file. \
223+ "Only print the specified range of lines for each file. \
220 For example:\n \
224 For example:\n \
221 '--line-range 30:40' prints lines 30 to 40\n \
225 '--line-range 30:40' prints lines 30 to 40\n \
222 '--line-range :40' prints lines 1 to 40\n \
226 '--line-range :40' prints lines 1 to 40\n \
223 '--line-range 40:' prints lines 40 to the end of the file",
227 '--line-range 40:' prints lines 40 to the end of the file",
226− Arg::with_name("color")
230+ Arg::with_name("color")
227− .long("color")
231+ .long("color")
228− .overrides_with("color")
232+ .overrides_with("color")
229− .takes_value(true)
233+ .takes_value(true)
230− .value_name("when")
234+ .value_name("when")
231− .possible_values(&["auto", "never", "always"])
235+ .possible_values(&["auto", "never", "always"])
232− .default_value("auto")
236+ .default_value("auto")
233− .help("When to use colors.")
237+ .help("When to use colors.")
234− .long_help("Specify when to use colored output. The automatic mode \
238+ .long_help("Specify when to use colored output. The automatic mode \
235 only enables colors if an interactive terminal is detected."),
239 only enables colors if an interactive terminal is detected."),
237− Arg::with_name("decorations")
241+ Arg::with_name("decorations")
238− .long("decorations")
242+ .long("decorations")
239− .overrides_with("decorations")
243+ .overrides_with("decorations")
240− .takes_value(true)
244+ .takes_value(true)
241− .value_name("when")
245+ .value_name("when")
242− .possible_values(&["auto", "never", "always"])
246+ .possible_values(&["auto", "never", "always"])
243− .default_value("auto")
247+ .default_value("auto")
244− .help("When to show the decorations specified by '--style'.")
248+ .help("When to show the decorations specified by '--style'.")
245− .long_help("Specify when to use the decorations that have been specified \
249+ .long_help("Specify when to use the decorations that have been specified \
246 via '--style'. The automatic mode only enables decorations if \
250 via '--style'. The automatic mode only enables decorations if \
247 an interactive terminal is detected."),
251 an interactive terminal is detected."),
249− Arg::with_name("paging")
253+ Arg::with_name("paging")
250− .long("paging")
254+ .long("paging")
251− .overrides_with("paging")
255+ .overrides_with("paging")
252− .takes_value(true)
256+ .takes_value(true)
253− .value_name("when")
257+ .value_name("when")
254− .possible_values(&["auto", "never", "always"])
258+ .possible_values(&["auto", "never", "always"])
255− .default_value("auto")
259+ .default_value("auto")
256− .help("Specify when to use the pager.")
260+ .help("Specify when to use the pager.")
257− .long_help("Specify when to use the pager. To control which pager \
261+ .long_help("Specify when to use the pager. To control which pager \
258 is used, set the PAGER or BAT_PAGER environment \
262 is used, set the PAGER or BAT_PAGER environment \
259 variables (the latter takes precedence). The default \
263 variables (the latter takes precedence). The default \
260 pager is 'less'. To disable the pager permanently, set \
264 pager is 'less'. To disable the pager permanently, set \
261 BAT_PAGER to an empty string."),
265 BAT_PAGER to an empty string."),
267+ Arg::with_name("wrap")
269+ .overrides_with("wrap")
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."),
277+ Arg::with_name("unbuffered")
279+ .hidden_short_help(true)
281+ "This option exists for POSIX-compliance reasons ('u' is for \
282+ 'unbuffered'). The output is always unbuffered - this option \
283+ is simply ignored.",
286+ Arg::with_name("tabs")
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>
296+ .help("Sets the tab width.")
297+ .long_help("Sets the tab width. Use a width of 0 to pass tabs through \
300+ Arg::with_name("terminal-width")
301+ .long("terminal-width")
303+ .value_name("width")
305+ .help("Set the width of the terminal"),
307+ SubCommand::with_name("cache")
308+ .about("Modify the syntax-definition and theme cache")
310+ Arg::with_name("init")
313+ .help("Initialize the syntax/theme cache.")
315+ "Initialize the syntax/theme cache by loading from the \
316+ source directory (default: the configuration directory).",
319+ Arg::with_name("clear")
322+ .help("Remove the cached syntax definitions and themes."),
324+ Arg::with_name("config-dir")
327+ .help("Show bat's configuration directory."),
329+ ArgGroup::with_name("cache-actions")
330+ .args(&["init", "clear", "config-dir"])
331+ .required(arg_group_required),
263− Arg::with_name("wrap")
333+ Arg::with_name("source")
264− .long("wrap")
334+ .long("source")
265− .overrides_with("wrap")
335+ .requires("init")
266 .takes_value(true)
336 .takes_value(true)
267− .value_name("mode")
337+ .value_name("dir")
268− .possible_values(&["auto", "never", "character"])
338+ .help("Use a different directory to load syntaxes and themes from."),
269− .default_value("auto")
270− .help("Specify the text-wrapping mode.")
271− .long_help("Specify the text-wrapping mode."),
273− Arg::with_name("unbuffered")
340+ Arg::with_name("target")
274− .short("u")
341+ .long("target")
275− .hidden_short_help(true)
342+ .requires("init")
276− .long_help(
343+ .takes_value(true)
277− "This option exists for POSIX-compliance reasons ('u' is for \
344+ .value_name("dir")
278− 'unbuffered'). The output is always unbuffered - this option \
345+ .help(
279− is simply ignored.",
346+ "Use a different directory to store the cached syntax and theme set.",
282− Arg::with_name("terminal-width")
349+ Arg::with_name("blank")
283− .long("terminal-width")
350+ .long("blank")
285− .value_name("width")
287− .help("Set the width of the terminal"),
289− SubCommand::with_name("cache")
290− .about("Modify the syntax-definition and theme cache")
292− Arg::with_name("init")
295− .help("Initialize the syntax/theme cache.")
297− "Initialize the syntax/theme cache by loading from the \
298− source directory (default: the configuration directory).",
301− Arg::with_name("clear")
304− .help("Remove the cached syntax definitions and themes."),
306− Arg::with_name("config-dir")
309− .help("Show bat's configuration directory."),
311− ArgGroup::with_name("cache-actions")
312− .args(&["init", "clear", "config-dir"])
313− .required(arg_group_required),
315− Arg::with_name("source")
317− .requires("init")
351+ .requires("init")
320− .help("Use a different directory to load syntaxes and themes from."),
322− Arg::with_name("target")
328− "Use a different directory to store the cached syntax and theme set.",
331− Arg::with_name("blank")
334− .help("Create completely new syntax and theme sets \
352+ .help("Create completely new syntax and theme sets \
335 (instead of appending to the default sets).")
353 (instead of appending to the default sets).")
337− ).help_message("Print this help message.")
355+ ).help_message("Print this help message.")
338 .version_message("Show version information.")
356 .version_message("Show version information.")
339 .get_matches()
357 .get_matches()
@@ -343,6 +361,27 @@ impl App {
343 let files = self.files();
361 let files = self.files();
344 let output_components = self.output_components()?;
362 let output_components = self.output_components()?;
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
371+ if self.interactive_output && !atty::is(Stream::Stdin) {
372+ PagingMode::QuitIfOneScreen
377+ if self.interactive_output {
378+ PagingMode::QuitIfOneScreen
346 Ok(Config {
385 Ok(Config {
347 true_color: is_truecolor_terminal(),
386 true_color: is_truecolor_terminal(),
348 language: self.matches.value_of("language"),
387 language: self.matches.value_of("language"),
@@ -366,26 +405,7 @@ impl App {
366 Some("never") => false,
405 Some("never") => false,
367 Some("auto") | _ => self.interactive_output,
406 Some("auto") | _ => self.interactive_output,
369− paging_mode: match self.matches.value_of("paging") {
408+ paging_mode,
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
376− if self.interactive_output && !atty::is(Stream::Stdin) {
377− PagingMode::QuitIfOneScreen
382− if self.interactive_output {
383− PagingMode::QuitIfOneScreen
389 term_width: self
409 term_width: self
391 .value_of("terminal-width")
411 .value_of("terminal-width")
@@ -395,6 +415,19 @@ impl App {
395 || self.matches.value_of("color") == Some("always")
415 || self.matches.value_of("color") == Some("always")
396 || self.matches.value_of("decorations") == Some("always")),
416 || self.matches.value_of("decorations") == Some("always")),
422+ .or_else(|| env::var("BAT_TABS").ok())
423+ .and_then(|t| t.parse().ok())
425+ if output_components.plain() && paging_mode == PagingMode::Never {
398 theme: self
431 theme: self
400 .value_of("theme")
433 .value_of("theme")