1use std::cmp::Ordering;
2
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6use ruff_text_size::{Ranged, TextRange, TextSize};
7
8#[derive(Clone, Debug, PartialEq, Eq, Hash, get_size2::GetSize)]
11#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
12pub struct Edit {
13 range: TextRange,
15 content: Option<Box<str>>,
17}
18
19impl Edit {
20 #[inline]
22 pub const fn deletion(start: TextSize, end: TextSize) -> Self {
23 Self::range_deletion(TextRange::new(start, end))
24 }
25
26 pub const fn range_deletion(range: TextRange) -> Self {
28 Self {
29 content: None,
30 range,
31 }
32 }
33
34 #[inline]
36 pub fn replacement(content: String, start: TextSize, end: TextSize) -> Self {
37 Self::range_replacement(content, TextRange::new(start, end))
38 }
39
40 pub fn range_replacement(content: String, range: TextRange) -> Self {
42 debug_assert!(!content.is_empty(), "Prefer `Edit::deletion`");
43
44 Self {
45 content: Some(Box::from(content)),
46 range,
47 }
48 }
49
50 pub fn insertion(content: String, at: TextSize) -> Self {
52 debug_assert!(!content.is_empty(), "Insert content is empty");
53
54 Self {
55 content: Some(Box::from(content)),
56 range: TextRange::new(at, at),
57 }
58 }
59
60 pub fn content(&self) -> Option<&str> {
62 self.content.as_deref()
63 }
64
65 pub fn into_content(self) -> Option<Box<str>> {
66 self.content
67 }
68
69 fn kind(&self) -> EditOperationKind {
70 if self.content.is_none() {
71 EditOperationKind::Deletion
72 } else if self.range.is_empty() {
73 EditOperationKind::Insertion
74 } else {
75 EditOperationKind::Replacement
76 }
77 }
78
79 #[inline]
81 pub fn is_deletion(&self) -> bool {
82 self.kind().is_deletion()
83 }
84
85 #[inline]
87 pub fn is_insertion(&self) -> bool {
88 self.kind().is_insertion()
89 }
90
91 #[inline]
93 pub fn is_replacement(&self) -> bool {
94 self.kind().is_replacement()
95 }
96}
97
98impl Ord for Edit {
99 fn cmp(&self, other: &Self) -> Ordering {
100 self.start()
101 .cmp(&other.start())
102 .then_with(|| self.end().cmp(&other.end()))
103 .then_with(|| self.content.cmp(&other.content))
104 }
105}
106
107impl PartialOrd for Edit {
108 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
109 Some(self.cmp(other))
110 }
111}
112
113impl Ranged for Edit {
114 fn range(&self) -> TextRange {
115 self.range
116 }
117}
118
119#[derive(Copy, Clone, Eq, PartialEq, Debug)]
120enum EditOperationKind {
121 Insertion,
123
124 Deletion,
126
127 Replacement,
129}
130
131impl EditOperationKind {
132 const fn is_insertion(self) -> bool {
133 matches!(self, EditOperationKind::Insertion)
134 }
135
136 const fn is_deletion(self) -> bool {
137 matches!(self, EditOperationKind::Deletion)
138 }
139
140 const fn is_replacement(self) -> bool {
141 matches!(self, EditOperationKind::Replacement)
142 }
143}