ruff/crates/ruff_server/src/edit/replacement.rs

229 lines
5.9 KiB
Rust

use ruff_text_size::{TextLen, TextRange, TextSize};
#[derive(Debug)]
pub(crate) struct Replacement {
pub(crate) source_range: TextRange,
pub(crate) modified_range: TextRange,
}
impl Replacement {
/// Creates a [`Replacement`] that describes the `source_range` of `source` to replace
/// with `modified` sliced by `modified_range`.
pub(crate) fn between(
source: &str,
source_line_starts: &[TextSize],
modified: &str,
modified_line_starts: &[TextSize],
) -> Self {
let mut source_start = TextSize::default();
let mut modified_start = TextSize::default();
for (source_line_start, modified_line_start) in source_line_starts
.iter()
.copied()
.zip(modified_line_starts.iter().copied())
.skip(1)
{
if source[TextRange::new(source_start, source_line_start)]
!= modified[TextRange::new(modified_start, modified_line_start)]
{
break;
}
source_start = source_line_start;
modified_start = modified_line_start;
}
let mut source_end = source.text_len();
let mut modified_end = modified.text_len();
for (source_line_start, modified_line_start) in source_line_starts
.iter()
.rev()
.copied()
.zip(modified_line_starts.iter().rev().copied())
{
if source_line_start < source_start
|| modified_line_start < modified_start
|| source[TextRange::new(source_line_start, source_end)]
!= modified[TextRange::new(modified_line_start, modified_end)]
{
break;
}
source_end = source_line_start;
modified_end = modified_line_start;
}
Replacement {
source_range: TextRange::new(source_start, source_end),
modified_range: TextRange::new(modified_start, modified_end),
}
}
}
#[cfg(test)]
mod tests {
use ruff_source_file::LineIndex;
use ruff_text_size::TextRange;
use super::Replacement;
fn compute_replacement(source: &str, modified: &str) -> (Replacement, String) {
let source_index = LineIndex::from_source_text(source);
let modified_index = LineIndex::from_source_text(modified);
let replacement = Replacement::between(
source,
source_index.line_starts(),
modified,
modified_index.line_starts(),
);
let mut expected = source.to_string();
expected.replace_range(
replacement.source_range.start().to_usize()..replacement.source_range.end().to_usize(),
&modified[replacement.modified_range],
);
(replacement, expected)
}
#[test]
fn delete_first_line() {
let source = "aaaa
bbbb
cccc
";
let modified = "bbbb
cccc
";
let (replacement, expected) = compute_replacement(source, modified);
assert_eq!(replacement.source_range, TextRange::new(0.into(), 5.into()));
assert_eq!(replacement.modified_range, TextRange::empty(0.into()));
assert_eq!(modified, &expected);
}
#[test]
fn delete_middle_line() {
let source = "aaaa
bbbb
cccc
dddd
";
let modified = "aaaa
bbbb
dddd
";
let (replacement, expected) = compute_replacement(source, modified);
assert_eq!(
replacement.source_range,
TextRange::new(10.into(), 15.into())
);
assert_eq!(replacement.modified_range, TextRange::empty(10.into()));
assert_eq!(modified, &expected);
}
#[test]
fn delete_multiple_lines() {
let source = "aaaa
bbbb
cccc
dddd
eeee
ffff
";
let modified = "aaaa
cccc
dddd
ffff
";
let (replacement, expected) = compute_replacement(source, modified);
assert_eq!(
replacement.source_range,
TextRange::new(5.into(), 25.into())
);
assert_eq!(
replacement.modified_range,
TextRange::new(5.into(), 15.into())
);
assert_eq!(modified, &expected);
}
#[test]
fn insert_first_line() {
let source = "bbbb
cccc
";
let modified = "aaaa
bbbb
cccc
";
let (replacement, expected) = compute_replacement(source, modified);
assert_eq!(replacement.source_range, TextRange::empty(0.into()));
assert_eq!(
replacement.modified_range,
TextRange::new(0.into(), 5.into())
);
assert_eq!(modified, &expected);
}
#[test]
fn insert_middle_line() {
let source = "aaaa
cccc
";
let modified = "aaaa
bbbb
cccc
";
let (replacement, expected) = compute_replacement(source, modified);
assert_eq!(replacement.source_range, TextRange::empty(5.into()));
assert_eq!(
replacement.modified_range,
TextRange::new(5.into(), 10.into())
);
assert_eq!(modified, &expected);
}
#[test]
fn insert_multiple_lines() {
let source = "aaaa
cccc
eeee
";
let modified = "aaaa
bbbb
cccc
dddd
";
let (replacement, expected) = compute_replacement(source, modified);
assert_eq!(
replacement.source_range,
TextRange::new(5.into(), 15.into())
);
assert_eq!(
replacement.modified_range,
TextRange::new(5.into(), 20.into())
);
assert_eq!(modified, &expected);
}
#[test]
fn replace_lines() {
let source = "aaaa
bbbb
cccc
";
let modified = "aaaa
bbcb
cccc
";
let (replacement, expected) = compute_replacement(source, modified);
assert_eq!(
replacement.source_range,
TextRange::new(5.into(), 10.into())
);
assert_eq!(
replacement.modified_range,
TextRange::new(5.into(), 10.into())
);
assert_eq!(modified, &expected);
}
}