use anyhow::Result;
use clap::{Parser, Subcommand};
use std::path::PathBuf;
use doxx::{ColorDepth, ExportFormat};
mod ansi;
mod config;
mod document;
mod export;
pub mod image_extractor;
mod keymap;
mod state;
pub mod terminal_image;
mod ui;
mod widgets;
#[derive(Parser)]
#[command(
name = "doxx",
version,
about = "Terminal document viewer for .docx files",
long_about = "Beautiful .docx viewing in your terminal"
)]
struct Cli {
#[arg(value_name = "FILE")]
file: Option<PathBuf>,
#[arg(short, long)]
outline: bool,
#[arg(short, long)]
page: Option<usize>,
#[arg(short, long)]
search: Option<String>,
#[arg(long, value_enum)]
export: Option<ExportFormat>,
#[arg(short = 'w', long, value_name = "COLS")]
terminal_width: Option<usize>,
#[arg(long, value_enum, default_value = "auto")]
color_depth: ColorDepth,
#[arg(long)]
force_ui: bool,
#[arg(long)]
color: bool,
#[arg(short = 'r', long)]
restore_position: bool,
#[arg(long)]
images: bool,
#[arg(long)]
no_images: bool,
#[arg(long)]
extract_images: Option<PathBuf>,
#[arg(long, value_name = "COLS")]
image_width: Option<u32>,
#[arg(long, value_name = "ROWS")]
image_height: Option<u32>,
#[arg(long, value_name = "SCALE")]
image_scale: Option<f32>,
#[arg(long)]
debug_terminal: bool,
#[command(subcommand)]
config: Option<ConfigCommands>,
}
#[derive(Subcommand)]
enum ConfigCommands {
Set { key: String, value: String },
Get { key: String },
Init,
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
if cli.debug_terminal {
use terminal_image::TerminalImageRenderer;
let renderer = TerminalImageRenderer::new();
renderer.print_capabilities();
return Ok(());
}
match &cli.config {
Some(ConfigCommands::Init) => {
let cfg = config::Config::default();
cfg.save()?;
println!(
"Configuration initialized at: {}",
config::Config::config_file_path()?.display()
);
println!("Edit that file to customize keybindings. Valid presets: default, vim, less");
return Ok(());
}
Some(ConfigCommands::Set { key, value }) => {
let mut cfg = config::Config::load()?;
cfg.set_value(key, value)?;
cfg.save()?;
println!("{key} = {value}");
return Ok(());
}
Some(ConfigCommands::Get { key }) => {
let cfg = config::Config::load()?;
match cfg.get_value(key) {
Some(v) => println!("{v}"),
None => anyhow::bail!("Unknown config key: {key}"),
}
return Ok(());
}
None => {}
}
let cfg = config::Config::load()?;
let file_path = cli
.file
.clone()
.ok_or_else(|| anyhow::anyhow!("Please provide a document file to view"))?;
if !file_path.exists() {
anyhow::bail!("File not found: {}", file_path.display());
}
let image_options = document::ImageOptions {
enabled: cli.images,
max_width: cli.image_width,
max_height: cli.image_height,
scale: cli.image_scale,
};
let file_path_clone = file_path.clone();
let document = tokio::task::spawn_blocking(move || {
document::load_document(&file_path_clone, image_options)
})
.await??;
if let Some(extract_dir) = &cli.extract_images {
use image_extractor::ImageExtractor;
let mut extractor = ImageExtractor::new()?;
extractor.extract_images_from_docx(&file_path)?;
std::fs::create_dir_all(extract_dir)?;
for (rel_id, temp_path) in extractor.list_images() {
let target_path = extract_dir.join(rel_id);
std::fs::copy(temp_path, &target_path)?;
println!("Extracted: {}", target_path.display());
}
println!(
"Successfully extracted {} images to {}",
extractor.list_images().len(),
extract_dir.display()
);
return Ok(());
}
if let Some(export_format) = &cli.export {
match export_format {
ExportFormat::Ansi => {
export::export_to_ansi_with_cli_options(
&document,
cli.terminal_width,
&cli.color_depth,
)?;
}
_ => {
export::export_document(&document, export_format)?;
}
}
return Ok(());
}
ui::run_viewer(document, &cli, &cfg).await?;
Ok(())
}