Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,263 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use ai::diff_validation::DiffDelta;
|
||||
use rangemap::RangeSet;
|
||||
use warp_editor::content::text::LineCount;
|
||||
use warp_editor::render::model::LineCount as RenderLineCount;
|
||||
|
||||
/// The number of context lines to show before and after each change
|
||||
const CONTEXT_LINES: usize = 4;
|
||||
|
||||
/// Calculate which lines should be hidden in a file after applying diffs.
|
||||
///
|
||||
/// This function takes a list of diff deltas and comment line numbers, then calculates which lines should be hidden
|
||||
/// (everything except for CONTEXT_LINES before and after each change and comment).
|
||||
///
|
||||
/// These are 0-indexed line numbers BEFORE the diffs are applied, so the first line is 0.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `diffs` - The list of diff deltas that will be applied to the file
|
||||
/// * `line_count` - The total number of lines in the file before any diffs are applied
|
||||
/// * `comment_line_numbers` - Line numbers where comments exist (0-indexed)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `RangeSet<usize>` containing the line ranges that should be hidden (0-indexed).
|
||||
///
|
||||
/// Note that DiffDelta uses 1-indexed line ranges, so we convert them to 0-indexed
|
||||
/// ```
|
||||
pub fn calculate_hidden_lines(
|
||||
diffs: &[DiffDelta],
|
||||
line_count: usize,
|
||||
comment_line_numbers: &[RenderLineCount],
|
||||
) -> RangeSet<LineCount> {
|
||||
// Calculate the visible line ranges (with context)
|
||||
let mut visible_ranges: RangeSet<LineCount> = RangeSet::new();
|
||||
|
||||
// Add ranges for diffs
|
||||
for diff in diffs {
|
||||
// Convert 1-indexed line ranges to 0-indexed
|
||||
let start_line = diff.replacement_line_range.start.saturating_sub(1);
|
||||
let end_line = diff.replacement_line_range.end.saturating_sub(1);
|
||||
|
||||
let context_start = start_line.saturating_sub(CONTEXT_LINES);
|
||||
let context_end = end_line + CONTEXT_LINES;
|
||||
|
||||
if context_start < context_end {
|
||||
visible_ranges.insert(context_start.into()..context_end.into());
|
||||
}
|
||||
}
|
||||
|
||||
// Add ranges for comments
|
||||
for &comment_line in comment_line_numbers {
|
||||
let line_number = comment_line.as_usize();
|
||||
let context_start = line_number.saturating_sub(CONTEXT_LINES);
|
||||
let context_end = (line_number + CONTEXT_LINES + 1).min(line_count); // +1 because we want to include the line itself, clamped to file bounds
|
||||
|
||||
if context_start < context_end {
|
||||
visible_ranges.insert(LineCount::from(context_start)..LineCount::from(context_end));
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate hidden ranges as the complement of visible ranges
|
||||
let all_lines: Range<LineCount> = LineCount::from(0)..LineCount::from(line_count);
|
||||
|
||||
// Find gaps in the visible ranges
|
||||
visible_ranges
|
||||
.gaps(&all_lines)
|
||||
.collect::<RangeSet<LineCount>>()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_empty_diffs() {
|
||||
let hidden_lines = calculate_hidden_lines(&[], 20, &[]);
|
||||
|
||||
let expected_hidden = [LineCount::from(0)..LineCount::from(20)]
|
||||
.into_iter()
|
||||
.collect::<RangeSet<LineCount>>();
|
||||
|
||||
assert_eq!(hidden_lines, expected_hidden);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_diff_middle_of_file() {
|
||||
// File with 20 lines, change at lines 10-12 (1-indexed)
|
||||
let diffs = vec![DiffDelta {
|
||||
replacement_line_range: 10..13, // 1-indexed, replacing lines 10, 11, 12
|
||||
insertion: "new line 1\nnew line 2\nnew line 3".to_string(), // 3 lines
|
||||
}];
|
||||
|
||||
let hidden_lines = calculate_hidden_lines(&diffs, 20, &[]);
|
||||
|
||||
let mut expected_hidden = RangeSet::new();
|
||||
expected_hidden.insert(0.into()..5.into()); // lines 1-5 (1-indexed)
|
||||
expected_hidden.insert(16.into()..20.into()); // lines 17-20 (1-indexed)
|
||||
|
||||
assert_eq!(hidden_lines, expected_hidden);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insertion_at_beginning() {
|
||||
// Insert at the very beginning of file
|
||||
let diffs = vec![DiffDelta {
|
||||
replacement_line_range: 1..1, // Insert at beginning
|
||||
insertion: "new line 1\nnew line 2".to_string(), // 2 lines
|
||||
}];
|
||||
|
||||
let hidden_lines = calculate_hidden_lines(&diffs, 20, &[]);
|
||||
|
||||
let mut expected_hidden = RangeSet::new();
|
||||
expected_hidden.insert(4.into()..20.into());
|
||||
|
||||
assert_eq!(hidden_lines, expected_hidden);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_diffs_overlapping_context() {
|
||||
// Two changes close enough that their context overlaps
|
||||
let diffs = vec![
|
||||
DiffDelta {
|
||||
replacement_line_range: 5..6, // 1-indexed, replace line 5
|
||||
insertion: "change 1".to_string(), // 1 line
|
||||
},
|
||||
DiffDelta {
|
||||
replacement_line_range: 8..9, // 1-indexed, replace line 8
|
||||
insertion: "change 2".to_string(), // 1 line
|
||||
},
|
||||
];
|
||||
|
||||
let hidden_lines = calculate_hidden_lines(&diffs, 20, &[]);
|
||||
let mut expected_hidden = RangeSet::new();
|
||||
expected_hidden.insert(12.into()..20.into());
|
||||
|
||||
assert_eq!(hidden_lines, expected_hidden);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_diffs_separate_context() {
|
||||
// Two changes far apart
|
||||
let diffs = vec![
|
||||
DiffDelta {
|
||||
replacement_line_range: 3..4, // 1-indexed, replace line 3
|
||||
insertion: "change 1".to_string(), // 1 line
|
||||
},
|
||||
DiffDelta {
|
||||
replacement_line_range: 15..16, // 1-indexed, replace line 15
|
||||
insertion: "change 2".to_string(), // 1 line
|
||||
},
|
||||
];
|
||||
|
||||
let hidden_lines = calculate_hidden_lines(&diffs, 20, &[]);
|
||||
|
||||
let mut expected_hidden = RangeSet::new();
|
||||
expected_hidden.insert(7.into()..10.into());
|
||||
expected_hidden.insert(19.into()..20.into());
|
||||
|
||||
assert_eq!(hidden_lines, expected_hidden);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_comments_only_no_diffs() {
|
||||
// File with 20 lines, comments at lines 5 and 15 (0-indexed)
|
||||
let comment_lines = vec![RenderLineCount::from(5), RenderLineCount::from(15)];
|
||||
|
||||
let hidden_lines = calculate_hidden_lines(&[], 20, &comment_lines);
|
||||
|
||||
let mut expected_hidden = RangeSet::new();
|
||||
// Comment at line 5 shows lines 1-9 (0-indexed), so hide lines 0 and 10-14
|
||||
expected_hidden.insert(0.into()..1.into());
|
||||
expected_hidden.insert(10.into()..11.into());
|
||||
// Comment at line 15 shows lines 11-19 (0-indexed), so hide line 20
|
||||
// Note: lines 10-19 are visible due to comment at line 15
|
||||
|
||||
assert_eq!(hidden_lines, expected_hidden);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_comment_overlapping_with_diff_context() {
|
||||
// File with 20 lines, diff at lines 8-9 (1-indexed) and comment at line 10 (0-indexed)
|
||||
let diffs = vec![DiffDelta {
|
||||
replacement_line_range: 8..10, // 1-indexed, replacing lines 8, 9
|
||||
insertion: "new line 1\nnew line 2".to_string(),
|
||||
}];
|
||||
let comment_lines = vec![RenderLineCount::from(10)];
|
||||
|
||||
let hidden_lines = calculate_hidden_lines(&diffs, 20, &comment_lines);
|
||||
|
||||
// Diff at lines 8-9 (1-indexed) = lines 7-8 (0-indexed) with ±4 context = lines 3-12
|
||||
// Comment at line 10 (0-indexed) with ±4 context = lines 6-14
|
||||
// Combined visible range: lines 3-14
|
||||
let mut expected_hidden = RangeSet::new();
|
||||
expected_hidden.insert(0.into()..3.into());
|
||||
expected_hidden.insert(15.into()..20.into());
|
||||
|
||||
assert_eq!(hidden_lines, expected_hidden);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_comment_separate_from_diffs() {
|
||||
// File with 30 lines, diff at lines 5-6 (1-indexed) and comment at line 20 (0-indexed)
|
||||
let diffs = vec![DiffDelta {
|
||||
replacement_line_range: 5..7, // 1-indexed, replacing lines 5, 6
|
||||
insertion: "changed line".to_string(),
|
||||
}];
|
||||
let comment_lines = vec![RenderLineCount::from(20)];
|
||||
|
||||
let hidden_lines = calculate_hidden_lines(&diffs, 30, &comment_lines);
|
||||
|
||||
// Diff at lines 5-6 (1-indexed) = lines 4-5 (0-indexed) with ±4 context = lines 0-9
|
||||
// Comment at line 20 (0-indexed) with ±4 context = lines 16-24
|
||||
let mut expected_hidden = RangeSet::new();
|
||||
expected_hidden.insert(10.into()..16.into());
|
||||
expected_hidden.insert(25.into()..30.into());
|
||||
|
||||
assert_eq!(hidden_lines, expected_hidden);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_comment_outside_file_bounds() {
|
||||
// File with 10 lines, comment at line 15 (0-indexed) - outside bounds
|
||||
let comment_lines = vec![RenderLineCount::from(15)];
|
||||
|
||||
let hidden_lines = calculate_hidden_lines(&[], 10, &comment_lines);
|
||||
|
||||
// Comment is outside file bounds, so everything should be hidden
|
||||
let mut expected_hidden = RangeSet::new();
|
||||
expected_hidden.insert(0.into()..10.into());
|
||||
|
||||
assert_eq!(hidden_lines, expected_hidden);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_comment_at_file_beginning() {
|
||||
// File with 20 lines, comment at line 1 (0-indexed)
|
||||
let comment_lines = vec![RenderLineCount::from(1)];
|
||||
|
||||
let hidden_lines = calculate_hidden_lines(&[], 20, &comment_lines);
|
||||
|
||||
// Comment at line 1 with ±4 context = lines 0-5 (saturating_sub handles negative)
|
||||
let mut expected_hidden = RangeSet::new();
|
||||
expected_hidden.insert(6.into()..20.into());
|
||||
|
||||
assert_eq!(hidden_lines, expected_hidden);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_comment_at_file_end() {
|
||||
// File with 20 lines, comment at line 18 (0-indexed)
|
||||
let comment_lines = vec![RenderLineCount::from(18)];
|
||||
|
||||
let hidden_lines = calculate_hidden_lines(&[], 20, &comment_lines);
|
||||
|
||||
// Comment at line 18 with ±4 context = lines 14-19 (clamped to file bounds)
|
||||
let mut expected_hidden = RangeSet::new();
|
||||
expected_hidden.insert(0.into()..14.into());
|
||||
|
||||
assert_eq!(hidden_lines, expected_hidden);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user