#![forbid(unsafe_code)]
pub mod buffer;
pub mod change;
pub mod decoration;
pub mod history;
pub mod movement;
pub mod position;
pub mod search;
pub mod selection;
pub mod transaction;
pub use buffer::{Buffer, EditError, EditResult, LineEnding};
pub use change::{Assoc, ChangeSet, ChangeSetBuilder, ChangedSpan, Operation, RangeEffect};
pub use decoration::{
Decoration, DecorationClass, DecorationSet, HunkSide, LineDecoration, Severity, SyntaxKind,
};
pub use history::History;
pub use search::{find_all, CaseMode, Match};
pub use selection::{Range, Selection};
pub use transaction::{EditSource, EditTransaction, UndoGroupId, Version};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConflictReason {
EditedInsideRange,
AnchorDeleted,
}
impl ConflictReason {
pub fn from_effect(effect: RangeEffect) -> Option<Self> {
match effect {
RangeEffect::Untouched => None,
RangeEffect::InsertedInside => Some(ConflictReason::EditedInsideRange),
RangeEffect::PartlyDeleted => Some(ConflictReason::AnchorDeleted),
}
}
}
impl core::fmt::Display for ConflictReason {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ConflictReason::EditedInsideRange => f.write_str("you edited inside this change"),
ConflictReason::AnchorDeleted => {
f.write_str("the code this change referred to is gone")
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HunkState {
Clean,
Conflicted(ConflictReason),
Satisfied,
}
impl HunkState {
pub fn is_applicable(&self) -> bool {
matches!(self, HunkState::Clean)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_clean_hunks_apply() {
assert!(HunkState::Clean.is_applicable());
assert!(!HunkState::Conflicted(ConflictReason::AnchorDeleted).is_applicable());
assert!(!HunkState::Satisfied.is_applicable(), "already done is not applied again");
}
#[test]
fn a_surviving_range_is_not_a_conflict() {
assert_eq!(ConflictReason::from_effect(RangeEffect::Untouched), None);
}
#[test]
fn each_overlap_becomes_the_conflict_it_implies() {
assert_eq!(
ConflictReason::from_effect(RangeEffect::InsertedInside),
Some(ConflictReason::EditedInsideRange)
);
assert_eq!(
ConflictReason::from_effect(RangeEffect::PartlyDeleted),
Some(ConflictReason::AnchorDeleted)
);
}
#[test]
fn a_human_edit_inside_a_hunk_makes_it_unapplicable() {
let hunk = (10, 20);
let typed_inside = ChangeSet::replace(40, 15, 15, "mine");
let state = match ConflictReason::from_effect(typed_inside.touches(hunk.0, hunk.1)) {
Some(reason) => HunkState::Conflicted(reason),
None => HunkState::Clean,
};
assert_eq!(state, HunkState::Conflicted(ConflictReason::EditedInsideRange));
assert!(!state.is_applicable(), "never silently destroy what the human wrote");
}
#[test]
fn every_conflict_reason_explains_itself_to_the_user() {
for reason in [ConflictReason::EditedInsideRange, ConflictReason::AnchorDeleted] {
let msg = reason.to_string();
assert!(!msg.is_empty());
assert!(!msg.contains("Conflict"), "should read as prose, got {msg:?}");
}
}
}