diff --git a/Cargo.lock b/Cargo.lock index d709b97477..e66e9e6bcc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -181,6 +181,7 @@ dependencies = [ "regex", "rpds", "rustc-hash", + "serde", "strsim", "term_size", "tree-sitter", @@ -463,6 +464,26 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "serde" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "static_assertions" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index b9692b50cf..a49e02409a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ const_format = "0.2.22" owo-colors = "3.2.0" rpds = "0.10.0" wu-diff = "0.1.2" +serde = { version = "1.0", features = ["derive"] } [dev-dependencies] pretty_assertions = "1.0.0" diff --git a/src/guess_language.rs b/src/guess_language.rs index 82e2d829f4..6d29f68f04 100644 --- a/src/guess_language.rs +++ b/src/guess_language.rs @@ -50,7 +50,7 @@ pub enum Language { Zig, } -use Language::*; +pub use Language::*; pub fn guess(path: &Path, src: &str) -> Option { if let Some(lang) = from_emacs_mode_header(src) { diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000000..33bb88107c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,323 @@ +//! Difftastic is a syntactic diff tool. +//! +//! For usage instructions and advice on contributing, see [the +//! manual](http://difftastic.wilfred.me.uk/). +//! + +// This tends to trigger on larger tuples of simple types, and naming +// them would probably be worse for readability. +#![allow(clippy::type_complexity)] +// == "" is often clearer when dealing with strings. +#![allow(clippy::comparison_to_empty)] +// It's common to have pairs foo_lhs and foo_rhs, leading to double +// the number of arguments and triggering this lint. +#![allow(clippy::too_many_arguments)] + +mod context; +mod dijkstra; +pub mod files; +mod graph; +pub mod guess_language; +mod hunks; +mod inline; +mod line_parser; +mod lines; +mod myers_diff; +pub mod option_types; +mod positions; +mod side_by_side; +mod sliders; +pub mod style; +pub mod summary; +pub mod syntax; +pub mod tree_sitter_parser; +mod unchanged; + +#[macro_use] +extern crate log; + +use crate::hunks::{matched_pos_to_hunks, merge_adjacent}; +use context::opposite_positions; +use guess_language::guess; +use log::info; +use mimalloc::MiMalloc; +use option_types::DisplayMode; + +/// The global allocator used by difftastic. +/// +/// Diffing allocates a large amount of memory, and `MiMalloc` performs +/// better. +#[global_allocator] +static GLOBAL: MiMalloc = MiMalloc; + +use sliders::fix_all_sliders; +use std::{env, path::Path}; +use style::BackgroundColor; +pub use summary::{DiffResult, FileContent}; +use syntax::{init_next_prev, Syntax}; +use typed_arena::Arena; + +use crate::{ + dijkstra::mark_syntax, files::is_probably_binary, lines::MaxLine, syntax::init_all_info, + tree_sitter_parser as tsp, +}; + +extern crate pretty_env_logger; + +pub fn diff_file_content( + display_path: &str, + lhs_bytes: &[u8], + rhs_bytes: &[u8], + node_limit: u32, + byte_limit: usize, + language_override: Option, +) -> DiffResult { + if is_probably_binary(lhs_bytes) || is_probably_binary(rhs_bytes) { + return DiffResult { + path: display_path.into(), + language: None, + lhs_src: FileContent::Binary(lhs_bytes.to_vec()), + rhs_src: FileContent::Binary(rhs_bytes.to_vec()), + lhs_positions: vec![], + rhs_positions: vec![], + }; + } + + // TODO: don't replace tab characters inside string literals. + let mut lhs_src = String::from_utf8_lossy(lhs_bytes) + .to_string() + .replace('\t', " "); + let mut rhs_src = String::from_utf8_lossy(rhs_bytes) + .to_string() + .replace('\t', " "); + + // Ignore the trailing newline, if present. + // TODO: highlight if this has changes (#144). + // TODO: factor out a string cleaning function. + if lhs_src.ends_with('\n') { + lhs_src.pop(); + } + if rhs_src.ends_with('\n') { + rhs_src.pop(); + } + + // TODO: take a Path directly instead. + let path = Path::new(&display_path); + + // Take the larger of the two files when guessing the + // language. This is useful when we've added or removed a whole + // file. + let guess_src = if lhs_src.len() > rhs_src.len() { + &lhs_src + } else { + &rhs_src + }; + let ts_lang = language_override + .or_else(|| guess(path, guess_src)) + .map(tsp::from_language); + + if lhs_bytes == rhs_bytes { + // If the two files are completely identical, return early + // rather than doing any more work. + return DiffResult { + path: display_path.into(), + language: ts_lang.map(|l| l.name.into()), + lhs_src: FileContent::Text("".into()), + rhs_src: FileContent::Text("".into()), + lhs_positions: vec![], + rhs_positions: vec![], + }; + } + + let (lang_name, lhs_positions, rhs_positions) = match ts_lang { + _ if lhs_bytes.len() > byte_limit || rhs_bytes.len() > byte_limit => { + let lhs_positions = line_parser::change_positions(&lhs_src, &rhs_src); + let rhs_positions = line_parser::change_positions(&rhs_src, &lhs_src); + ( + Some("Text (exceeded DFT_BYTE_LIMIT)".into()), + lhs_positions, + rhs_positions, + ) + } + Some(ts_lang) => { + let arena = Arena::new(); + let lhs = tsp::parse(&arena, &lhs_src, &ts_lang); + let rhs = tsp::parse(&arena, &rhs_src, &ts_lang); + + init_all_info(&lhs, &rhs); + + let possibly_changed = if env::var("DFT_DBG_KEEP_UNCHANGED").is_ok() { + vec![(lhs.clone(), rhs.clone())] + } else { + unchanged::mark_unchanged(&lhs, &rhs) + }; + + let possibly_changed_max = max_num_nodes(&possibly_changed); + if possibly_changed_max > node_limit { + info!( + "Found {} nodes, exceeding the limit {}", + possibly_changed_max, node_limit + ); + + let lhs_positions = line_parser::change_positions(&lhs_src, &rhs_src); + let rhs_positions = line_parser::change_positions(&rhs_src, &lhs_src); + ( + Some("Text (exceeded DFT_NODE_LIMIT)".into()), + lhs_positions, + rhs_positions, + ) + } else { + for (lhs_section_nodes, rhs_section_nodes) in possibly_changed { + init_next_prev(&lhs_section_nodes); + init_next_prev(&rhs_section_nodes); + + mark_syntax( + lhs_section_nodes.get(0).copied(), + rhs_section_nodes.get(0).copied(), + ); + + fix_all_sliders(&lhs_section_nodes); + fix_all_sliders(&rhs_section_nodes); + } + + let lhs_positions = syntax::change_positions(&lhs); + let rhs_positions = syntax::change_positions(&rhs); + (Some(ts_lang.name.into()), lhs_positions, rhs_positions) + } + } + None => { + let lhs_positions = line_parser::change_positions(&lhs_src, &rhs_src); + let rhs_positions = line_parser::change_positions(&rhs_src, &lhs_src); + (None, lhs_positions, rhs_positions) + } + }; + + DiffResult { + path: display_path.into(), + language: lang_name, + lhs_src: FileContent::Text(lhs_src), + rhs_src: FileContent::Text(rhs_src), + lhs_positions, + rhs_positions, + } +} + +// TODO: factor out a DiffOptions struct. +pub fn print_diff_result( + display_width: usize, + use_color: bool, + display_mode: DisplayMode, + background: BackgroundColor, + print_unchanged: bool, + summary: &DiffResult, +) { + match (&summary.lhs_src, &summary.rhs_src) { + (FileContent::Text(lhs_src), FileContent::Text(rhs_src)) => { + let opposite_to_lhs = opposite_positions(&summary.lhs_positions); + let opposite_to_rhs = opposite_positions(&summary.rhs_positions); + + let hunks = matched_pos_to_hunks(&summary.lhs_positions, &summary.rhs_positions); + let hunks = merge_adjacent( + &hunks, + &opposite_to_lhs, + &opposite_to_rhs, + lhs_src.max_line(), + rhs_src.max_line(), + ); + + let lang_name = summary.language.clone().unwrap_or_else(|| "Text".into()); + if hunks.is_empty() { + if print_unchanged { + println!( + "{}", + style::header(&summary.path, 1, 1, &lang_name, use_color, background) + ); + if lang_name == "Text" || summary.lhs_src == summary.rhs_src { + // TODO: there are other Text names now, so + // they will hit the second case incorrectly. + println!("No changes.\n"); + } else { + println!("No syntactic changes.\n"); + } + } + return; + } + + match display_mode { + DisplayMode::Inline => { + inline::print( + lhs_src, + rhs_src, + &summary.lhs_positions, + &summary.rhs_positions, + &hunks, + &summary.path, + &lang_name, + use_color, + background, + ); + } + DisplayMode::SideBySide | DisplayMode::SideBySideShowBoth => { + side_by_side::print( + &hunks, + display_width, + use_color, + display_mode, + background, + &summary.path, + &lang_name, + lhs_src, + rhs_src, + &summary.lhs_positions, + &summary.rhs_positions, + ); + } + } + } + (FileContent::Binary(lhs_bytes), FileContent::Binary(rhs_bytes)) => { + let changed = lhs_bytes != rhs_bytes; + if print_unchanged || changed { + println!( + "{}", + style::header(&summary.path, 1, 1, "binary", use_color, background) + ); + if changed { + println!("Binary contents changed."); + } else { + println!("No changes."); + } + } + } + (_, FileContent::Binary(_)) | (FileContent::Binary(_), _) => { + // We're diffing a binary file against a text file. + println!( + "{}", + style::header(&summary.path, 1, 1, "binary", use_color, background) + ); + println!("Binary contents changed."); + } + } +} + +/// What is the total number of nodes in `roots`? +fn num_nodes(roots: &[&Syntax]) -> u32 { + roots + .iter() + .map(|n| { + 1 + match n { + Syntax::List { + num_descendants, .. + } => *num_descendants, + Syntax::Atom { .. } => 0, + } + }) + .sum() +} + +fn max_num_nodes(roots_vec: &[(Vec<&Syntax>, Vec<&Syntax>)]) -> u32 { + roots_vec + .iter() + .map(|(lhs, rhs)| num_nodes(lhs) + num_nodes(rhs)) + .max() + .unwrap_or(0) +} diff --git a/src/lines.rs b/src/lines.rs index 6090cdf664..6cb664cb4d 100644 --- a/src/lines.rs +++ b/src/lines.rs @@ -1,6 +1,7 @@ //! Manipulate lines of text and groups of lines. use crate::positions::SingleLineSpan; +use serde::{Deserialize, Serialize}; use std::{ cmp::{max, Ordering}, fmt, @@ -10,7 +11,7 @@ use std::{ /// other numerical data. /// /// Zero-indexed internally. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct LineNumber(pub usize); impl LineNumber { diff --git a/src/main.rs b/src/main.rs index 417292715c..999ae511f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,59 +13,28 @@ // the number of arguments and triggering this lint. #![allow(clippy::too_many_arguments)] -mod context; -mod dijkstra; -mod files; -mod graph; -mod guess_language; -mod hunks; -mod inline; -mod line_parser; -mod lines; -mod myers_diff; mod options; -mod positions; -mod side_by_side; -mod sliders; -mod style; -mod summary; -mod syntax; -mod tree_sitter_parser; -mod unchanged; #[macro_use] extern crate log; -use crate::hunks::{matched_pos_to_hunks, merge_adjacent}; -use context::opposite_positions; -use files::read_files_or_die; -use guess_language::guess; use log::info; -use mimalloc::MiMalloc; -/// The global allocator used by difftastic. -/// -/// Diffing allocates a large amount of memory, and `MiMalloc` performs -/// better. -#[global_allocator] -static GLOBAL: MiMalloc = MiMalloc; - -use options::{should_use_color, DisplayMode, Mode}; -use sliders::fix_all_sliders; -use std::{env, path::Path}; -use style::BackgroundColor; -use summary::{DiffResult, FileContent}; -use syntax::init_next_prev; -use typed_arena::Arena; -use walkdir::WalkDir; - -use crate::{ - dijkstra::mark_syntax, - files::{is_probably_binary, read_or_die}, - lines::MaxLine, +use difftastic::{ + diff_file_content, + files::{read_files_or_die, read_or_die}, + guess_language, + guess_language::guess, + option_types::Mode, + print_diff_result, + summary::DiffResult, syntax::init_all_info, tree_sitter_parser as tsp, }; +use options::should_use_color; +use std::path::Path; +use typed_arena::Arena; +use walkdir::WalkDir; extern crate pretty_env_logger; @@ -223,144 +192,6 @@ fn diff_file( ) } -fn diff_file_content( - display_path: &str, - lhs_bytes: &[u8], - rhs_bytes: &[u8], - node_limit: u32, - byte_limit: usize, - language_override: Option, -) -> DiffResult { - if is_probably_binary(lhs_bytes) || is_probably_binary(rhs_bytes) { - return DiffResult { - path: display_path.into(), - language: None, - lhs_src: FileContent::Binary(lhs_bytes.to_vec()), - rhs_src: FileContent::Binary(rhs_bytes.to_vec()), - lhs_positions: vec![], - rhs_positions: vec![], - }; - } - - // TODO: don't replace tab characters inside string literals. - let mut lhs_src = String::from_utf8_lossy(lhs_bytes) - .to_string() - .replace('\t', " "); - let mut rhs_src = String::from_utf8_lossy(rhs_bytes) - .to_string() - .replace('\t', " "); - - // Ignore the trailing newline, if present. - // TODO: highlight if this has changes (#144). - // TODO: factor out a string cleaning function. - if lhs_src.ends_with('\n') { - lhs_src.pop(); - } - if rhs_src.ends_with('\n') { - rhs_src.pop(); - } - - // TODO: take a Path directly instead. - let path = Path::new(&display_path); - - // Take the larger of the two files when guessing the - // language. This is useful when we've added or removed a whole - // file. - let guess_src = if lhs_src.len() > rhs_src.len() { - &lhs_src - } else { - &rhs_src - }; - let ts_lang = language_override - .or_else(|| guess(path, guess_src)) - .map(tsp::from_language); - - if lhs_bytes == rhs_bytes { - // If the two files are completely identical, return early - // rather than doing any more work. - return DiffResult { - path: display_path.into(), - language: ts_lang.map(|l| l.name.into()), - lhs_src: FileContent::Text("".into()), - rhs_src: FileContent::Text("".into()), - lhs_positions: vec![], - rhs_positions: vec![], - }; - } - - let (lang_name, lhs_positions, rhs_positions) = match ts_lang { - _ if lhs_bytes.len() > byte_limit || rhs_bytes.len() > byte_limit => { - let lhs_positions = line_parser::change_positions(&lhs_src, &rhs_src); - let rhs_positions = line_parser::change_positions(&rhs_src, &lhs_src); - ( - Some("Text (exceeded DFT_BYTE_LIMIT)".into()), - lhs_positions, - rhs_positions, - ) - } - Some(ts_lang) => { - let arena = Arena::new(); - let lhs = tsp::parse(&arena, &lhs_src, &ts_lang); - let rhs = tsp::parse(&arena, &rhs_src, &ts_lang); - - init_all_info(&lhs, &rhs); - - let possibly_changed = if env::var("DFT_DBG_KEEP_UNCHANGED").is_ok() { - vec![(lhs.clone(), rhs.clone())] - } else { - unchanged::mark_unchanged(&lhs, &rhs) - }; - - let possibly_changed_max = max_num_nodes(&possibly_changed); - if possibly_changed_max > node_limit { - info!( - "Found {} nodes, exceeding the limit {}", - possibly_changed_max, node_limit - ); - - let lhs_positions = line_parser::change_positions(&lhs_src, &rhs_src); - let rhs_positions = line_parser::change_positions(&rhs_src, &lhs_src); - ( - Some("Text (exceeded DFT_NODE_LIMIT)".into()), - lhs_positions, - rhs_positions, - ) - } else { - for (lhs_section_nodes, rhs_section_nodes) in possibly_changed { - init_next_prev(&lhs_section_nodes); - init_next_prev(&rhs_section_nodes); - - mark_syntax( - lhs_section_nodes.get(0).copied(), - rhs_section_nodes.get(0).copied(), - ); - - fix_all_sliders(&lhs_section_nodes); - fix_all_sliders(&rhs_section_nodes); - } - - let lhs_positions = syntax::change_positions(&lhs); - let rhs_positions = syntax::change_positions(&rhs); - (Some(ts_lang.name.into()), lhs_positions, rhs_positions) - } - } - None => { - let lhs_positions = line_parser::change_positions(&lhs_src, &rhs_src); - let rhs_positions = line_parser::change_positions(&rhs_src, &lhs_src); - (None, lhs_positions, rhs_positions) - } - }; - - DiffResult { - path: display_path.into(), - language: lang_name, - lhs_src: FileContent::Text(lhs_src), - rhs_src: FileContent::Text(rhs_src), - lhs_positions, - rhs_positions, - } -} - /// Given two directories that contain the files, compare them /// pairwise. Returns an iterator, so we can print results /// incrementally. @@ -398,126 +229,6 @@ fn diff_directories<'a>( }) } -// TODO: factor out a DiffOptions struct. -fn print_diff_result( - display_width: usize, - use_color: bool, - display_mode: DisplayMode, - background: BackgroundColor, - print_unchanged: bool, - summary: &DiffResult, -) { - match (&summary.lhs_src, &summary.rhs_src) { - (FileContent::Text(lhs_src), FileContent::Text(rhs_src)) => { - let opposite_to_lhs = opposite_positions(&summary.lhs_positions); - let opposite_to_rhs = opposite_positions(&summary.rhs_positions); - - let hunks = matched_pos_to_hunks(&summary.lhs_positions, &summary.rhs_positions); - let hunks = merge_adjacent( - &hunks, - &opposite_to_lhs, - &opposite_to_rhs, - lhs_src.max_line(), - rhs_src.max_line(), - ); - - let lang_name = summary.language.clone().unwrap_or_else(|| "Text".into()); - if hunks.is_empty() { - if print_unchanged { - println!( - "{}", - style::header(&summary.path, 1, 1, &lang_name, use_color, background) - ); - if lang_name == "Text" || summary.lhs_src == summary.rhs_src { - // TODO: there are other Text names now, so - // they will hit the second case incorrectly. - println!("No changes.\n"); - } else { - println!("No syntactic changes.\n"); - } - } - return; - } - - match display_mode { - DisplayMode::Inline => { - inline::print( - lhs_src, - rhs_src, - &summary.lhs_positions, - &summary.rhs_positions, - &hunks, - &summary.path, - &lang_name, - use_color, - background, - ); - } - DisplayMode::SideBySide | DisplayMode::SideBySideShowBoth => { - side_by_side::print( - &hunks, - display_width, - use_color, - display_mode, - background, - &summary.path, - &lang_name, - lhs_src, - rhs_src, - &summary.lhs_positions, - &summary.rhs_positions, - ); - } - } - } - (FileContent::Binary(lhs_bytes), FileContent::Binary(rhs_bytes)) => { - let changed = lhs_bytes != rhs_bytes; - if print_unchanged || changed { - println!( - "{}", - style::header(&summary.path, 1, 1, "binary", use_color, background) - ); - if changed { - println!("Binary contents changed."); - } else { - println!("No changes."); - } - } - } - (_, FileContent::Binary(_)) | (FileContent::Binary(_), _) => { - // We're diffing a binary file against a text file. - println!( - "{}", - style::header(&summary.path, 1, 1, "binary", use_color, background) - ); - println!("Binary contents changed."); - } - } -} - -/// What is the total number of nodes in `roots`? -fn num_nodes(roots: &[&syntax::Syntax]) -> u32 { - roots - .iter() - .map(|n| { - 1 + match n { - syntax::Syntax::List { - num_descendants, .. - } => *num_descendants, - syntax::Syntax::Atom { .. } => 0, - } - }) - .sum() -} - -fn max_num_nodes(roots_vec: &[(Vec<&syntax::Syntax>, Vec<&syntax::Syntax>)]) -> u32 { - roots_vec - .iter() - .map(|(lhs, rhs)| num_nodes(lhs) + num_nodes(rhs)) - .max() - .unwrap_or(0) -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/option_types.rs b/src/option_types.rs new file mode 100644 index 0000000000..57d08fe1ce --- /dev/null +++ b/src/option_types.rs @@ -0,0 +1,41 @@ +use crate::guess_language; +use crate::style::BackgroundColor; + +#[derive(Debug)] +pub enum ColorOutput { + Always, + Auto, + Never, +} + +#[derive(Debug, Copy, Clone)] +pub enum DisplayMode { + Inline, + SideBySide, + SideBySideShowBoth, +} + +pub enum Mode { + Diff { + node_limit: u32, + byte_limit: usize, + print_unchanged: bool, + missing_as_empty: bool, + display_mode: DisplayMode, + background_color: BackgroundColor, + color_output: ColorOutput, + display_width: usize, + display_path: String, + language_override: Option, + lhs_path: String, + rhs_path: String, + }, + DumpTreeSitter { + path: String, + language_override: Option, + }, + DumpSyntax { + path: String, + language_override: Option, + }, +} diff --git a/src/options.rs b/src/options.rs index a7bbccbf43..318d1b2c4b 100644 --- a/src/options.rs +++ b/src/options.rs @@ -4,20 +4,15 @@ use atty::Stream; use clap::{crate_authors, crate_description, crate_version, App, AppSettings, Arg}; use const_format::formatcp; -use crate::{guess_language, style::BackgroundColor}; +use difftastic::option_types::*; + +use difftastic::{guess_language, style::BackgroundColor}; pub const DEFAULT_NODE_LIMIT: u32 = 30_000; pub const DEFAULT_BYTE_LIMIT: usize = 1_000_000; const USAGE: &str = concat!(env!("CARGO_BIN_NAME"), " [OPTIONS] OLD-PATH NEW-PATH"); -#[derive(Debug)] -pub enum ColorOutput { - Always, - Auto, - Never, -} - fn app() -> clap::App<'static> { App::new("Difftastic") .override_usage(USAGE) @@ -124,38 +119,6 @@ fn app() -> clap::App<'static> { .setting(AppSettings::ArgRequiredElseHelp) } -#[derive(Debug, Copy, Clone)] -pub enum DisplayMode { - Inline, - SideBySide, - SideBySideShowBoth, -} - -pub enum Mode { - Diff { - node_limit: u32, - byte_limit: usize, - print_unchanged: bool, - missing_as_empty: bool, - display_mode: DisplayMode, - background_color: BackgroundColor, - color_output: ColorOutput, - display_width: usize, - display_path: String, - language_override: Option, - lhs_path: String, - rhs_path: String, - }, - DumpTreeSitter { - path: String, - language_override: Option, - }, - DumpSyntax { - path: String, - language_override: Option, - }, -} - /// Parse CLI arguments passed to the binary. pub fn parse_args() -> Mode { let matches = app().get_matches(); diff --git a/src/positions.rs b/src/positions.rs index 3506b2fb74..daaa2ad02d 100644 --- a/src/positions.rs +++ b/src/positions.rs @@ -1,9 +1,10 @@ //! Represents positions within a string. use crate::lines::LineNumber; +use serde::{Deserialize, Serialize}; /// A range within a single line of a string. -#[derive(Debug, PartialEq, Clone, Copy, Eq, PartialOrd, Ord, Hash)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy, Eq, PartialOrd, Ord, Hash)] pub struct SingleLineSpan { /// All zero-indexed. pub line: LineNumber, diff --git a/src/side_by_side.rs b/src/side_by_side.rs index 9cfa96407c..25d6a22e96 100644 --- a/src/side_by_side.rs +++ b/src/side_by_side.rs @@ -10,7 +10,7 @@ use crate::{ context::all_matched_lines_filled, hunks::{matched_lines_for_hunk, Hunk}, lines::{codepoint_len, format_line_num, LineNumber}, - options::DisplayMode, + option_types::DisplayMode, positions::SingleLineSpan, style::{self, apply_colors, color_positions, novel_style, split_and_apply, BackgroundColor}, syntax::{zip_pad_shorter, MatchedPos}, diff --git a/src/summary.rs b/src/summary.rs index e7e7273cd2..bb86dda8fb 100644 --- a/src/summary.rs +++ b/src/summary.rs @@ -1,12 +1,13 @@ use crate::syntax::MatchedPos; +use serde::{Deserialize, Serialize}; -#[derive(Debug, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] pub enum FileContent { Text(String), Binary(Vec), } -#[derive(Debug)] +#[derive(Serialize, Deserialize, Debug)] pub struct DiffResult { pub path: String, pub language: Option, diff --git a/src/syntax.rs b/src/syntax.rs index 84eae013fe..8370561ad9 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -2,6 +2,7 @@ #![allow(clippy::mutable_key_type)] // Hash for Syntax doesn't use mutable fields. +use serde::{Deserialize, Serialize}; use std::{ cell::Cell, collections::HashMap, @@ -552,7 +553,7 @@ impl<'a> Hash for Syntax<'a> { } } -#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)] +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Copy, Hash)] pub enum AtomKind { Normal, String, @@ -562,14 +563,14 @@ pub enum AtomKind { } /// Unlike atoms, tokens can be delimiters like `{`. -#[derive(PartialEq, Eq, Debug, Clone, Copy)] +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Copy)] pub enum TokenKind { Delimiter, Atom(AtomKind), } /// A matched token (an atom, a delimiter, or a comment word). -#[derive(PartialEq, Eq, Debug, Clone)] +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] pub enum MatchKind { UnchangedToken { highlight: TokenKind, @@ -598,7 +599,7 @@ impl MatchKind { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] pub struct MatchedPos { pub kind: MatchKind, pub pos: SingleLineSpan,