Skip to main content

ruff_diagnostics/
edit.rs

1use std::cmp::Ordering;
2
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6use ruff_text_size::{Ranged, TextRange, TextSize};
7
8/// A text edit to be applied to a source file. Inserts, deletes, or replaces
9/// content at a given location.
10#[derive(Clone, Debug, PartialEq, Eq, Hash, get_size2::GetSize)]
11#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
12pub struct Edit {
13    /// The start location of the edit.
14    range: TextRange,
15    /// The replacement content to insert between the start and end locations.
16    content: Option<Box<str>>,
17}
18
19impl Edit {
20    /// Creates an edit that deletes the content in the `start` to `end` range.
21    #[inline]
22    pub const fn deletion(start: TextSize, end: TextSize) -> Self {
23        Self::range_deletion(TextRange::new(start, end))
24    }
25
26    /// Creates an edit that deletes the content in `range`.
27    pub const fn range_deletion(range: TextRange) -> Self {
28        Self {
29            content: None,
30            range,
31        }
32    }
33
34    /// Creates an edit that replaces the content in the `start` to `end` range with `content`.
35    #[inline]
36    pub fn replacement(content: String, start: TextSize, end: TextSize) -> Self {
37        Self::range_replacement(content, TextRange::new(start, end))
38    }
39
40    /// Creates an edit that replaces the content in `range` with `content`.
41    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    /// Creates an edit that inserts `content` at the [`TextSize`] `at`.
51    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    /// Returns the new content for an insertion or deletion.
61    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    /// Returns `true` if this edit deletes content from the source document.
80    #[inline]
81    pub fn is_deletion(&self) -> bool {
82        self.kind().is_deletion()
83    }
84
85    /// Returns `true` if this edit inserts new content into the source document.
86    #[inline]
87    pub fn is_insertion(&self) -> bool {
88        self.kind().is_insertion()
89    }
90
91    /// Returns `true` if this edit replaces some existing content with new content.
92    #[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    /// Edit that inserts new content into the source document.
122    Insertion,
123
124    /// Edit that deletes content from the source document.
125    Deletion,
126
127    /// Edit that replaces content from the source document.
128    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}