1use clap::{CommandFactory, Parser};
11use clap_complete::generate;
12
13use crate::args::{Args, PagingMode};
14use crate::output::Output;
15use crate::{
16 create_resource_handler, process_file, Multiplexer, Settings, TerminalProgram, TerminalSize,
17 Theme,
18};
19use syntect::parsing::SyntaxSet;
20use tracing::{event, Level};
21use tracing_subscriber::filter::LevelFilter;
22use tracing_subscriber::EnvFilter;
23
24pub fn run() -> ! {
26 let filter = EnvFilter::builder()
27 .with_default_directive(LevelFilter::OFF.into())
28 .with_env_var("MDCAT_LOG")
29 .from_env_lossy();
30 tracing_subscriber::fmt::Subscriber::builder()
31 .pretty()
32 .with_env_filter(filter)
33 .with_writer(std::io::stderr)
34 .init();
35
36 let args = Args::parse().command;
37 event!(target: "mdcat::main", Level::TRACE, ?args, "mdcat arguments");
38
39 if let Some(shell) = args.completions {
40 let binary = match args {
41 crate::args::Command::Mdcat { .. } => "mdcat",
42 crate::args::Command::Mdless { .. } => "mdless",
43 };
44 let mut command = Args::command();
45 let subcommand = command.find_subcommand_mut(binary).unwrap();
46 generate(shell, subcommand, binary, &mut std::io::stdout());
47 std::process::exit(0);
48 }
49
50 let stdout_is_tty = std::io::IsTerminal::is_terminal(&std::io::stdout());
51 let paging_mode = args.paging_mode();
52
53 let terminal = if args.no_colour {
54 TerminalProgram::Dumb
55 } else if paging_mode.is_paginated() || args.ansi_only {
56 TerminalProgram::Ansi
57 } else if !stdout_is_tty {
58 TerminalProgram::Dumb
59 } else {
60 TerminalProgram::detect()
61 };
62
63 let multiplexer = Multiplexer::detect();
64
65 let probed = if !args.no_probe_terminal
66 && terminal == TerminalProgram::Ansi
67 && stdout_is_tty
68 && !paging_mode.is_paginated()
69 {
70 crate::terminal::probe_da1(std::time::Duration::from_millis(args.probe_timeout_ms))
71 } else {
72 None
73 };
74
75 if args.detect_and_exit {
76 println!("Terminal: {terminal}");
77 if multiplexer != Multiplexer::None {
78 println!("Multiplexer: {multiplexer:?}");
79 }
80 if let Some(attrs) = probed {
81 println!("Probed: sixel={}", attrs.sixel);
82 }
83 std::process::exit(0);
84 }
85
86 if paging_mode == PagingMode::Interactive {
87 std::process::exit(run_interactive_mdless(&args));
88 }
89
90 #[cfg(windows)]
91 anstyle_query::windows::enable_ansi_colors();
92
93 let base = TerminalSize::detect().unwrap_or_default();
96 let max_columns = args.columns.unwrap_or(if base.columns > 20 {
97 base.columns - 2
98 } else {
99 base.columns
100 });
101 let terminal_size = base.with_max_columns(max_columns);
102
103 let exit_code = match Output::new(paging_mode == PagingMode::ExternalLess) {
104 Ok(mut output) => {
105 #[cfg_attr(not(feature = "sixel"), allow(unused_mut))]
106 let mut capabilities = terminal.capabilities();
107 #[cfg(feature = "sixel")]
108 if probed.is_some_and(|attrs| attrs.sixel) && capabilities.image.is_none() {
109 use crate::terminal::capabilities::{sixel::SixelProtocol, ImageCapability};
110 capabilities.image = Some(ImageCapability::Sixel(SixelProtocol));
111 }
112 let settings = Settings {
113 terminal_capabilities: capabilities,
114 terminal_size,
115 multiplexer,
116 syntax_set: &SyntaxSet::load_defaults_newlines(),
117 theme: Theme::default(),
118 wrap_code: args.wrap_code,
119 };
120 event!(
121 target: "mdcat::main",
122 Level::TRACE,
123 ?settings.terminal_size,
124 ?settings.terminal_capabilities,
125 "settings"
126 );
127 let resource_handler = create_resource_handler(args.resource_access()).unwrap();
128 args.filenames
129 .iter()
130 .try_fold(0, |code, filename| {
131 process_file(
132 filename,
133 &settings,
134 args.resource_access(),
135 &resource_handler,
136 &mut output,
137 )
138 .map(|()| code)
139 .or_else(|error| {
140 eprintln!("Error: {filename}: {error}");
141 if args.fail_fast {
142 Err(error)
143 } else {
144 Ok(1)
145 }
146 })
147 })
148 .unwrap_or(1)
149 }
150 Err(error) => {
151 eprintln!("Error: {error:#}");
152 128
153 }
154 };
155 event!(target: "mdcat::main", Level::TRACE, "Exiting with final exit code {}", exit_code);
156 std::process::exit(exit_code);
157}
158
159fn run_interactive_mdless(args: &crate::args::Command) -> i32 {
164 let resource_handler =
165 create_resource_handler(args.resource_access()).expect("resource handler");
166 let filename = args.filenames.first().map_or("-", String::as_str);
167 if args.filenames.len() > 1 {
168 eprintln!(
169 "mdless: only the first file is shown interactively; {} more ignored",
170 args.filenames.len() - 1,
171 );
172 }
173 let opts = match args {
174 crate::args::Command::Mdless {
175 search,
176 case_sensitive,
177 regex,
178 line_numbers,
179 ..
180 } => crate::mdless::MdlessOptions {
181 initial: search.clone(),
182 case_sensitive: *case_sensitive,
183 regex: *regex,
184 line_numbers: *line_numbers,
185 },
186 crate::args::Command::Mdcat { .. } => crate::mdless::MdlessOptions::default(),
187 };
188 match crate::mdless::run(filename, args, opts, &resource_handler) {
189 Ok(code) => code,
190 Err(error) => {
191 eprintln!("Error: {filename}: {error:#}");
192 1
193 }
194 }
195}