1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
use clap::{Parser, ValueEnum};
use std::path::PathBuf;
// NOTE: Flag fields are kept in alphabetical order by their long-flag name
// (e.g. --color before --decorations before --diff). Short flags ride along
// wherever their long form lands. See CLAUDE.md "Conventions" for the rule.
#[derive(Parser, Debug, Clone)]
#[command(name = "batty", version, about = "A cat clone with syntax highlighting and Rhai support")]
pub struct Cli {
/// Files to display (use - for stdin)
pub files: Vec<PathBuf>,
/// When to use colors
#[arg(long, value_enum, default_value_t = ColorWhen::Auto)]
pub color: ColorWhen,
/// Decoration mode override
#[arg(long, value_enum, default_value_t = DecorationsWhen::Auto)]
pub decorations: DecorationsWhen,
/// Show git diff markers
#[arg(short = 'd', long, overrides_with = "diff")]
pub diff: bool,
/// Lines of context for diff
#[arg(long, default_value_t = 2)]
pub diff_context: usize,
/// Input file encoding. `auto` tries UTF-8 and falls back to ISO-8859-1
/// if UTF-8 decoding fails.
#[arg(long, value_enum, default_value_t = Encoding::Auto)]
pub encoding: Encoding,
/// Tail mode: show the last lines of the file and keep watching for new
/// content (like `tail -f`). Single file only; no stdin.
#[arg(short = 'f', long, overrides_with = "no_follow")]
pub follow: bool,
/// Show the left-side gutter (line numbers, diff markers, grid bar).
/// Cancels `--no-gutter` from config; otherwise inert (defers to --style).
#[arg(long, overrides_with = "no_gutter")]
pub gutter: bool,
/// Highlight specific line(s)
#[arg(short = 'H', long)]
pub highlight_line: Vec<usize>,
/// Enter interactive TUI mode (vim-style navigation: j/k, g/G, Ctrl-d/u, q to quit)
#[arg(short = 'i', long, overrides_with = "no_interactive")]
pub interactive: bool,
/// Set the language for syntax highlighting
#[arg(short = 'l', long)]
pub language: Option<String>,
/// Line numbering style
#[arg(long, value_enum, default_value_t = LineNumberStyle::Absolute)]
pub line_numbers: LineNumberStyle,
/// Display only specified line range, e.g. 10:20, :15, 30:
#[arg(long)]
pub line_range: Option<String>,
/// List supported languages and exit
#[arg(short = 'L', long, overrides_with = "list_languages")]
pub list_languages: bool,
/// List supported themes and exit
#[arg(long, overrides_with = "list_themes")]
pub list_themes: bool,
/// Render Markdown files instead of showing the raw source. Works for any
/// file but most useful for .md / .markdown.
#[arg(short = 'm', long, overrides_with = "no_markdown")]
pub markdown: bool,
/// Render markdown files (.md / .markdown / .mdown / .mkd) automatically,
/// but leave other files as raw highlighted source. Lower priority than
/// --markdown (force on for any file) and --no-markdown (force off).
#[arg(long, overrides_with = "markdown_on_extension")]
pub markdown_on_extension: bool,
/// Disable follow mode. Overrides `follow = true` in the config.
#[arg(long, overrides_with = "follow")]
pub no_follow: bool,
/// Hide the gutter (line numbers, diff markers, grid bar) regardless of
/// --style. Header / rule / snip stay on. A "cleaner reading" preset
/// that's less aggressive than --plain.
#[arg(long, overrides_with = "gutter")]
pub no_gutter: bool,
/// Disable interactive mode. Overrides `interactive = true` in the config file.
#[arg(long, overrides_with = "interactive")]
pub no_interactive: bool,
/// Disable Markdown rendering. Overrides `markdown = true` in the config.
#[arg(long, overrides_with = "markdown")]
pub no_markdown: bool,
/// Show line numbers (equivalent to --style=numbers)
#[arg(short = 'n', long, overrides_with = "number")]
pub number: bool,
/// When to use the pager. `never` also disables interactive mode (treats
/// the flag as a global "flat output" override).
#[arg(long, value_enum, default_value_t = PagingWhen::Auto)]
pub paging: PagingWhen,
/// Plain output (no decorations, equivalent to --style=plain)
#[arg(short = 'p', long, overrides_with = "plain")]
pub plain: bool,
/// Show non-printable characters
#[arg(short = 'A', long, overrides_with = "show_all")]
pub show_all: bool,
/// Style components, comma-separated: full, plain, numbers, grid, header, rule, changes, snip
#[arg(long, default_value = "full")]
pub style: String,
/// Tab width
#[arg(long, default_value_t = 4)]
pub tabs: usize,
/// Number of trailing lines to show in follow mode.
#[arg(long, default_value_t = 10)]
pub tail_lines: usize,
/// Set the color theme
#[arg(long)]
pub theme: Option<String>,
/// Reserve N rows at the top of the screen in interactive mode. Useful for
/// terminals like Warp that overlay UI on the alternate screen's top rows.
#[arg(long, default_value_t = 0)]
pub top_pad: u16,
/// Wrap mode
#[arg(long, value_enum, default_value_t = WrapMode::Auto)]
pub wrap: WrapMode,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum ColorWhen { Always, Auto, Never }
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum PagingWhen { Always, Auto, Never }
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum WrapMode { Never, Character, Auto }
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum DecorationsWhen { Always, Auto, Never }
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum LineNumberStyle { Absolute, Relative }
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum Encoding {
/// Try UTF-8 first; fall back to ISO-8859-1 on decode failure.
Auto,
/// Strict UTF-8. Errors out on invalid byte sequences.
#[value(name = "utf-8", alias = "utf8")]
Utf8,
/// ISO-8859-1 (Latin-1). Each byte maps to its matching Unicode code point.
#[value(name = "iso-8859-1", alias = "latin1", alias = "latin-1")]
Latin1,
}