Skip to main content

roder_edit_core/
hunks.rs

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}