#![allow(renamed_and_removed_lints)]
#![allow(clippy::type_complexity)]
#![allow(clippy::comparison_to_empty)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::if_same_then_else)]
#![allow(clippy::mutable_key_type)]
#![allow(unknown_lints)]
#![allow(clippy::manual_unwrap_or_default)]
#![allow(clippy::implicit_saturating_sub)]
#![allow(clippy::needless_as_bytes)]
#![warn(clippy::str_to_string)]
#![warn(clippy::string_to_string)]
#![warn(clippy::todo)]
#![warn(clippy::dbg_macro)]
mod cli;
mod config;
mod constants;
mod diff;
mod engine;
mod exit_codes;
use engine::diff_file_content;
mod files;
mod git;
mod gitattributes;
mod hash;
mod line_layout;
mod line_parser;
mod lines;
mod options;
mod pairing;
mod parse;
mod plugin;
pub(crate) mod protocol;
mod summary;
mod tags;
mod version;
mod words;
#[macro_use]
extern crate log;
use crate::config::Params;
use crate::exit_codes::EXIT_BAD_ARGUMENTS;
use crate::files::{guess_content, read_files_or_die, read_or_die, ProbableFileKind};
use crate::gitattributes::{check_diff_attr, DiffAttribute};
use crate::parse::guess_language::{
guess, language_globs, language_name, Language, LanguageOverride,
};
use crate::parse::syntax;
#[cfg(not(any(windows, target_os = "illumos", target_os = "freebsd")))]
use tikv_jemallocator::Jemalloc;
#[cfg(not(any(windows, target_os = "illumos", target_os = "freebsd")))]
#[global_allocator]
static GLOBAL: Jemalloc = Jemalloc;
use std::path::Path;
use strum::IntoEnumIterator;
use typed_arena::Arena;
use crate::engine::QueryConflict;
use crate::options::{DiffOptions, FileArgument, Mode};
use crate::parse::folds::Conflict;
use crate::parse::syntax::init_all_info;
use crate::parse::tree_sitter_parser as tsp;
use crate::summary::{DiffResult, FileContent, FileFormat};
extern crate pretty_env_logger;
#[cfg(unix)]
fn reset_sigpipe() {
unsafe {
libc::signal(libc::SIGPIPE, libc::SIG_DFL);
}
}
#[cfg(not(unix))]
fn reset_sigpipe() {
}
fn main() {
pretty_env_logger::try_init_timed_custom_env("DFT_LOG")
.expect("The logger has not been previously initialized");
reset_sigpipe();
let result = match std::env::args_os().nth(1).as_deref() {
Some(arg) if arg == "debug" => {
run_debug();
return;
}
_ => cli::run(),
};
match result {
Ok(code) => std::process::exit(code),
Err(error) => {
eprintln!("{error}");
std::process::exit(2);
}
}
}
fn run_debug() {
let params = &Params::default();
match options::parse_args() {
Mode::DumpTreeSitter {
path,
language_overrides,
} => {
let path = Path::new(&path);
let bytes = read_or_die(path);
let src = String::from_utf8_lossy(&bytes).to_string();
let language = guess(path, &src, &language_overrides);
match language {
Some(lang) => {
let ts_lang = tsp::from_language(lang);
let tree = tsp::to_tree(&src, ts_lang);
tsp::print_tree(&src, &tree);
}
None => {
eprintln!("No tree-sitter parser for file: {:?}", path);
}
}
}
Mode::DumpSyntax {
path,
ignore_comments,
language_overrides,
} => {
let path = Path::new(&path);
let bytes = read_or_die(path);
let src = String::from_utf8_lossy(&bytes).to_string();
let language = guess(path, &src, &language_overrides);
match language {
Some(lang) => {
let ts_lang = params.language(lang);
let arena = Arena::new();
let ast = conflict_or_die(tsp::parse(&arena, &src, ts_lang, ignore_comments));
init_all_info(&ast, &[]);
println!("{:#?}", ast);
}
None => {
eprintln!("No tree-sitter parser for file: {:?}", path);
}
}
}
Mode::DumpSyntaxDot {
path,
ignore_comments,
language_overrides,
} => {
let path = Path::new(&path);
let bytes = read_or_die(path);
let src = String::from_utf8_lossy(&bytes).to_string();
let language = guess(path, &src, &language_overrides);
match language {
Some(lang) => {
let ts_lang = params.language(lang);
let arena = Arena::new();
let ast = conflict_or_die(tsp::parse(&arena, &src, ts_lang, ignore_comments));
init_all_info(&ast, &[]);
syntax::print_as_dot(&ast);
}
None => {
eprintln!("No tree-sitter parser for file: {:?}", path);
}
}
}
Mode::ListLanguages { language_overrides } => {
for (lang_override, globs) in language_overrides {
let name = match lang_override {
LanguageOverride::Language(lang) => language_name(lang),
LanguageOverride::PlainText => "Text",
};
println!("{} (from override)", name);
for glob in globs {
print!(" {}", glob.as_str());
}
println!();
}
for language in Language::iter() {
println!("{}", language_name(language));
for glob in language_globs(language) {
print!(" {}", glob.as_str());
}
println!();
}
}
};
}
fn diff_file(
params: &Params,
display_path: &str,
lhs_path: &FileArgument,
rhs_path: &FileArgument,
diff_options: &DiffOptions,
missing_as_empty: bool,
overrides: &[(LanguageOverride, Vec<glob::Pattern>)],
binary_overrides: &[glob::Pattern],
) -> Result<DiffResult, QueryConflict> {
let (lhs_bytes, rhs_bytes) = read_files_or_die(lhs_path, rhs_path, missing_as_empty);
let (mut lhs_src, mut rhs_src) = match (
guess_content(&lhs_bytes, lhs_path, binary_overrides),
guess_content(&rhs_bytes, rhs_path, binary_overrides),
check_diff_attr(Path::new(display_path)),
) {
(ProbableFileKind::Binary, _, _)
| (_, ProbableFileKind::Binary, _)
| (_, _, Some(DiffAttribute::AssumeBinary)) => {
return Ok(DiffResult {
file_format: FileFormat::Binary,
lhs_src: FileContent::Binary,
rhs_src: FileContent::Binary,
lhs_positions: vec![],
rhs_positions: vec![],
lhs_folds: vec![],
rhs_folds: vec![],
});
}
(ProbableFileKind::Text(lhs_src), ProbableFileKind::Text(rhs_src), _) => (lhs_src, rhs_src),
};
if !lhs_src.is_empty() && !lhs_src.ends_with('\n') {
lhs_src.push('\n');
}
if !rhs_src.is_empty() && !rhs_src.ends_with('\n') {
rhs_src.push('\n');
}
diff_file_content(
params,
display_path,
lhs_path,
rhs_path,
&lhs_src,
&rhs_src,
diff_options,
overrides,
)
}
fn conflict_or_die<T>(result: Result<T, Conflict>) -> T {
match result {
Ok(value) => value,
Err(conflict) => {
eprintln!(
"line {}: {} and {} capture the same {} with different fold ranges",
conflict.line + 1,
conflict.sources.0,
conflict.sources.1,
conflict.kind
);
std::process::exit(EXIT_BAD_ARGUMENTS);
}
}
}
#[cfg(test)]
mod tests {
use std::ffi::OsStr;
use super::*;
#[test]
fn test_diff_identical_content() {
let s = "foo";
let res = diff_file_content(
&Params::default(),
"foo.el",
&FileArgument::from_path_argument(OsStr::new("foo.el")),
&FileArgument::from_path_argument(OsStr::new("foo.el")),
s,
s,
&DiffOptions::default(),
&[],
)
.unwrap();
assert_eq!(res.lhs_positions, vec![]);
assert_eq!(res.rhs_positions, vec![]);
}
}