1use std::collections::BTreeMap;
2
3use blazingly_json::Value;
4use serde::{Deserialize, Serialize};
5
6use crate::{
10 error::EditError,
11 limits::PlanLimits,
12 validation::{ValidatedEditPlan, validate_edit_plan},
13};
14
15pub use crate::provenance::Provenance;
16
17pub const EDIT_PLAN_SCHEMA: &str = "weavatrix.edit-plan.v1";
19
20#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
22pub struct Position {
23 pub line: u32,
24 pub character: u32,
25}
26
27#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
29pub enum PositionEncoding {
30 Utf8,
31 #[default]
32 Utf16,
33 Utf32,
34}
35
36impl Position {
37 #[must_use]
38 pub const fn new(line: u32, character: u32) -> Self {
39 Self { line, character }
40 }
41}
42
43#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
45pub struct TextRange {
46 pub start: Position,
47 pub end: Position,
48}
49
50impl TextRange {
51 #[must_use]
52 pub const fn new(start: Position, end: Position) -> Self {
53 Self { start, end }
54 }
55
56 #[must_use]
57 pub const fn empty(position: Position) -> Self {
58 Self::new(position, position)
59 }
60}
61
62#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
64#[serde(transparent)]
65pub struct Completeness(pub String);
66
67impl Completeness {
68 pub const COMPLETE: &'static str = "COMPLETE";
69 pub const PARTIAL: &'static str = "PARTIAL";
70
71 #[must_use]
72 pub fn new(value: impl Into<String>) -> Self {
73 Self(value.into())
74 }
75
76 #[must_use]
77 pub fn as_str(&self) -> &str {
78 &self.0
79 }
80}
81
82#[derive(Clone, Debug, PartialEq)]
87pub struct TextEdit {
88 pub start_line: u32,
89 pub start_char: u32,
90 pub end_line: u32,
91 pub end_char: u32,
92 pub before: String,
93 pub after: String,
94 pub provenance: Provenance,
95 pub extensions: BTreeMap<String, Value>,
96}
97
98#[derive(Clone, Debug, Eq, PartialEq)]
100pub struct ByteEdit {
101 pub start: usize,
102 pub end: usize,
103 pub before: String,
104 pub after: String,
105 pub provenance: Provenance,
106}
107
108impl ByteEdit {
109 #[must_use]
110 pub fn replace(
111 range: core::ops::Range<usize>,
112 before: impl Into<String>,
113 after: impl Into<String>,
114 provenance: impl AsRef<str>,
115 ) -> Self {
116 Self {
117 start: range.start,
118 end: range.end,
119 before: before.into(),
120 after: after.into(),
121 provenance: Provenance::new(provenance),
122 }
123 }
124
125 #[must_use]
126 pub fn insert(offset: usize, after: impl Into<String>, provenance: impl AsRef<str>) -> Self {
127 Self::replace(offset..offset, "", after, provenance)
128 }
129
130 #[must_use]
131 pub fn delete(
132 range: core::ops::Range<usize>,
133 before: impl Into<String>,
134 provenance: impl AsRef<str>,
135 ) -> Self {
136 Self::replace(range, before, "", provenance)
137 }
138}
139
140impl TextEdit {
141 #[must_use]
142 pub fn replace(
143 range: TextRange,
144 before: impl Into<String>,
145 after: impl Into<String>,
146 provenance: impl AsRef<str>,
147 ) -> Self {
148 Self {
149 start_line: range.start.line,
150 start_char: range.start.character,
151 end_line: range.end.line,
152 end_char: range.end.character,
153 before: before.into(),
154 after: after.into(),
155 provenance: Provenance::new(provenance),
156 extensions: BTreeMap::new(),
157 }
158 }
159
160 #[must_use]
161 pub fn insert(
162 position: Position,
163 after: impl Into<String>,
164 provenance: impl AsRef<str>,
165 ) -> Self {
166 Self::replace(TextRange::empty(position), "", after, provenance)
167 }
168
169 #[must_use]
170 pub fn delete(
171 range: TextRange,
172 before: impl Into<String>,
173 provenance: impl AsRef<str>,
174 ) -> Self {
175 Self::replace(range, before, "", provenance)
176 }
177
178 #[must_use]
179 pub const fn range(&self) -> TextRange {
180 TextRange::new(
181 Position::new(self.start_line, self.start_char),
182 Position::new(self.end_line, self.end_char),
183 )
184 }
185}
186
187#[derive(Clone, Debug, PartialEq)]
189pub struct FileEdit {
190 pub path: String,
191 pub sha256: String,
192 pub edits: Vec<TextEdit>,
193 pub extensions: BTreeMap<String, Value>,
194}
195
196impl FileEdit {
197 #[must_use]
198 pub fn new(path: impl Into<String>, sha256: impl Into<String>, edits: Vec<TextEdit>) -> Self {
199 Self {
200 path: path.into(),
201 sha256: sha256.into(),
202 edits,
203 extensions: BTreeMap::new(),
204 }
205 }
206}
207
208#[derive(Clone, Debug, PartialEq)]
215pub struct EditPlan {
216 pub schema_version: String,
217 pub operation: String,
218 pub files: Vec<FileEdit>,
219 pub completeness: Option<Completeness>,
220 pub extensions: BTreeMap<String, Value>,
221}
222
223impl EditPlan {
224 #[must_use]
225 pub fn new(operation: impl Into<String>, files: Vec<FileEdit>) -> Self {
226 Self {
227 schema_version: EDIT_PLAN_SCHEMA.to_owned(),
228 operation: operation.into(),
229 files,
230 completeness: None,
231 extensions: BTreeMap::new(),
232 }
233 }
234
235 pub fn validate(&self) -> Result<ValidatedEditPlan<'_>, EditError> {
236 validate_edit_plan(self, PlanLimits::default())
237 }
238
239 pub fn validate_with(&self, limits: PlanLimits) -> Result<ValidatedEditPlan<'_>, EditError> {
240 validate_edit_plan(self, limits)
241 }
242}