backhopper_core/compat/
added_lines.rs1use crate::compat::call_sites::{AttrCtxScanner, strip_line_comment};
11use crate::compat::patch::{Hunk, HunkLine};
12use crate::model::names::RelativePath;
13use crate::model::symbol::LineClass;
14
15#[derive(Debug, Clone, Copy)]
19pub struct AddedLinesSubject<'a> {
20 pub source_path: &'a RelativePath,
21 pub added_text: &'a str,
22 pub line_map: &'a [u32],
23}
24
25pub fn added_lines_with_offsets(hunks: &[Hunk]) -> (String, Vec<u32>) {
30 let mut blob = String::new();
31 let mut line_map = Vec::new();
32 for hunk in hunks {
33 let mut new_line = hunk.new_start as u32;
34 for line in &hunk.lines {
35 match line {
36 HunkLine::Added(s) => {
37 blob.push_str(s);
38 blob.push('\n');
39 line_map.push(new_line);
40 new_line += 1;
41 }
42 HunkLine::Context(_) => new_line += 1,
43 HunkLine::Removed(_) => {}
44 }
45 }
46 }
47 (blob, line_map)
48}
49
50#[derive(Debug, Clone, Copy)]
52pub enum LineContextSource<'a> {
53 PostImage(&'a str),
56 HunksOnly,
58}
59
60pub fn added_lines_with_context(
65 hunks: &[Hunk],
66 source: LineContextSource<'_>,
67) -> (String, Vec<u32>, Vec<LineClass>) {
68 let (blob, line_map) = added_lines_with_offsets(hunks);
69 let ctx = match source {
70 LineContextSource::PostImage(text) => post_image_classes(text, &blob, &line_map),
71 LineContextSource::HunksOnly => None,
72 }
73 .unwrap_or_else(|| hunk_classes(hunks));
74 (blob, line_map, ctx)
75}
76
77fn post_image_classes(text: &str, blob: &str, line_map: &[u32]) -> Option<Vec<LineClass>> {
78 let mut scanner = AttrCtxScanner::new();
79 let classified: Vec<(&str, LineClass)> = text
80 .lines()
81 .map(|line| (line, scanner.classify(strip_line_comment(line))))
82 .collect();
83 blob.lines()
84 .zip(line_map)
85 .map(|(added, &file_line)| {
86 let (line, class) = classified.get((file_line as usize).checked_sub(1)?)?;
87 (line.trim_end() == added.trim_end()).then(|| class.clone())
88 })
89 .collect()
90}
91
92fn hunk_classes(hunks: &[Hunk]) -> Vec<LineClass> {
96 let mut ctx = Vec::new();
97 let mut scanner = AttrCtxScanner::new();
98 for hunk in hunks {
99 for line in &hunk.lines {
100 match line {
101 HunkLine::Added(s) => ctx.push(scanner.classify(strip_line_comment(s))),
102 HunkLine::Context(s) => {
103 scanner.classify(strip_line_comment(s));
104 }
105 HunkLine::Removed(_) => {}
106 }
107 }
108 }
109 ctx
110}
111
112pub fn file_line(line_map: &[u32], blob_line: u32) -> u32 {
116 line_map
117 .get(blob_line.saturating_sub(1) as usize)
118 .copied()
119 .unwrap_or(blob_line)
120}