use std::fmt;
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Chunk<'a> {
Equal(&'a str),
Delete(&'a str),
Insert(&'a str),
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum DiffAlgorithm {
#[default]
Myers,
Patience,
Histogram,
}
impl DiffAlgorithm {
pub(crate) fn similar(self) -> similar::Algorithm {
match self {
Self::Myers => similar::Algorithm::Myers,
Self::Patience => similar::Algorithm::Patience,
Self::Histogram => similar::Algorithm::Histogram,
}
}
}
#[derive(Debug, Clone)]
pub struct DiffOptions<'a> {
pub context_lines: usize,
pub old_label: Option<&'a str>,
pub new_label: Option<&'a str>,
pub missing_newline_hint: bool,
pub algorithm: DiffAlgorithm,
pub timeout: Duration,
pub inline_timeout: Duration,
}
impl Default for DiffOptions<'_> {
fn default() -> Self {
Self {
context_lines: 3,
old_label: None,
new_label: None,
missing_newline_hint: true,
algorithm: DiffAlgorithm::Myers,
timeout: Duration::from_millis(200),
inline_timeout: Duration::from_millis(40),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DiffHunk {
pub old_start: usize,
pub old_lines: usize,
pub new_start: usize,
pub new_lines: usize,
pub lines: Vec<DiffLine>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum DiffLineKind {
Context,
Addition,
Deletion,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DiffLine {
pub kind: DiffLineKind,
pub old_line: Option<u32>,
pub new_line: Option<u32>,
pub text: String,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DiffStats {
pub additions: usize,
pub deletions: usize,
pub context: usize,
pub hunks: usize,
pub omitted_rows: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DiffDocument {
pub hunks: Vec<DiffHunk>,
pub stats: DiffStats,
#[cfg_attr(feature = "serde", serde(default = "default_inline_timeout"))]
pub(crate) inline_timeout: Duration,
}
impl Default for DiffDocument {
fn default() -> Self {
Self {
hunks: Vec::new(),
stats: DiffStats::default(),
inline_timeout: default_inline_timeout(),
}
}
}
pub(crate) fn default_inline_timeout() -> Duration {
Duration::from_millis(40)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseDiffError {
message: &'static str,
}
impl ParseDiffError {
pub(crate) fn new(message: &'static str) -> Self {
Self { message }
}
}
impl fmt::Display for ParseDiffError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.message)
}
}
impl std::error::Error for ParseDiffError {}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DiffBundle {
pub hunks: Vec<DiffHunk>,
pub formatted: String,
pub is_empty: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct IntralineRange {
pub start: usize,
pub end: usize,
}
pub type WordChangedRanges = Vec<(usize, usize)>;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct DiffChangeCounts {
pub additions: usize,
pub deletions: usize,
}
impl DiffChangeCounts {
#[must_use]
pub const fn total(self) -> usize {
self.additions + self.deletions
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum DiffDisplayKind {
Metadata,
HunkHeader,
Context,
Addition,
Deletion,
}
impl DiffDisplayKind {
#[must_use]
pub const fn is_diff(self) -> bool {
matches!(self, Self::Context | Self::Addition | Self::Deletion)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DiffDisplayLine {
pub kind: DiffDisplayKind,
pub old_line: Option<u32>,
pub new_line: Option<u32>,
pub text: String,
pub changed: WordChangedRanges,
}
impl DiffDisplayLine {
#[must_use]
pub fn body(kind: DiffDisplayKind, old_line: Option<u32>, new_line: Option<u32>, text: String) -> Self {
Self {
kind,
old_line,
new_line,
text,
changed: Vec::new(),
}
}
#[must_use]
pub const fn is_diff(&self) -> bool {
self.kind.is_diff()
}
#[must_use]
pub fn numbered_text(&self, line_number_width: usize) -> String {
match self.kind {
DiffDisplayKind::Metadata | DiffDisplayKind::HunkHeader => self.text.clone(),
DiffDisplayKind::Deletion => {
format!("-{:>width$} │ {}", self.old_line.unwrap_or_default(), self.text, width = line_number_width)
}
DiffDisplayKind::Addition => {
format!("+{:>width$} │ {}", self.new_line.unwrap_or_default(), self.text, width = line_number_width)
}
DiffDisplayKind::Context => format!(
" {:>width$} │ {}",
self.new_line.or(self.old_line).unwrap_or_default(),
self.text,
width = line_number_width
),
}
}
}
pub const DIFF_MIN_SIDE_BY_SIDE_WIDTH: usize = 60;
#[must_use]
pub const fn diff_side_by_side_fits(available_width: Option<usize>) -> bool {
match available_width {
Some(width) => width >= DIFF_MIN_SIDE_BY_SIDE_WIDTH,
None => true,
}
}
pub const DIFF_MIN_BODY_WIDTH_WITH_GUTTER: usize = 20;
#[must_use]
pub const fn diff_gutter_width(line_number_width: usize) -> usize {
line_number_width.saturating_add(4)
}
#[must_use]
pub const fn diff_gutter_fits(available_width: usize, line_number_width: usize) -> bool {
available_width >= diff_gutter_width(line_number_width).saturating_add(DIFF_MIN_BODY_WIDTH_WITH_GUTTER)
}
#[must_use]
pub const fn diff_layout_width(available_width: usize, line_number_width: usize, show_gutter: bool) -> usize {
if show_gutter {
available_width
} else {
available_width.saturating_add(diff_gutter_width(line_number_width))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SideBySideRow {
pub left: Option<DiffDisplayLine>,
pub right: Option<DiffDisplayLine>,
}
impl SideBySideRow {
#[must_use]
pub fn is_full_width(&self) -> bool {
self.right.is_none()
&& self
.left
.as_ref()
.is_some_and(|line| matches!(line.kind, DiffDisplayKind::HunkHeader | DiffDisplayKind::Metadata))
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum DiffLayout {
#[default]
Unified,
SideBySide,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LayoutOptions {
pub layout: DiffLayout,
pub width: usize,
pub max_rows: usize,
pub wrap: bool,
pub min_side_by_side_width: usize,
}
impl Default for LayoutOptions {
fn default() -> Self {
Self {
layout: DiffLayout::Unified,
width: 80,
max_rows: 2_000,
wrap: true,
min_side_by_side_width: DIFF_MIN_SIDE_BY_SIDE_WIDTH,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiffRowKind {
Metadata,
HunkHeader,
Context,
Addition,
Deletion,
Omission,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DiffSegment {
pub text: String,
pub emphasized: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DiffCell {
pub old_line: Option<u32>,
pub new_line: Option<u32>,
pub marker: char,
pub segments: Vec<DiffSegment>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DiffRow {
pub kind: DiffRowKind,
pub marker: char,
pub hunk_index: Option<usize>,
pub continuation: bool,
pub left: Option<DiffCell>,
pub right: Option<DiffCell>,
}