use std::{env, ffi::OsStr, path::Path, path::PathBuf};
use crossterm::tty::IsTty;
use crate::{
display::style::BackgroundColor,
parse::guess_language::LanguageOverride,
};
pub const DEFAULT_BYTE_LIMIT: usize = 1_000_000;
pub const DEFAULT_GRAPH_LIMIT: usize = 3_000_000;
pub const DEFAULT_PARSE_ERROR_LIMIT: usize = 0;
pub const DEFAULT_TAB_WIDTH: usize = 8;
#[derive(Debug, Clone, Copy)]
pub enum ColorOutput {
Always,
Auto,
Never,
}
#[derive(Debug, Clone)]
pub struct DisplayOptions {
pub background_color: BackgroundColor,
pub use_color: bool,
pub display_mode: DisplayMode,
pub print_unchanged: bool,
pub tab_width: usize,
pub display_width: usize,
pub num_context_lines: u32,
pub in_vcs: bool,
pub syntax_highlight: bool,
}
impl Default for DisplayOptions {
fn default() -> Self {
Self {
background_color: BackgroundColor::Dark,
use_color: false,
display_mode: DisplayMode::SideBySide,
print_unchanged: true,
tab_width: 8,
display_width: 80,
num_context_lines: 3,
in_vcs: false,
syntax_highlight: true,
}
}
}
#[derive(Debug, Clone)]
pub struct DiffOptions {
pub graph_limit: usize,
pub byte_limit: usize,
pub parse_error_limit: usize,
pub check_only: bool,
pub ignore_comments: bool,
pub strip_cr: bool,
}
impl Default for DiffOptions {
fn default() -> Self {
Self {
graph_limit: DEFAULT_GRAPH_LIMIT,
byte_limit: DEFAULT_BYTE_LIMIT,
parse_error_limit: DEFAULT_PARSE_ERROR_LIMIT,
check_only: false,
ignore_comments: false,
strip_cr: false,
}
}
}
#[derive(Debug, Copy, Clone)]
pub enum DisplayMode {
Inline,
SideBySide,
SideBySideShowBoth,
Json,
}
#[derive(Eq, PartialEq, Debug)]
pub enum FileArgument {
NamedPath(PathBuf),
Stdin,
DevNull,
}
fn try_canonicalize(path: &Path) -> PathBuf {
path.canonicalize().unwrap_or_else(|_| path.into())
}
fn relative_to_current(path: &Path) -> PathBuf {
if let Ok(current_path) = env::current_dir() {
let path = try_canonicalize(path);
let current_path = try_canonicalize(¤t_path);
if let Ok(rel_path) = path.strip_prefix(current_path) {
return rel_path.into();
}
}
path.into()
}
impl FileArgument {
pub fn from_cli_argument(arg: &OsStr) -> Self {
if arg == "/dev/null" {
FileArgument::DevNull
} else if arg == "-" {
FileArgument::Stdin
} else {
FileArgument::NamedPath(PathBuf::from(arg))
}
}
pub fn from_path_argument(arg: &OsStr) -> Self {
if arg == "/dev/null" {
FileArgument::DevNull
} else {
FileArgument::NamedPath(PathBuf::from(arg))
}
}
pub fn display(&self) -> String {
match self {
FileArgument::NamedPath(path) => relative_to_current(path).display().to_string(),
FileArgument::Stdin => "(stdin)".to_string(),
FileArgument::DevNull => "/dev/null".to_string(),
}
}
}
pub enum Mode {
Diff {
diff_options: DiffOptions,
display_options: DisplayOptions,
set_exit_code: bool,
language_overrides: Vec<(LanguageOverride, Vec<glob::Pattern>)>,
lhs_path: FileArgument,
rhs_path: FileArgument,
display_path: String,
old_path: Option<String>,
},
DiffFromConflicts {
diff_options: DiffOptions,
display_options: DisplayOptions,
set_exit_code: bool,
language_overrides: Vec<(LanguageOverride, Vec<glob::Pattern>)>,
path: FileArgument,
display_path: String,
},
ListLanguages {
use_color: bool,
language_overrides: Vec<(LanguageOverride, Vec<glob::Pattern>)>,
},
DumpTreeSitter {
path: String,
language_overrides: Vec<(LanguageOverride, Vec<glob::Pattern>)>,
},
DumpSyntax {
path: String,
ignore_comments: bool,
language_overrides: Vec<(LanguageOverride, Vec<glob::Pattern>)>,
},
}
fn common_path_suffix(lhs_path: &Path, rhs_path: &Path) -> Option<String> {
let lhs_rev_components = lhs_path
.components()
.map(|c| c.as_os_str())
.rev()
.collect::<Vec<_>>();
let rhs_rev_components = rhs_path
.components()
.map(|c| c.as_os_str())
.rev()
.collect::<Vec<_>>();
let mut common_components = vec![];
for (lhs_component, rhs_component) in lhs_rev_components.iter().zip(rhs_rev_components.iter()) {
if lhs_component == rhs_component {
common_components.push(lhs_component.to_string_lossy());
} else {
break;
}
}
if common_components.is_empty() {
None
} else {
common_components.reverse();
Some(common_components.join(&std::path::MAIN_SEPARATOR.to_string()))
}
}
#[allow(dead_code)]
fn build_display_path(lhs_path: &FileArgument, rhs_path: &FileArgument) -> String {
match (lhs_path, rhs_path) {
(FileArgument::NamedPath(lhs), FileArgument::NamedPath(rhs)) => {
match common_path_suffix(lhs, rhs) {
Some(common_suffix) => common_suffix,
None => rhs.display().to_string(),
}
}
(FileArgument::NamedPath(p), _) | (_, FileArgument::NamedPath(p)) => {
p.display().to_string()
}
(FileArgument::DevNull, _) | (_, FileArgument::DevNull) => "/dev/null".into(),
(FileArgument::Stdin, FileArgument::Stdin) => "-".into(),
}
}
pub fn should_use_color(color_output: ColorOutput) -> bool {
match color_output {
ColorOutput::Always => true,
ColorOutput::Auto => {
std::io::stdout().is_tty() || env::var("GIT_PAGER_IN_USE").is_ok()
}
ColorOutput::Never => false,
}
}