1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4pub struct EditHunk {
5 pub id: Option<String>,
6 pub path: String,
7 pub old_start: u32,
8 pub old_lines: u32,
9 pub new_start: u32,
10 pub new_lines: u32,
11 pub diff: Vec<HunkDiffLine>,
12 pub reverse_patch: Option<String>,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
16pub struct HunkDiffLine {
17 pub kind: HunkDiffLineKind,
18 pub text: String,
19 pub old_line: Option<u32>,
20 pub new_line: Option<u32>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
24#[serde(rename_all = "camelCase")]
25pub enum HunkDiffLineKind {
26 Context,
27 Added,
28 Removed,
29}
30
31pub fn text_edit_hunk(
32 path: impl Into<String>,
33 old_text: &str,
34 new_text: &str,
35 index: usize,
36) -> EditHunk {
37 let path = path.into();
38 let old_lines = old_text.lines().map(str::to_string).collect::<Vec<_>>();
39 let new_lines = new_text.lines().map(str::to_string).collect::<Vec<_>>();
40 lines_hunk(path, old_lines, new_lines, index)
41}
42
43pub fn lines_hunk(
44 path: impl Into<String>,
45 old_lines: Vec<String>,
46 new_lines: Vec<String>,
47 index: usize,
48) -> EditHunk {
49 let path = path.into();
50 let mut diff = Vec::new();
51 for (old_line, line) in (1u32..).zip(old_lines.iter()) {
52 diff.push(HunkDiffLine {
53 kind: HunkDiffLineKind::Removed,
54 text: line.clone(),
55 old_line: Some(old_line),
56 new_line: None,
57 });
58 }
59 for (new_line, line) in (1u32..).zip(new_lines.iter()) {
60 diff.push(HunkDiffLine {
61 kind: HunkDiffLineKind::Added,
62 text: line.clone(),
63 old_line: None,
64 new_line: Some(new_line),
65 });
66 }
67 EditHunk {
68 id: Some(format!("hunk-{}", index + 1)),
69 path: path.clone(),
70 old_start: 1,
71 old_lines: old_lines.len() as u32,
72 new_start: 1,
73 new_lines: new_lines.len() as u32,
74 diff,
75 reverse_patch: Some(reverse_codex_patch(&path, &old_lines, &new_lines)),
76 }
77}
78
79pub fn reverse_codex_patch(path: &str, old_lines: &[String], new_lines: &[String]) -> String {
80 let mut patch = String::from("*** Begin Patch\n");
81 patch.push_str(&format!("*** Update File: {path}\n@@\n"));
82 for line in new_lines {
83 patch.push('-');
84 patch.push_str(line);
85 patch.push('\n');
86 }
87 for line in old_lines {
88 patch.push('+');
89 patch.push_str(line);
90 patch.push('\n');
91 }
92 patch.push_str("*** End Patch\n");
93 patch
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn hunk_contains_reverse_patch() {
102 let hunk = text_edit_hunk("src/lib.rs", "old", "new", 0);
103 assert_eq!(hunk.old_lines, 1);
104 assert_eq!(hunk.new_lines, 1);
105 assert!(hunk.reverse_patch.unwrap().contains("-new\n+old"));
106 }
107}